Как записать видеопоток, содержащий B-кадр и без DTS, в контейнер MP4? - PullRequest
1 голос
/ 12 февраля 2020

Я хочу сохранить видеопоток h264, полученный от источника RTSP, в контейнер MP4. В отличие от других вопросов, задаваемых по SO, здесь возникают следующие проблемы:

  • Поток содержит B кадров.

  • Поток имеет только PTS, заданный RTP / RTCP.

Вот код, который я сделал

//  ffmpeg
    pkt->data = ..;
    pkt->size = ..;
    pkt->flags = bKeyFrame? AV_PKT_FLAG_KEY : 0;    
    pkt->dts = AV_NOPTS_VALUE;
    pkt->pts = PTS;

    // PTS is based on epoch microseconds so I ignored re-scaling.
    //av_packet_rescale_ts(pkt, { 1, AV_TIME_BASE }, muxTimebase);

    auto ret = av_interleaved_write_frame(m_pAVFormatCtx, pkt);

Я получил много сообщений об ошибках, таких как: «Приложение предоставило недопустимые, немонотонно увеличивающие dts для muxer ...».

Результат: файл mp4 воспроизводится через VL C, но FPS просто половина исходного FPS и продолжительность видео неверна (VL C показывает странное число).

Так как мне установить правильные DTS и PTS перед отправкой в ​​контейнер?

Обновление: Я попробовал некоторые изменения, но пока не удалось, я обнаружил, что причина падения частоты кадров заключается в том, что мультиплексор сбрасывает кадры с неправильным DTS. Кроме того, если я установил слишком большое значение начала PTS и DTS, некоторым игрокам, таким как VL C, придется подождать некоторое время, прежде чем показывать видео.

Ответы [ 2 ]

1 голос
/ 14 февраля 2020

Я провел несколько экспериментов и хочу поделиться с вами некоторыми вещами.

  1. Независимо от наличия B-кадров или нет, для MP4 muxer требуется DTS (не менее):

    • Монотонно увеличивается.
    • DTS <= PTS за каждый кадр. </li>
    • PTS и DTS должны начинаться с значений, близких к нулю (в противном случае такие игроки, как VL C, должны задержка на некоторое время перед отображением видео).
  2. Если в потоке нет B-кадров, DTS можно скопировать из PTS и сохранить кадры в файл mp4 без каких-либо проблем. .

  3. Если в потоке присутствуют B-кадры, история будет совершенно другой. В этом случае PTS кадров не монотонно увеличивается из-за B-кадров. Следовательно, простое копирование DTS = PTS определенно не сработает. Мы должны найти способ получить DTS, отправляя DTS через внеполосный канал или вычисляя из FPS и PTS.

Для отправки внеполосных сигналов это довольно сложно. потому что это требует обработки и сервера RTSP и клиента RTSP. Здесь я просто хочу показать простой способ определения DTS из FPS и PTS.

Грубые шаги таковы:

Определяет среднюю продолжительность (или FPS) между кадрами

  • Анализирует FPS из SDP принимающего сеанса RTSP. Этот способ зависит от поддержки RTSP-сервера. Одни поддерживают, другие нет.

  • Другим способом является вычисление средней длительности между кадрами из последовательности кадров. Вы можете буферизовать количество кадров, равное размеру одной GOP, получая разность PTS первого и последнего кадра GOP, деленную на количество кадров, у которых будет средняя продолжительность. Например, если FPS предполагается равным 30, то расчетная средняя продолжительность должна составлять приблизительно 33333 долл. США

Сохранение в контейнер

// Initialize container

    pAVStream->time_base = { 1, AV_TIME_BASE }; // PTS/DTS in microseconds.
    pAVFormatCtx->oformat->flags |= AVFMT_SEEK_TO_PTS;
    ret = avformat_write_header(m_pAVFormatCtx, &priv_opts);

    Assume that you have pre-calculated average duration: 
    nAvgDuration = 33'333LL;

    //  Per each frame

    if (waitingForTheFirstKeyFrame) {
        if (!bsKeyFrame) {
            return false;
        }

        waitingForTheFirstKeyFrame = false;
        nPTSOffset = nPTS; // pts will start from 0
        nStartDTS = nPTS - nAvgDuration; // dts will start from -nAvgDuration
    }

    nDTS = nStartDTS;
    nStartDTS += nAvgDuration; // dts is monotonically increasing

    pkt->pts = nPTS - nPTSOffset;
    pkt->dts = nDTS - nPTSOffset;

    //  Since PTS/DTS are in microseconds, no need to rescalling more.
    //  Of course, you can use a different time_base.

    auto ret = av_interleaved_write_frame(m_pAVFormatCtx, pkt);

Предостережение:

Это решение хорошо работает с допущением, что исходная PTS потока (на стороне сервера) монотонно увеличивается, между кадрами нет промежутков и нет потери кадров. В противном случае точность DTS может быть снижена или даже файл mp4 не сможет быть воспроизведен.

0 голосов
/ 13 февраля 2020

Нормально, что « Поток имеет только PTS, заданный RTP / RTCP. ». Что-то здесь не так.
Если нет dts, это означает, что вы должны использовать только pts. Если в действительности есть B-кадры, то у вас будут значения dts, отличные от pts.

Попробуйте свой код dts = pts и посмотрите, что произойдет.

...