75 lines
3.6 KiB
C++
75 lines
3.6 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
extern "C" {
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavutil/hwcontext.h>
|
|
}
|
|
|
|
namespace playback {
|
|
inline bool hasIDR(const AVPacket &packet, const AVCodecParameters &codec) {
|
|
const auto *bytes = packet.data;
|
|
const size_t size = size_t(std::max(0, packet.size));
|
|
if (codec.extradata_size >= 5 && codec.extradata[0] == 1) {
|
|
const size_t lengthSize = (codec.extradata[4] & 3) + 1;
|
|
for (size_t at = 0; at + lengthSize <= size;) {
|
|
uint32_t length = 0;
|
|
for (size_t i = 0; i < lengthSize; ++i) length = (length << 8) | bytes[at++];
|
|
if (!length || length > size - at) return false;
|
|
if ((bytes[at] & 31) == 5) return true;
|
|
at += length;
|
|
}
|
|
return false;
|
|
}
|
|
for (size_t at = 0; at + 3 < size; ++at) {
|
|
if (bytes[at] == 0 && bytes[at + 1] == 0 && bytes[at + 2] == 1 &&
|
|
(bytes[at + 3] & 31) == 5) return true;
|
|
}
|
|
return false;
|
|
}
|
|
// FFmpeg retries software if VideoToolbox rejects the stream; HEVC may also use
|
|
// Apple's platform software fallback. Keep the normal decoder for other codecs.
|
|
static inline void preferVideoToolbox(AVCodecContext *context) {
|
|
#ifndef TEREBI_SOFTWARE_DECODE
|
|
// Prefer only the codecs measured in native playback and the Cast relay.
|
|
if (context->codec_id != AV_CODEC_ID_H264 && context->codec_id != AV_CODEC_ID_HEVC &&
|
|
context->codec_id != AV_CODEC_ID_VP9) return;
|
|
for (int index = 0; const auto *config = avcodec_get_hw_config(context->codec, index); ++index) {
|
|
if (config->device_type != AV_HWDEVICE_TYPE_VIDEOTOOLBOX ||
|
|
!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) continue;
|
|
// The default get_format prefers this device and retries software if
|
|
// VideoToolbox rejects the stream; device creation failure is also safe.
|
|
av_hwdevice_ctx_create(&context->hw_device_ctx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
|
nullptr, nullptr, 0);
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
// Starting at a dependent packet can make VideoToolbox fail during send_packet,
|
|
// after format negotiation has already succeeded. Preserve software preroll.
|
|
inline bool independentVideoPacket(const AVPacket &packet, const AVCodecParameters ¶meters) {
|
|
return (packet.flags & AV_PKT_FLAG_KEY) &&
|
|
(parameters.codec_id != AV_CODEC_ID_H264 || hasIDR(packet, parameters));
|
|
}
|
|
inline int prepareVideoPacket(AVCodecContext *&context, const AVCodecParameters ¶meters,
|
|
const AVPacket &packet) {
|
|
if (!context->hw_device_ctx || independentVideoPacket(packet, parameters)) return 0;
|
|
auto *software = avcodec_alloc_context3(context->codec);
|
|
if (!software) return AVERROR(ENOMEM);
|
|
int result = avcodec_parameters_to_context(software, ¶meters);
|
|
software->pkt_timebase = context->pkt_timebase;
|
|
software->thread_count = context->thread_count;
|
|
software->max_pixels = context->max_pixels;
|
|
if (result >= 0) result = avcodec_open2(software, context->codec, nullptr);
|
|
if (result < 0) { avcodec_free_context(&software); return result; }
|
|
avcodec_free_context(&context); context = software;
|
|
return 0;
|
|
}
|
|
// Mapping retains and locks the CVPixelBuffer until the last frame reference
|
|
// is released. Consumers see ordinary CPU-readable planes without a pixel copy.
|
|
inline int refVideoFrame(AVFrame *destination, const AVFrame *source) {
|
|
if (source->format == AV_PIX_FMT_VIDEOTOOLBOX)
|
|
return av_hwframe_map(destination, source, AV_HWFRAME_MAP_READ);
|
|
return av_frame_ref(destination, source);
|
|
}
|
|
}
|