При создании кадров H.264 и их декодировании с помощью pyAV пакеты анализируются из кадров только при двойном вызове методов parse
.
Рассмотрим следующий тестовый ввод H.264, созданный с использованием:
ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 -f image2 -vcodec libx264 -bsf h264_mp4toannexb -force_key_frames source -x264-params keyint=1:scenecut=0 "frame-%4d.h264"
Теперь, используя pyAV для разбора первого кадра:
import av
codec = av.CodecContext.create('h264', 'r')
with open('/path/to/frame-0001.h264', 'rb') as file_handler:
chunk = file_handler.read()
packets = codec.parse(chunk) # This line needs to be invoked twice to parse packets
пакеты остаются пустыми, если последняя строка не вызывается снова (packets = codec.parse(chunk)
)
Кроме того, для различных примеров из реальной жизни, которые я не могу охарактеризовать, кажется, что для декодирования кадров из пакетов также требуется несколько вызовов декодирования:
packet = packets[0]
frames = codec.decode(packet) # This line needs to be invoked 2-3 times to actually receive frames.
Кто-нибудь знает что-нибудь об этом непоследовательном поведении pyAV?
(Использование Python 3.8.12 на macOS Monterey 12.3.1, ffmpeg 4.4.1, pyAV 9.0.2)
Это ожидаемое поведение PyAV. Мало того, это ожидаемое поведение базового libav
. Один пакет не гарантирует кадр, и для создания кадра может потребоваться несколько пакетов. Это видно в Пример декодера видео FFmpeg:
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
Если для формирования кадра требуется больше пакетов, выдается ошибка EAGAIN
.
[редактировать]
На самом деле, приведенный выше пример не является хорошим примером, поскольку он просто завершается на EAGAIN
. Чтобы получить кадр, он должен скорее continue
на EAGAIN
:
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (AVERROR(EAGAIN))
continue;
if (ret == AVERROR_EOF)
return;
[редактировать]
пьяв codec.parse()
Декодирование, иногда требующее дополнительных вызовов, является довольно известным фактом, но синтаксический анализатор, нуждающийся в очистке, встречается реже. Вот разница между PyAV и FFmpeg:
PyAV анализирует входные данные с помощью av_parser_parse2()
следующим образом: [ссылка]:
while True:
with nogil:
consumed = lib.av_parser_parse2(
self.parser,
self.ptr,
&out_data, &out_size,
in_data, in_size,
lib.AV_NOPTS_VALUE, lib.AV_NOPTS_VALUE,
0
)
err_check(consumed)
# ...snip...
if not in_size:
# This was a flush. Only one packet should ever be returned.
break
in_data += consumed
in_size -= consumed
if not in_size:
# Aaaand now we're done.
break
Таким образом, он считывается до тех пор, пока входные данные не будут использованы на 100%, и обратите внимание, что он не вызывает av_parser_parse2
в конце буфера (что имеет смысл, поскольку входные данные могут быть только частью данных потока.
Напротив, FFmpeg не вызывает av_parser_parse2
напрямую, а использует parse_packet
, и вы можете видеть, как он обрабатывает аналогичную ситуацию:
while (size > 0 || (flush && got_output)) {
int64_t next_pts = pkt->pts;
int64_t next_dts = pkt->dts;
int len;
len = av_parser_parse2(sti->parser, sti->avctx,
&out_pkt->data, &out_pkt->size, data, size,
pkt->pts, pkt->dts, pkt->pos);
Он также вызывает av_parser_parse2
для сброса потока после того, как поток входных данных исчерпан. Итак, вам нужно сделать то же самое в PyAV: после того, как все ваши кадры будут прочитаны, в последний раз вызвать codec.parse()
, чтобы сбросить последний пакет.
Это бывший. Вам нужно скормить следующий пакет. Если в пакете есть ошибка, он вернет не-EAGAIN
ошибку.
Приведенный мной пример работает, когда я пытаюсь снова декодировать тот же пакет, а не следующий пакет (в моем случае кадр находится точно в первом пакете полностью).
Что произойдет, если вы снова вызовете парсер и декодер без входного аргумента (таким образом, сбрасывая демультиплексор и декодер)? Вы получаете тот же результат?
Да вы правы! очистка буфера, похоже, помогает как для анализа пакетов, так и для декодирования кадров: pyav.org/docs/develop/api/… (если вы хотите уточнить и объяснить поведение из исходного кода, я отмечу его как принятый ответ)
Немного покопался (намного больше, чем предполагалось изначально) при разборе.
Я предполагаю, что это может быть связано с тем, что кадр может охватывать разные пакеты или из-за поведения исправления ошибок пакета в потоке? в таком случае, как он узнает, что нужно сформировать кадр, если я снова попытаюсь декодировать точно такой же пакет? А также почему это относится и к разбору пакетов? (не только декодирование кадра)