Files

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 &parameters) {
return (packet.flags & AV_PKT_FLAG_KEY) &&
(parameters.codec_id != AV_CODEC_ID_H264 || hasIDR(packet, parameters));
}
inline int prepareVideoPacket(AVCodecContext *&context, const AVCodecParameters &parameters,
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, &parameters);
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);
}
}