perf: prefer VideoToolbox decoding with software fallback
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user