ZaStoGram_desktop/Telegram/SourceFiles/tests/test_ffmpeg_decoder_policy.py
loop-uh d439cc4cd2
All checks were successful
Desktop source guards / guards (push) Successful in 5s
Не оставлять видеоклип без декодера при занятом потоке
Предпросмотр видеостикера оставался размытой миниатюрой и не проигрывался.
Рабочий поток клипов не открывал новый декодер, пока на нём числилось
четыре открытых, а освобождался декодер только при удалении клипа: пауза
его не снимала. Новый клип попадает на поток с наименьшей площадью кадров,
то есть как раз на тот, где четыре мелких клипа, и ждал там бесконечно.
Предпросмотр ставит остальные клипы на паузу, но слоты они не отдавали.

Теперь в нагрузку идут только клипы, которые действительно декодируются,
а очередь задерживает запуск, но не отказывает в нём: следующий декодер
на занятом потоке открывается не раньше чем через 50 мс после предыдущего.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:05:56 +03:00

87 lines
3.7 KiB
Python

from pathlib import Path
SOURCE_DIR = Path(__file__).resolve().parents[1]
FFMPEG_HEADER = SOURCE_DIR / "ffmpeg" / "ffmpeg_utility.h"
FFMPEG_SOURCE = SOURCE_DIR / "ffmpeg" / "ffmpeg_utility.cpp"
CLIP_SOURCE = SOURCE_DIR / "media" / "clip" / "media_clip_ffmpeg.cpp"
CLIP_READER_SOURCE = SOURCE_DIR / "media" / "clip" / "media_clip_reader.cpp"
def body_after(source: Path, signature: str) -> str:
text = source.read_text(encoding="utf-8")
start = text.index(signature)
brace = text.index(" {\n", start) + 1
depth = 0
for index in range(brace, len(text)):
char = text[index]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return text[brace + 1:index]
raise AssertionError(f"body not found for {signature}")
def test_ffmpeg_decoder_threads_have_shared_policy():
header = FFMPEG_HEADER.read_text(encoding="utf-8")
ffmpeg = FFMPEG_SOURCE.read_text(encoding="utf-8")
make_codec = body_after(FFMPEG_SOURCE, "CodecPointer MakeCodecPointer")
clip = CLIP_SOURCE.read_text(encoding="utf-8")
assert "ConfigureDecoderThreads(not_null<AVCodecContext*> context);" in header
assert "kMaxSoftwareVideoDecoderThreads = 2" in ffmpeg
assert "\"threads\", \"auto\"" not in ffmpeg
# The capped default applies unless the experimental options are tuned.
configure = body_after(FFMPEG_SOURCE, "void ConfigureDecoderThreads")
assert "OptionFFmpegThreadCount.value()" in configure
assert "OptionFFmpegMultiThread.value()" in configure
assert "kMaxSoftwareVideoDecoderThreads" in configure
assert "FF_THREAD_FRAME" in configure
assert "ConfigureDecoderThreads(context);" in make_codec
assert (
make_codec.index("ConfigureDecoderThreads(context);")
< make_codec.index("const auto codec = FindDecoder(context);")
)
assert "FFmpeg::ConfigureDecoderThreads(_codecContext);" in clip
assert (
clip.index("FFmpeg::ConfigureDecoderThreads(_codecContext);")
< clip.index("const auto codec = FFmpeg::FindDecoder(_codecContext);")
)
def test_clip_reader_queues_decoder_starts_under_load():
reader = CLIP_READER_SOURCE.read_text(encoding="utf-8")
process = body_after(CLIP_READER_SOURCE, "void Manager::process")
assert "kBusyClipReadersForStartThrottle" in reader
assert "kQueuedClipStartDelay" in reader
assert "bool hasDecoder() const" in reader
assert "bool needsDecoderStart() const" in reader
assert "needsDecoderStart()" in process
assert "busyReaders >= kBusyClipReadersForStartThrottle" in process
def test_clip_reader_start_queue_never_starves_a_reader():
reader = CLIP_READER_SOURCE.read_text(encoding="utf-8")
process = body_after(CLIP_READER_SOURCE, "void Manager::process")
decoding = body_after(CLIP_READER_SOURCE, "bool decoding() const")
# Paused readers keep their decoder but do no work, so they must not
# count as load: the media preview pauses every other clip and still
# has to start its own one.
assert "_implementation" in decoding
assert "!_autoPausedGif" in decoding
assert "!_videoPausedAtMs" in decoding
assert "i.key()->decoding()" in process
assert "i.key()->hasDecoder()" not in process
# The queue is a delay with a deadline, not a cap: a waiting reader is
# rescheduled to the moment the next start is allowed and starts then.
assert "crl::time _nextQueuedStartAt = 0;" in reader
assert "&& ms < _nextQueuedStartAt" in process
assert "i.value() = _nextQueuedStartAt;" in process
assert "_nextQueuedStartAt = ms + kQueuedClipStartDelay;" in process
assert "kMaxStartedClipReadersPerWorker" not in reader