Finish decoder capability probes after first paced output and drain

This commit is contained in:
evgeny committed 2026-10-20 01:07:31 +02:00
1 parent 7ae204ba4f
commit 8ad5c09474
2 files changed
+42 -9

No files matched your search

+19 -8
View File
@@ -232,6 +232,7 @@ int CallVideoDecoder::receive(call_video_frame& f, QImage& image) {
}
call_video_caps CallVideoDecoder::capabilities(const std::atomic<bool>* running) {
const int64_t started = call_video_playout_now_us();
static call_video_caps caps{};
static std::mutex lock;
static bool initialized = false;
@@ -241,7 +242,11 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic<bool>* running)
};
std::unique_lock<std::mutex> guard(lock, std::defer_lock);
while (!guard.try_lock()) { if (cancelled()) return {}; QThread::usleep(1000); }
if (initialized) return caps;
if (initialized) {
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder capabilities cache hit wait_us=%lld",
(long long)(call_video_playout_now_us() - started));
return caps;
}
call_video_caps probed{};
for (unsigned codec = 1; codec <= 2; ++codec) {
call_video_limit limit{};
@@ -269,9 +274,10 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic<bool>* running)
4000000, (uint8_t)profile};
unsigned sent = 0, received = 0; bool valid = true, outputBeforeEos = false;
const int64_t start = call_video_playout_now_us();
while (valid && received < 8 && call_video_playout_now_us() - start < 1500000) {
// Pacing нужен до первого выхода для измерения pipeline, затем проверяем drain принятых входов.
while (valid && call_video_playout_now_us() - start < 1500000) {
if (cancelled()) return {};
if (sent < 8 && call_video_playout_now_us() - start >= sent * 1000000ll / fps) {
if (!outputBeforeEos && sent < 8 && call_video_playout_now_us() - start >= sent * 1000000ll / fps) {
input.frame_id = sent + 1; input.pts_us = 1000000 + sent * 1000000ll / fps;
int result = decoder.submit(input); if (result < 0) valid = false; else if (result) ++sent;
}
@@ -283,12 +289,15 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic<bool>* running)
if (!decoder.m->eos) outputBeforeEos = true;
++received;
}
if (sent == 8 && decoder.finish() < 0) valid = false;
if (valid && received < 8) QThread::usleep(1000);
if ((outputBeforeEos || sent == 8) && decoder.finish() < 0) valid = false;
if (decoder.m->eos && received == sent) break;
if (valid) QThread::usleep(1000);
}
valid = valid && received == 8 && outputBeforeEos;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder probe codec=%u profile=%u size=%ux%u fps=%u backend=%d valid=%d inputs=%u outputs=%u pipeline=%u",
codec, profile, w, h, fps, int(backend), valid, sent, received, decoder.pipelineFrames());
valid = valid && sent && received == sent && outputBeforeEos;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder probe codec=%u profile=%u size=%ux%u fps=%u backend=%d "
"valid=%d inputs=%u outputs=%u pipeline=%u elapsed_us=%lld",
codec, profile, w, h, fps, int(backend), valid, sent, received, decoder.pipelineFrames(),
(long long)(call_video_playout_now_us() - start));
bothOrientations = valid; backendPipeline = std::max(backendPipeline, decoder.pipelineFrames());
}
@@ -314,5 +323,7 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic<bool>* running)
}
}
caps = probed; initialized = true;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder capabilities verified elapsed_us=%lld",
(long long)(call_video_playout_now_us() - started));
return caps;
}
+23 -1
View File
@@ -25,9 +25,10 @@ static QByteArray selected;
static unsigned opened = 0, software = 0;
static int depth = 1;
static bool burst = false, badReplacement = false, badProbe = false;
struct DecodePending { std::deque<AVFrame*> frames; bool eos = false; };
struct DecodePending { std::deque<AVFrame*> frames; bool eos = false; unsigned inputs = 0; };
static std::map<AVCodecContext*, DecodePending> decoderContexts;
static bool delayedDecoder = false;
static unsigned probeInputs = 0, probeOutputs = 0, probeMaxInputs = 0;
extern "C" const AVCodec* __real_avcodec_find_encoder_by_name(const char*);
extern "C" const AVCodec* __wrap_avcodec_find_encoder_by_name(const char* name) {
@@ -116,6 +117,9 @@ extern "C" void __wrap_avcodec_free_context(AVCodecContext** context) {
extern "C" int __real_avcodec_send_packet(AVCodecContext*, const AVPacket*);
extern "C" int __wrap_avcodec_send_packet(AVCodecContext* context, const AVPacket* packet) {
int result = __real_avcodec_send_packet(context, packet);
if (delayedDecoder && packet && result >= 0) {
++probeInputs; probeMaxInputs = std::max(probeMaxInputs, ++decoderContexts[context].inputs);
}
if (delayedDecoder && !packet && result >= 0) decoderContexts[context].eos = true;
return result;
}
@@ -129,6 +133,7 @@ extern "C" int __wrap_avcodec_receive_frame(AVCodecContext* context, AVFrame* ou
if (result < 0 && result != AVERROR(EAGAIN) && result != AVERROR_EOF) return result;
if (state.frames.empty()) return result;
if (!state.eos && state.frames.size() <= 2) return AVERROR(EAGAIN);
++probeOutputs;
av_frame_move_ref(output, state.frames.front()); av_frame_free(&state.frames.front()); state.frames.pop_front(); return 0;
}
@@ -138,9 +143,26 @@ private slots:
void initTestCase() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); av_log_set_level(AV_LOG_ERROR); }
void init() {
selected.clear(); opened = software = 0; depth = 1; burst = badReplacement = badProbe = delayedDecoder = false;
probeInputs = probeOutputs = probeMaxInputs = 0;
QVERIFY(contexts.empty()); QVERIFY(decoderContexts.empty());
}
void cleanup() { QVERIFY(contexts.empty()); QVERIFY(decoderContexts.empty()); }
void capabilityProbeDrainsFirstOutput() {
// Реальный software decoder выдаёт первый кадр только после третьего input, затем хранит хвост до EOS.
delayedDecoder = true;
QElapsedTimer timer; timer.start();
const auto caps = CallVideoDecoder::capabilities();
for (const auto& limit : caps.decode) {
QCOMPARE(limit.profiles, uint8_t(3)); QCOMPARE(limit.fps, uint16_t(60));
QCOMPARE(limit.width, uint16_t(1920)); QCOMPARE(limit.height, uint16_t(1920));
}
QVERIFY(probeInputs > 0); QCOMPARE(probeMaxInputs, 3u); QCOMPARE(probeOutputs, probeInputs);
const unsigned before = probeInputs;
const auto cached = CallVideoDecoder::capabilities();
QCOMPARE(cached.decode[0].profiles, caps.decode[0].profiles); QCOMPARE(probeInputs, before);
qInfo("Decoder capability probe with delayed output: %lldms, inputs=%u outputs=%u max_per_mode=%u",
timer.elapsed(), probeInputs, probeOutputs, probeMaxInputs);
}
void delayedDecodeAndGenerationDrain() {
LiveVideoEncoder encoder; LiveVideoConfig config;
config.width = 640; config.height = 360; config.codec = 1; config.backend = "libx264";