使用ffmpeg libavcodec将视频流编码为H264,为啥持续时间为零

Posted

技术标签:

【中文标题】使用ffmpeg libavcodec将视频流编码为H264,为啥持续时间为零【英文标题】:using ffmpeg libavcodec encode video stream to H264, why duration is zero使用ffmpeg libavcodec将视频流编码为H264,为什么持续时间为零 【发布时间】:2018-02-10 08:14:14 【问题描述】:

需要帮助,最近我正在使用ffmpeg libavcodec解码视频文件然后编码为H264并写入mp4媒体容器,最后媒体文件的持续时间为零,以下是我的代码工作流程:

AVFormatContext*    input_format_context = NULL;
AVFormatContext*    output_format_context = NULL;
AVIOContext*        output_io_context = NULL;
AVCodecContext*     input_codec_context = NULL;
AVCodecContext*     output_codec_context = NULL;
AVCodec*            codec = NULL;
AVStream*           input_stream = NULL;
AVStream*           output_stream = NULL;
AVFrame*            frame = NULL;

int convert_init(const char* input_filename, const char* output_filename)

    /** Allocate a new encode context */
    avformat_open_input(&input_format_context, 
            input_filename, NULL, NULL); 

    /** Get information on the input file (number of streams etc.). */
    avformat_find_stream_info(input_format_context, NULL);  

    /** Open the output file to write to it. */
    avio_open(&output_io_context, output_filename, 
            AVIO_FLAG_WRITE);

    /** Create a new format context for the output container format. */
    output_format_context = avformat_alloc_context();

    /** Associate the output file (pointer) with the container format context. */
    output_format_context->pb = output_io_context;

    /** Guess the desired container format based on the file extension. */
    output_format_context->oformat = av_guess_format(NULL, 
            output_filename, NULL);

    av_strlcpy((output_format_context)->filename, output_filename, 
            sizeof(output_format_context->filename));

    /** stream0 is the video stream */
    AVStream* input_stream = input_format_context->streams[0];


    /** 
     * Init the input_codec_context 
     */

    /** Find a decoder for the audio stream. */
    codec = avcodec_find_decoder(input_stream->codecpar->codec_id);

    /** Allocate a new decode context */
    input_codec_context = avcodec_alloc_context3(codec);

    /** Initialize the stream parameters with demuxer information */
    avcodec_parameters_to_context(input_codec_context, 
            input_stream->codecpar);    

    /** Open the decoder for the stream. */
    avcodec_open2(input_codec_context, codec, NULL);                                

    /** 
     *  Create an output stream for writing encoded data 
     *
     *  AM I MISSING SOMETHING ?
     *
     */
    output_stream = avformat_new_stream(output_format_context, NULL);

    /** 
     * Init the output_codec_context 
     */
    /** Find a encoder for the output video stream, using H264. */
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);     

    /** Allocate an encode context. */
    output_codec_context = avcodec_alloc_context3(codec);  

    /** 
     *  Setup encode context parameters.
     *  
     *  AM I MISSING SOMETHING ?
     *
     * */
    output_codec_context->bit_rate = input_codec_context->bit_rate;
    output_codec_context->width = input_codec_context->width;
    output_codec_context->height = input_codec_context->height;
    output_codec_context->time_base = (AVRational)1, 25;
    output_codec_context->framerate = (AVRational)25, 1;
    output_codec_context->gop_size = 10;
    output_codec_context->max_b_frames = 1;
    output_codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    output_codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    /** Setup output_stream codecpar. */
    avcodec_parameters_from_context(output_stream->codecpar, 
            codec_context);

    /** Alloc an av frame */
    frame = av_frame_alloc();           



void convert_it()

    AVPacket input_packet;
    AVPacket output_packet;

    /** Write the media file container header */
    avformat_write_header(output_format_context, NULL);

    /** 
     * decode frames and encode to H264 
     * */
    while (1)  
        av_init_packet(&input_packet);
        input_packet.data = NULL;
        input_packet.size = 0;

        av_init_packet(&output_packet);
        output_packet.data = NULL;
        output_packet.size = 0;

        /** Read a frame to decode */
        av_read_frame(input_format_context, &input_packet);         
        if (av_read_frame is end of file) 
            break;
        
        ...
        ...

        /** Decoding... */
        avcodec_send_packet(input_codec_context, &input_packet);
        ...
        ...

        /** Get a decoded frame */
        avcodec_receive_frame(input_codec_context, frame);
        ...
        ...

        /** Make the frame writable, is it necessary ?? */
        av_frame_make_writable(frame);

        /** Encode to H264 */
        avcodec_send_frame(output_codec_context, frame);
        ...
        ...

        /** Get a encoded packet */
        avcodec_receive_packet(output_codec_context, &output_packet);

        /** 
         * Write the packet to output.  
         * Here is the point! should I configure the parameters 
         * in packet such as 'pts', 'dts', 'duration', etc, if so, 
         * hwo? or I just directly write the packet into output? 
         */
        av_interleaved_write_frame(output_format_context, &packet);                 
    

    /** Write the media file container trailer */
    av_write_trailer(output_format_context);



int main() 
    convert_init("./sample.avi", "./output.mp4");
    convert_it();


使用VLC或QuickTime播放output.mp4文件失败,导致文件时长为零,拖动时间进度条时,我可以清楚地看到画框,看来编码包缓冲区数据是正确的,但时间戳是错误的,我在配置 output_stream 或数据包时是否遗漏了什么?以下是来自ffprobe的消息。

ffprobe output.mp4
ffprobe version 3.3.3 Copyright (c) 2007-2017 the FFmpeg developers
  built with Apple LLVM version 8.1.0 (clang-802.0.42)
  configuration: --enable-shared --enable-libmp3lame
  libavutil      55. 58.100 / 55. 58.100
  libavcodec     57. 89.100 / 57. 89.100
  libavformat    57. 71.100 / 57. 71.100
  libavdevice    57.  6.100 / 57.  6.100
  libavfilter     6. 82.100 /  6. 82.100
  libswscale      4.  6.100 /  4.  6.100
  libswresample   2.  7.100 /  2.  7.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf57.71.100
  Duration: 00:00:00.06, start: 0.000000, bitrate: 6902181 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 720x408 [SAR 1:1 DAR 30:17], 13929056 kb/s, 90k fps, 90k tbr, 90k tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler

【问题讨论】:

请提供输入输出文件。 【参考方案1】:

您的问题可能是在复用过程中帧的时基错误。 取决于格式,the muxer can change the stream timebase。

流时基应设置为调用者希望用于此流的时基(注意,复用器实际使用的时基可能不同,稍后将介绍)。 [...] 请注意,发送到复用器的数据包的时间信息必须在相应的 AVStream 的时基中。该时基由复用器设置(在 avformat_write_header() 步骤中),并且可能与调用者请求的时基不同。

因此,在编写帧之前,您必须将其时基从创建 FormatContext 时设置的理论时基转换为流实际使用的时基。为此,您可以使用 av_packet_rescale_ts 函数。

例如:av_packet_rescale_ts( packet, codec_contex->time_base, // your theoric timebase format_context->streams[packet->stream_index]->time_base); // the actual timebase

【讨论】:

以上是关于使用ffmpeg libavcodec将视频流编码为H264,为啥持续时间为零的主要内容,如果未能解决你的问题,请参考以下文章

基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

基于 FFMPEG 的视频编码(libavcodec ,致敬雷霄骅)

基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

基于 FFMPEG 的视频编码 源码(libavcodec,C++ Qt)

ffmpeg