FFMEG libavcodec解码器然后重新编码视频问题

时间:2018-11-03 00:48:24

标签: ffmpeg libavcodec

我正在尝试使用FFMpeg中的libavcodec库进行解码,然后重新编码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获取第一个数据包之前,我必须向avcodec_send_frame发送50多个帧。

我构建了FFMpeg的调试版本,并逐步进入代码,我注意到avcodec_receive_packet返回了AVERROR(EAGAIN),因为x264encoder :: encode编码器.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


研究了编码器初始化后,我发现必须在调用avcodec_open2之前设置编解码器AV_CODEC_FLAG_GLOBAL_HEADER

pStreamCodecContainer->pEncodingCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

此更改导致重新编码的moov框看起来更加清晰(使用MP4Box.js对其进行解析)。但是,视频仍无法正确播放,在VLC中播放时,输出视频在开始时会出现灰色帧,而在其他播放器中将无法播放。

此后,我尝试通过示例代码(而不是使用解码编解码器参数)创建编码上下文。这导致修复错误/数据或编码问题。但是,我的DTS时间越来越多了

这是我的新编解码器初始化

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时间没有正确缩放?


我设法直接从解码流中使用time_base值来修复DTS标度。

所以

pStreamCodecContainer->pEncodingCodecContext->time_base = m_pDecodingFormatContext->streams[pStreamCodecContainer->streamIndex]->time_base

代替

pStreamCodecContainer->pEncodingCodecContext->time_base = av_inv_q(pStreamCodecContainer->pDecodingCodecContext->framerate);

我将根据我的所有发现创建答案。

1 个答案:

答案 0 :(得分:0)

要解决moov框损坏的最初问题,我必须在调用avcodec_open2之前向编码编解码器上下文中添加AV_CODEC_FLAG_GLOBAL_HEADER标志。

encCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

下一个问题是编码包中的DTS值缩放不正确,这导致最终mp4持续时间长达数百小时。为了解决这个问题,我不得不将编码编解码器上下文时基更改为解码上下文流时基。这与avcodec转码示例中建议的使用av_inv_q(framerate)不同。

encCodecContext->time_base = decCodecFormatContext->streams[streamIndex]->time_base;