perf: prefer VideoToolbox decoding with software fallback

This commit is contained in:
2026-10-17 01:11:58 +02:00
parent 599c1492ee
commit fcad96de62
8 changed files with 513 additions and 36 deletions
+25 -23
View File
@@ -1,6 +1,7 @@
#include "cast_media.h"
#include "network_policy.hpp"
#include "network_input.hpp"
#include "video_decoder.hpp"
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
@@ -40,6 +41,9 @@ using Clock = std::chrono::steady_clock;
constexpr double segmentSeconds = 6;
constexpr size_t segmentLimit = 32 * 1024 * 1024;
constexpr size_t windowSegments = 8;
#ifdef CAST_MEDIA_CHECK
std::atomic<uint64_t> hardwareVideoFrames{0}, softwareVideoFrames{0};
#endif
void check(int value) { if (value < 0) throw std::runtime_error("Cast media operation failed (private details redacted)."); }
void require(bool value) { if (!value) throw std::runtime_error("Cast media is unavailable or unsupported (private details redacted)."); }
void privateLog(void *, int level, const char *, va_list) {
@@ -55,26 +59,6 @@ Frame frame() { auto p = Frame(av_frame_alloc()); require(bool(p)); return p; }
Packet packet() { auto p = Packet(av_packet_alloc()); require(bool(p)); return p; }
int64_t aacBoundary(double seconds) { return int64_t(std::ceil(seconds * 48000 / 1024)) * 1024; }
double aacPreroll(double seconds) { return double(std::max<int64_t>(0, aacBoundary(seconds) - 12 * 1024)) / 48000; }
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;
}
struct Source {
std::string video, videoHeaders, audio, audioHeaders;
bool local, restricted, live;
@@ -234,7 +218,7 @@ struct Track {
const auto rate = av_guess_frame_rate(input->format, stream, nullptr);
const double openingGap = rate.num > 0 ? av_q2d(av_inv_q(rate)) : 0;
remux = remux && !live && pending && (pending->flags & AV_PKT_FLAG_KEY) &&
hasIDR(*pending, *stream->codecpar) &&
playback::hasIDR(*pending, *stream->codecpar) &&
(std::abs(packetTime() - start) < .001 ||
(start == 0 && packetTime() >= 0 && packetTime() <= openingGap));
if (aligned && !remux) {
@@ -243,7 +227,7 @@ struct Track {
}
}
if (!live && type == AVMEDIA_TYPE_VIDEO && pending && (pending->flags & AV_PKT_FLAG_KEY) &&
!hasIDR(*pending, *stream->codecpar) && stream->codecpar->codec_id == AV_CODEC_ID_H264) {
!playback::hasIDR(*pending, *stream->codecpar) && stream->codecpar->codec_id == AV_CODEC_ID_H264) {
// ponytail: rare intra-refresh seeks decode from the beginning; index real IDRs if this becomes slow.
input->seek(0);
nextPacket();
@@ -258,6 +242,9 @@ struct Track {
decoder->pkt_timebase = stream->time_base;
decoder->thread_count = 2;
decoder->max_pixels = 4096LL * 2160;
if (type == AVMEDIA_TYPE_VIDEO && pending &&
playback::independentVideoPacket(*pending, *stream->codecpar))
playback::preferVideoToolbox(decoder);
check(avcodec_open2(decoder, codec, nullptr));
} catch (...) { avcodec_free_context(&decoder); throw; }
}
@@ -294,6 +281,10 @@ struct Track {
require(!input->interrupted());
int result = avcodec_receive_frame(decoder, f.get());
if (result == 0) {
#ifdef CAST_MEDIA_CHECK
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
(f->format == AV_PIX_FMT_VIDEOTOOLBOX ? hardwareVideoFrames : softwareVideoFrames).fetch_add(1, std::memory_order_relaxed);
#endif
decoded = std::move(f);
const double step = stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ? double(decoded->nb_samples) / decoded->sample_rate : 1.0 / 30;
nextTime = frameTime() + step;
@@ -337,6 +328,7 @@ struct OutputTrack {
AVStream *stream = nullptr;
AVCodecContext *encoder = nullptr;
SwsContext *scale = nullptr;
Frame readable;
SwrContext *resample = nullptr;
AVAudioFifo *fifo = nullptr;
int64_t audioPTS = AV_NOPTS_VALUE;
@@ -463,8 +455,14 @@ struct OutputTrack {
auto out = frame(); out->format = encoder->pix_fmt; out->width = encoder->width; out->height = encoder->height;
out->sample_aspect_ratio = encoder->sample_aspect_ratio;
check(av_frame_get_buffer(out.get(), 32));
if (f->format == AV_PIX_FMT_VIDEOTOOLBOX) {
if (!readable) readable = frame();
check(playback::refVideoFrame(readable.get(), f));
f = readable.get();
}
scale = sws_getCachedContext(scale, f->width, f->height, AVPixelFormat(f->format), out->width, out->height, encoder->pix_fmt, SWS_BILINEAR, nullptr, nullptr, nullptr);
require(scale != nullptr); check(sws_scale(scale, f->data, f->linesize, 0, f->height, out->data, out->linesize));
if (readable) av_frame_unref(readable.get());
out->pts = int64_t(std::llround(std::max(start, time) * 90000));
out->pict_type = firstVideo ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; firstVideo = false; encoded(out.get());
lastVideoTime = time;
@@ -816,7 +814,7 @@ struct CastMedia {
const bool independent = !video || !video->remux ||
(video->pending && std::abs(video->packetTime() - start) < .001 &&
(video->pending->flags & AV_PKT_FLAG_KEY) &&
hasIDR(*video->pending, *video->stream->codecpar));
playback::hasIDR(*video->pending, *video->stream->codecpar));
const bool drained = (vodPipeline->videoOutput && vodPipeline->videoOutput->drained) ||
(vodPipeline->audioOutput && vodPipeline->audioOutput->drained);
if (vodPipeline->nextStart != start || !independent || drained)
@@ -971,6 +969,10 @@ extern "C" bool cast_media_subtitle(CastMedia *media, int id, const void *bytes,
extern "C" void cast_media_cancel(CastMedia *media) { if (media) media->cancel(); }
extern "C" void cast_media_destroy(CastMedia *media) { delete media; }
#ifdef CAST_MEDIA_CHECK
extern "C" void cast_media_check_decode_counts(uint64_t *hardware, uint64_t *software) {
*hardware = hardwareVideoFrames.load(std::memory_order_relaxed);
*software = softwareVideoFrames.load(std::memory_order_relaxed);
}
extern "C" bool cast_media_check_advance(CastMedia *media, double seconds) {
if (!media || !media->source.live || !std::isfinite(seconds) || seconds < 6 || seconds > 3600) return false;
std::unique_lock guard(media->lock);