Я пытаюсь использовать библиотеку libavcodec в FFMpeg для декодирования и перекодирования видео h264.
У меня работает декодирующая часть (нормально обращается к окну SDL), но когда я пытаюсь перекодировать кадры, я получаю неверные данные в сэмплах перекодированного видео.
Вот фрагмент кода моей логики кодирования.
EncodeResponse H264Codec::EncodeFrame(AVFrame* pFrame, StreamCodecContainer* pStreamCodecContainer, AVPacket* pPacket)
{
int result = 0;
result = avcodec_send_frame(pStreamCodecContainer->pEncodingCodecContext, pFrame);
if(result < 0)
{
return EncodeResponse::Fail;
}
while (result >= 0)
{
result = avcodec_receive_packet(pStreamCodecContainer->pEncodingCodecContext, pPacket);
// If the encoder needs more frames to create a packed then return and wait for
// method to be called again upon a new frame been present.
// Else check if we have failed to encode for some reason.
// Else a packet has successfully been returned, then write it to the file.
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
{
// Higher level logic, dedcodes next frame from source
// video then calls this method again.
return EncodeResponse::SendNextFrame;
}
else if (result < 0)
{
return EncodeResponse::Fail;
}
else
{
// Prepare packet for muxing.
if (pStreamCodecContainer->codecType == AVMEDIA_TYPE_VIDEO)
{
av_packet_rescale_ts(m_pPacket, pStreamCodecContainer->pEncodingCodecContext->time_base,
m_pDecodingFormatContext->streams[pStreamCodecContainer->streamIndex]->time_base);
}
m_pPacket->stream_index = pStreamCodecContainer->streamIndex;
int result = av_interleaved_write_frame(m_pEncodingFormatContext, m_pPacket);
av_packet_unref(m_pPacket);
}
}
return EncodeResponse::EncoderEndOfFile;
}
Странное поведение: я замечаю, что прежде чем я получу первый пакет из avcodec_receive_packet, мне нужно отправить более 50 кадров в avcodec_send_frame.
Я построил отладочную сборку FFMpeg и вошел в код. Я заметил, что AVERROR (EAGAIN) возвращается avcodec_receive_packet из-за следующего в x264encoder :: encode в encoder.c
if( h->frames.i_input <= h->frames.i_delay + 1 - h->i_thread_frames )
{
/* Nothing yet to encode, waiting for filling of buffers */
pic_out->i_type = X264_TYPE_AUTO;
return 0;
}
По какой-то причине в моем кодовом контексте (h) никогда не было фреймов. Я потратил много времени, пытаясь отладить ffmpeg и определить, что я делаю неправильно. Но достигли предела моих знаний видео кодеков (что мало).
Я тестирую это с видео, в котором нет звука, чтобы уменьшить сложность.
Я создал урезанную версию своего приложения и предоставил самостоятельный проект (со встроенными зависимостями ffmpeg и SDL). Надеюсь, это может помочь любому, кто хочет помочь мне:).
Ссылка на проект
https://github.com/maxhap/video-codec
Изучив инициализацию кодера, я обнаружил, что должен установить кодек AV_CODEC_FLAG_GLOBAL_HEADER перед вызовом avcodec_open2
pStreamCodecContainer->pEncodingCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
Это изменение привело к тому, что перекодированный блок moov выглядел гораздо более опасным (для его анализа использовался MP4Box.js). Тем не менее, видео по-прежнему не воспроизводится правильно, выходное видео имеет серые рамки при запуске при воспроизведении в VLC и не воспроизводится на других проигрывателях.
С тех пор я пытался создать контекст кодирования с помощью примера кода, а не использовать параметры декодирования моего кодека. Это привело к исправлению ошибки / данных или проблемы с кодировкой. Тем не менее, мои времена DTS масштабируются до огромных цифр
Вот мой новый кодек init
if (pStreamCodecContainer->codecType == AVMEDIA_TYPE_VIDEO)
{
pStreamCodecContainer->pEncodingCodecContext->height = pStreamCodecContainer->pDecodingCodecContext->height;
pStreamCodecContainer->pEncodingCodecContext->width = pStreamCodecContainer->pDecodingCodecContext->width;
pStreamCodecContainer->pEncodingCodecContext->sample_aspect_ratio = pStreamCodecContainer->pDecodingCodecContext->sample_aspect_ratio;
/* take first format from list of supported formats */
if (pStreamCodecContainer->pEncodingCodec->pix_fmts)
{
pStreamCodecContainer->pEncodingCodecContext->pix_fmt = pStreamCodecContainer->pEncodingCodec->pix_fmts[0];
}
else
{
pStreamCodecContainer->pEncodingCodecContext->pix_fmt = pStreamCodecContainer->pDecodingCodecContext->pix_fmt;
}
/* video time_base can be set to whatever is handy and supported by encoder */
pStreamCodecContainer->pEncodingCodecContext->time_base = av_inv_q(pStreamCodecContainer->pDecodingCodecContext->framerate);
pStreamCodecContainer->pEncodingCodecContext->sample_aspect_ratio = pStreamCodecContainer->pDecodingCodecContext->sample_aspect_ratio;
}
else
{
pStreamCodecContainer->pEncodingCodecContext->channel_layout = pStreamCodecContainer->pDecodingCodecContext->channel_layout;
pStreamCodecContainer->pEncodingCodecContext->channels =
av_get_channel_layout_nb_channels(pStreamCodecContainer->pEncodingCodecContext->channel_layout);
/* take first format from list of supported formats */
pStreamCodecContainer->pEncodingCodecContext->sample_fmt = pStreamCodecContainer->pEncodingCodec->sample_fmts[0];
pStreamCodecContainer->pEncodingCodecContext->time_base = AVRational{ 1, pStreamCodecContainer->pEncodingCodecContext->sample_rate };
}
Есть идеи, почему мое время DTS неправильно масштабируется?
Мне удалось исправить смещение DTS, используя значение time_base непосредственно из потоков декодирования.
So
pStreamCodecContainer->pEncodingCodecContext->time_base = m_pDecodingFormatContext->streams[pStreamCodecContainer->streamIndex]->time_base
вместо
pStreamCodecContainer->pEncodingCodecContext->time_base = av_inv_q(pStreamCodecContainer->pDecodingCodecContext->framerate);
Я создам ответ на основе всех моих находок.