利用FFmpeg转码生成MP4文件

利用FFmpeg转码生成MP4文件

项目中,需要把一路音频流及一路视频流分别转码,生成指定格式(MP4)文件。在使用ffmpeg转码生成mp4文件的过程中,碰到了不少的问题,主要如下:
1. 生成的mp4文件无法正常播放
2. 生成的mp4,用ffmpeg分析,发现码率、帧率等参数不对(编码后的pkt结构体无pts,手动赋值错误,如果是mp4文件,不考虑B帧的情况下,pts是按512往上累加,如果是ts文件,则是按3600累加)
3. 生成的mp4文件,没有声音(编码后的pkt结构体无pts,手动赋值错误,如果是AAC文件,pts按1024累加)

在利用ffmpeg转码的时候,首先要初始化一些结构体,在生成一个MP4文件的时候,也需要创建并初始化一些结构体,具体为:

int open_out_put(const char* filename)
{
    int ret, i;
    int video_index = 0;
    int audio_index = 1;
    AVStream *out_stream;
    //编码参数的上下文
    AVCodecContext *enc_ctx;
    AVCodec *encoder;
    //这个参数最为重要,AVFormatContxt*,它为全局变量,该函数主要目的就是初始化它
    ofmt_ctx = NULL;
    //为ofmt_ctx申请空间
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
    if(ofmt_ctx == NULL)
    {
        av_log(NULL,AV_LOG_ERROR,"Could not create output context\n");
        return AVERROR_UNKNOWN;
    }
    //申请好空间之后,现在开始需要为它添加音频流及视频流了
    int nb_streams = 2;
    for(i = 0; i < nb_streams; i++)
    {
        //在ofmt_ctx结构体中添加一路媒体流,这路流的实际参数通过out_stream来设置
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if(!out_stream)//添加流失败了
        {
            av_log(NULL,AV_LOG_ERROR,"Failed allocating output stream\n");
            return AVERROR_UNKNOWN;
        }
        //获取这路流的编码参数结构体的指针,然后初始化它
        enc_ctx = out_stream->codec;
        if(i == video_index)
        {
            //添加了第一路流,认为它是视频流
            encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
            if(!encoder)
            {
                av_log(NULL,AV_LOG_ERROR,"Video encoder not found\n");
                return AVERROR_UNKNOWN;
            }
            enc_ctx->bit_rate = 2000000;//2M码率
            enc_ctx->width = 1920;
            enc_tx->height = 1080;
            enc_ctx->time_base = (AVRational)(1,25);//说明帧率为25fps
            enc_ctx->gop_size = 50;
            enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
            enc_ctx->thread_count = 2;//编码器的线程数,正常情况下可以使编码速度变快
            av_opt_set(enc_ctx->priv_data,"preset","untrafast",0);
        }
        else if(i == audio_index)
        {
            //添加了第二路流,认为它是音频流
            encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
            if(!encoder)
            {
                av_log(NULL,AV_LOG_ERROR,"Audio encoder not found\n");
                return AVERROR_UNKNOWN;
            }
            enc_ctx->sample_rate = 44100;
            enc_ctx->channels = 2;
            enc_ctx->channel_layout = av_get_default_channel_layout(enc_ctx->channels);
            enc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;//根据实际的编码格式来设置,AAC为该值
            enc_ctx->time_base = (AVRational){1,44100};
            enc_ctx->bit_rate = 128000;
        }

        //初始完各个流的编码参数之后,该设置其它媒体文件相关的参数了

        //ofmt_ctx->oformat结构体的值,根据filename的后缀名不同而有所不同,记住,这个判断在放在avcodec_open2之前,否则就算判断了,也不会生效,最终生成的mp4文件,无法正常播放
        if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        ret = avcodec_open2(enc_ctx, encoder, NULL);
    }
    av_dump_format(ofmt_ctx, 0, filename, 1);

    if(!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
        if(ret < 0)
        {
            av_log(NULL,AV_LOG_ERROR,"Could not open output file\n");
            return ret;
        }
    }

    AVDictionary *opt = NULL;
    //设置媒体文件的视频帧率信息
    av_dict_set_int(&opt, "video_track_timescale",25, 0);
    ret = avformat_write_header(ofmt_ctx, &opt);
    if(ret < 0)
    {
        av_log(NULL,AV_LOG_ERROR,"Write file header failed\n");
        return ret;
    }

    return 1;
}
  • 14
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

初始化好了之后,就开始编码了,编码成功后,调用函数

av_interleaved_write_frame(ofmt_ctx, &pkt);1

把编码成功后的数据写入媒体文件中。
当编码结束后,再调用函数

av_write_trailer(fmt_ctx);1

如果没调用此函数写入文件尾部,生成的mp4文件无法正常播放。
最后记得释放fmt_ctx结构体的内存空间。