转: FFMpeg 封装MP4 文件

本文介绍使用FFmpeg从H.264文件重新封装为MP4格式的过程。通过示例代码展示了如何读取源文件、解码视频、编码及写入目标MP4文件,并详细解释了关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FFmpeg 封装MP4文件的一个例子

    项目中用到FFmpeg 将IP摄像头的视频流的保存MP4文件的。之前的大哥将它先存成了H264文件,然后又调用FFMpeg的命令行去实现转码为MP4。感觉比较麻烦和拙劣 。
  网上看了很多,本身自己也是菜鸟。发一篇别人的 原文:最简单的FFmpeg封装MP4文件
            给那个代码添加了一些注释,本人刚接触,不对的地方还请大家纠正。
const char* SRC_FILE = "1.mkv";
const char* OUT_FILE = "outfile.h264";
const char* OUT_FMT_FILE = "outfmtfile.mp4";
int main()
{
    av_register_all();
    


    AVFormatContext* pFormat = NULL; // 输入文件的Format
    if (avformat_open_input(&pFormat, SRC_FILE, NULL, NULL) < 0)
    {
        return 0;
    }
    AVCodecContext* video_dec_ctx = NULL; // 解码器上下文
    AVCodec* video_dec = NULL;			  // 视频解码器
    if (avformat_find_stream_info(pFormat, NULL) < 0)
    {
        return 0;
    }
    av_dump_format(pFormat, 0, SRC_FILE, 0);
    video_dec_ctx = pFormat->streams[0]->codec;
    video_dec = avcodec_find_decoder(video_dec_ctx->codec_id);
    if (avcodec_open2(video_dec_ctx, video_dec, NULL) < 0)
    {
        return 0;
    }

    AVFormatContext* pOFormat = NULL;
    AVOutputFormat* ofmt = NULL;
    if (avformat_alloc_output_context2(&pOFormat, NULL, NULL, OUT_FILE) < 0) // 下面有解析
    {
        return 0;
    }
    ofmt = pOFormat->oformat;
    if (avio_open(&(pOFormat->pb), OUT_FILE, AVIO_FLAG_READ_WRITE) < 0)
    {
        return 0;
    }
    AVCodecContext *video_enc_ctx = NULL; // 视频编码器上下文
    AVCodec *video_enc = NULL;            // 视频编码器
    video_enc = avcodec_find_encoder(AV_CODEC_ID_H264); // 指定编码器
    AVStream *video_st = avformat_new_stream(pOFormat, video_enc); // 创建流
    if (!video_st)
        return 0;
	// 设置了编码器的相关信息
    video_enc_ctx = video_st->codec;
    video_enc_ctx->width = video_dec_ctx->width;
    video_enc_ctx->height = video_dec_ctx->height;
    video_enc_ctx->pix_fmt = PIX_FMT_YUV420P;
    video_enc_ctx->time_base.num = 1;
    video_enc_ctx->time_base.den = 25;
    video_enc_ctx->bit_rate = video_dec_ctx->bit_rate;
    video_enc_ctx->gop_size = 250;
    video_enc_ctx->max_b_frames = 10;
    //H264 
    //pCodecCtx->me_range = 16; 
    //pCodecCtx->max_qdiff = 4; 
    video_enc_ctx->qmin = 10;
    video_enc_ctx->qmax = 51;
	// 打开编码器
    if (avcodec_open2(video_enc_ctx, video_enc, NULL) < 0)
    {
        printf("编码器打开失败!\n");
        return 0;
    }
    printf("Output264video Information====================\n");
    av_dump_format(pOFormat, 0, OUT_FILE, 1);
    printf("Output264video Information====================\n");

    //mp4 file
    AVFormatContext* pMp4Format = NULL;   // FormatContext - 格式上下文
    AVOutputFormat* pMp4OFormat = NULL;   // 输出格式
	//可以初始化一个用于输出的AVFormatContext结构体,第二个参数如设成NULL格式有后面两个猜出
	// 第三个参数为format_name  第四个参数为fileName 
    if (avformat_alloc_output_context2(&pMp4Format, NULL, NULL, OUT_FMT_FILE) < 0)
    {
        return 0;
    }
	// 从输出格式上下文中知道确切的输出格式
    pMp4OFormat = pMp4Format->oformat;
	// 创建并初始化一个AVIOConetxt为接收给定路径文件资源的接收
    if (avio_open(&(pMp4Format->pb), OUT_FMT_FILE, AVIO_FLAG_READ_WRITE) < 0)
    {
        return 0;
    }

	// 循环看输入format中的流们,根据这个流创建输出流
    for (int i = 0; i < pFormat->nb_streams; i++) {
		// 根据输入流创建输出流
        AVStream *in_stream = pFormat->streams[i];
        AVStream *out_stream = avformat_new_stream(pMp4Format, in_stream->codec->codec);
        if (!out_stream) {
            return 0;
        }
		// 拷贝输入的avcodecConText 给输出的AVCodecContext
        int ret = 0;
        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
            return 0;
        }
		// 这是干啥?
        out_stream->codec->codec_tag = 0;
        if (pMp4Format->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

	//Print detailed information about the input or output format, such as
	//uration, bitrate, streams, container, programs, metadata, side data,
	//codec and time base.
    av_dump_format(pMp4Format, 0, OUT_FMT_FILE, 1);

	// 写视频文件头
    if (avformat_write_header(pMp4Format, NULL) < 0)
    {
        return 0;
    }


    ////


	// 设置AVOption(选项设置-根据字符串设置结构体的属性值)
    av_opt_set(video_enc_ctx->priv_data, "preset", "superfast", 0);
    av_opt_set(video_enc_ctx->priv_data, "tune", "zerolatency", 0);
    avformat_write_header(pOFormat, NULL); // 再次写头?
	// AVPacket是存储压缩编码数据相关信息的结构体。 保存的是解码前的数据,也就是压缩后的数据。 
	// 该结构体不直接包含数据,其中有一个执行数据域的指针,ffmpeg中的很多数据结构都是用这种方法来管理
	// 它保存了解复用之后,解码之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加信息,如
	// 显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等
    AVPacket *pkt = new AVPacket();
    av_init_packet(pkt);
    AVFrame *pFrame = avcodec_alloc_frame(); //帧
    int ts = 0;
    while (1)
    {
		// 作用是读取码流中的音频若干帧或者视频一帧 - 返回流的下一帧
        if (av_read_frame(pFormat, pkt) < 0)
        {
            avio_close(pOFormat->pb);     // 关闭流  - 输入的流哦
            av_write_trailer(pMp4Format); // 给输出MP4文件 写文件件尾
            avio_close(pMp4Format->pb);   // 关闭流  - 输出的MP4文件的流 
            delete pkt;                   // 销毁pkt - 包
            return 0;
        }
		// 根据pkt包中的流的索引来搞 0-对应的是视频流, 1 - 对应的是音频流
		// stream_index 标识当前AVPacket所从属的码流 
		// 在前面的某个地方搞过 了  AVMEDIA_TYPE_VIDEO = 0,AVMEDIA_TYPE_AUDIO = 1;
        if (pkt->stream_index == 0)
        {
            
            int got_picture = 0, ret = 0;
			// 作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
			// video_dec_ctx:编解码上下文环境,定义了编解码操作的一些细节;
			// got_picture_ptr:该值为0表明没有图像可以解码,否则表明有图像可以解码;
            ret = avcodec_decode_video2(video_dec_ctx, pFrame, &got_picture, pkt); // - 解码,将那个H264的文件解码了
            if (ret < 0)
            {
                delete pkt;
                return 0;
            }
			//DTS(decoding time stamp)  和PTS(Presentation Time stamp) 前者是解码的时间,后者是显示的时间。 两种时间戳
            pFrame->pts = pFrame->pkt_pts;//ts++;
			// 若为真,则证明有图像可以解码。
            if (got_picture)
            {
                AVPacket *tmppkt = new AVPacket;
                av_init_packet(tmppkt);
                int size = video_enc_ctx->width*video_enc_ctx->height * 3 / 2;
                char* buf = new char[size];
                memset(buf, 0, size);
                tmppkt->data = (uint8_t*)buf; // 数据域指针-搞它这个存放帧数据的地方的管理方式。
                tmppkt->size = size;
				// 编码一帧视频数据 
				// video_enc_ctx:编码器上下文
				// tmppkt: output AVPacket. (输出pkt)
				// pFrame: AVFrame containing the raw video data to be encoded. 
				// got_picture: 如果输出packt不为空 它被设为1,否则为0 ,若函数返回值为err,这个指针就不要用了。
                ret = avcodec_encode_video2(video_enc_ctx, tmppkt, pFrame, &got_picture);
                if (ret < 0)
                {
                    avio_close(pOFormat->pb); // 关了这个流吧
                    delete buf;
                    return 0;
                }
				// 如果tmppkt不为空
                if (got_picture)
                {
                    //ret = av_interleaved_write_frame(pOFormat, tmppkt);
                    AVStream *in_stream = pFormat->streams[pkt->stream_index]; 			// 输入流
                    AVStream *out_stream = pMp4Format->streams[pkt->stream_index];		// 输出流
					
					// 对新的pkt的时间戳们进行
					// 它的作用是计算 "a * b / c" 的值并分五种方式来取整. -  "时钟基c" 表示的 数值a 转换成以 "时钟基b" 来表示。
                    tmppkt->pts = av_rescale_q_rnd(tmppkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
                    tmppkt->dts = av_rescale_q_rnd(tmppkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
                    tmppkt->duration = av_rescale_q(tmppkt->duration, in_stream->time_base, out_stream->time_base);
                    tmppkt->pos = -1;
					// Write a packet to an output media file ensuring correct interleaving.
                    ret = av_interleaved_write_frame(pMp4Format, tmppkt);
                    if (ret < 0)
                        return 0;
                    delete tmppkt; // 删除临时的pkt
                    delete buf;    // 删除buf
                }
            }
            //avcodec_free_frame(&pFrame);
        }
        else if (pkt->stream_index == 1) // 音频的,我不太理解
        {
            AVStream *in_stream = pFormat->streams[pkt->stream_index];
            AVStream *out_stream = pMp4Format->streams[pkt->stream_index];

            pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
            pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
            pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);
            pkt->pos = -1;
            if (av_interleaved_write_frame(pMp4Format, pkt) < 0)
                return 0;
        }
    }
    avcodec_free_frame(&pFrame);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值