1
votes

Need help, recently, I am using ffmpeg libavcodec to decode a video file then encode to H264 and write to an mp4 media container, finally, the media file's duration is zero, the following is my code's workflow:

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();

}

using VLC or QuickTime to playback the output.mp4 file, it failed, cause the file duration is zero, when dragging the time progress bar, I can see the picture frame clearly, it seems that the encoding packet buffer data is correct, but the timestamp is error, am I missing something when configure the output_stream or packet? The following is message from 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
Please provide input and output files.Markus Schumann

1 Answers

3
votes

Your problem probably comes the timebase of your frames being wrong during muxing. Depending on the format, the muxer can change the stream timebase.

The stream timebase should be set to the timebase that the caller desires to use for this stream (note that the timebase actually used by the muxer can be different, as will be described later).
[...]
Do note that the timing information on the packets sent to the muxer must be in the corresponding AVStream's timebase. That timebase is set by the muxer (in the avformat_write_header() step) and may be different from the timebase requested by the caller.

So, before writing your frame, you must convert its timebase from the theoric one you set when creating the FormatContext to the one actually used by the stream. For that you can use the av_packet_rescale_ts function.

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