#pragma once #include extern "C" { #include #include } 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); } }