perf: prefer VideoToolbox decoding with software fallback
This commit is contained in:
+25
-23
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user