Несоответствие PyAV при анализе пакетов из кадров h264

При создании кадров 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)

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
47
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это ожидаемое поведение 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(), чтобы сбросить последний пакет.

Я предполагаю, что это может быть связано с тем, что кадр может охватывать разные пакеты или из-за поведения исправления ошибок пакета в потоке? в таком случае, как он узнает, что нужно сформировать кадр, если я снова попытаюсь декодировать точно такой же пакет? А также почему это относится и к разбору пакетов? (не только декодирование кадра)

Shlomi Uziel 03.04.2022 16:19

Это бывший. Вам нужно скормить следующий пакет. Если в пакете есть ошибка, он вернет не-EAGAIN ошибку.

kesh 03.04.2022 16:21

Приведенный мной пример работает, когда я пытаюсь снова декодировать тот же пакет, а не следующий пакет (в моем случае кадр находится точно в первом пакете полностью).

Shlomi Uziel 03.04.2022 16:23

Что произойдет, если вы снова вызовете парсер и декодер без входного аргумента (таким образом, сбрасывая демультиплексор и декодер)? Вы получаете тот же результат?

kesh 03.04.2022 16:45

Да вы правы! очистка буфера, похоже, помогает как для анализа пакетов, так и для декодирования кадров: pyav.org/docs/develop/api/… (если вы хотите уточнить и объяснить поведение из исходного кода, я отмечу его как принятый ответ)

Shlomi Uziel 03.04.2022 17:14

Немного покопался (намного больше, чем предполагалось изначально) при разборе.

kesh 03.04.2022 20:06

Другие вопросы по теме