音视频学习笔记——实现PCM和H264合成MP4功能

本文主要记录实现PCMH264合成MP4功能的整个框架,各个模块中FFmpegapi使用流程,便于后续学习和复盘。


本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育


🎡导航小助手🎡

    • 1. MP4合成
    • 2. muxer类
    • 3. audioencoder类
    • 4. videoencoder类
    • 5. audioresampler类
    • 6. mian函数:

1. MP4合成

MP4合成包括音频视频以及封装器3部分,框架如下图所示。
在这里插入图片描述

2. muxer类

首先,在h.文件中声明相关函数和参数。
在这里插入图片描述
以下是各个函数中重要的api使用。
Init():初始化

	//初始化一个用于输出的AVFormatContext结构体。其声明位于libavformat\avformat.h,avformat_alloc_output_context2(&fmt_ctx_,NULL,NULL,url)//

DeInit():资源释放

	//关闭打开的流avformat_close_input(&fmt_ctx_); 

*AddStream(AVCodecContext codec_ctx):创建流

	//创建流AVStream *st = avformat_new_stream(fmt_ctx_,NULL);//从编码器上下文复制avcodec_parameters_from_context(st->codecpar, codec_ctx);//打印输入流 av_dump_format(fmt_ctx_, 0, url_.c_str(), 1);

SendHeader():写header文件

	//分配一个 stream 的私有数据而且写 stream 的 header 到一个输出的媒体文件。int ret = avformat_write_header(fmt_ctx_, NULL); 

SendPacket():写packet,与

 	AVRational src_time_base; //编码后的包AVRational dst_time_vase; //mp4输出文件对应流的time_base//时间基转换packet->pts = av_rescale_q(packet->pts,src_time_base,dst_time_vase);packet->dts = av_rescale_q(packet->dts,src_time_base,dst_time_vase);packet->duration = av_rescale_q(packet->duration,src_time_base,dst_time_vase);ret = av_interleaved_write_frame(fmt_ctx_,packet); //不是立即写入文件,内部缓存,主要是对pts进行排序//ret = av_write_frame(fmt_ctx_,packet);

SendTrailer():输出文件尾

	//用于输出文件尾av_write_trailer(fmt_ctx_);

在对音频和视频进行编码得到数据流后,用muxer类实现将音视频流编码成mp4格式。

3. audioencoder类

h.文件中声明相关函数和参数。
在这里插入图片描述
主要函数实现:

	1.初始化AAC:InitAAC(int channels, int sample_rate, int bit_rate);2.编码:*Encode(AVFrame *frame, int stream_index, int64_t pts, int64_t time_base);3.返回一些常用的参数int GetFrameSize(); //获取一帧数据,每个通道需要多少个采样点int GetSampleFormat();  //编码器需要的采样格式int GetChannels(); //获取通道数int GetSampleRate(); //获取采样率AVCodecContext *GetCodecContext();

InitAAC():初始化
参数:

  • pcm_channels:pcm通道数
  • pcm_sample_rate:pcm样本采样率
  • audio_bit_rate:音频比特率
	//1.avcodec_find_encoder() 用于查找 FFmpeg 的编码器,AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);//获取的是默认的AAC。//2.avcodec_alloc_context3()主要是创建了 AVCodecContext ,并给结构体参数赋予初值。//初值设置主要分成两块,1. 所有编码器都相同的部分;2.每个编码器独有的参数设置。codec_ctx_ = avcodec_alloc_context3(codec);//配置参数codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //编码后的aac文件不会带ADTS Headercodec_ctx_->bit_rate = bit_rate_;codec_ctx_->sample_rate = sample_rate_;codec_ctx_->sample_fmt = AV_SAMPLE_FMT_FLTP;codec_ctx_->channels = channels_;codec_ctx_->channel_layout = av_get_default_channel_layout(codec_ctx_->channels);//3.初始化一个视音频编解码器的 AVCodecContextint ret = avcodec_open2(codec_ctx_, NULL, NULL);

Encode():编码
参数:

  • frame:帧
  • stream_index:数据流序号
  • pts:显示时间
  • time_base:时间基
	//时间转换frame->pts = av_rescale_q(pts,AVRational{1, (int)time_base}, codec_ctx_->time_base);//1.avcodec_send_frame()首先判断编码器有没打开、是否为编码器。//发送AVFrameint ret = avcodec_send_frame(codec_ctx_,frame);//av_packet_alloc(),申请的AVPacket*AVPacket *packet = av_packet_alloc();//接受packetret = avcodec_receive_packet(codec_ctx_,packet);

4. videoencoder类

实现与audioencoder类相似,但细节处不同。
在这里插入图片描述

1.初始化H264:int InitH264(int width, int height, int fps, int bit_rate);
2.编码:AVPacket *Encode(uint8_t *yuv_data, int yuv_size,int stream_index, int64_t pts, int64_t time_base);

InitH264():

  • width_ :画面宽度
  • height_ :画面高度
  • fps_ :帧率
  • bit_rate_ :比特率
	//1.avcodec_find_encoder() 用于查找 FFmpeg 的编码器,AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);//2.avcodec_alloc_context3()主要是创建了 AVCodecContext ,并给结构体参数赋予初值。//初值设置主要分成两块,1. 所有编码器都相同的部分;2.每个编码器独有的参数设置。codec_ctx_ = avcodec_alloc_context3(codec);//配置参数codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //编码后的aac文件不会带ADTS Headercodec_ctx_->bit_rate = bit_rate_;codec_ctx_->width = width_;codec_ctx_->height = height_;codec_ctx_->framerate = {fps, 1};codec_ctx_->time_base = {1,1000000}; //单位为微妙codec_ctx_->gop_size =fps_;codec_ctx_->max_b_frames =0; //B帧数量codec_ctx_->pix_fmt = AV_PIX_FMT_YUV420P;//3.初始化一个视音频编解码器的 AVCodecContextint ret = avcodec_open2(codec_ctx_, NULL, NULL); frame_ =av_frame_alloc();//视频与音频实现不同之处,需要声明下frame帧

Encode():
参数:

  • yuv_data:存放yuv帧的buffer
  • yuv_size:yuv帧的大小
  • stream_index:数据流序号
  • pts:显示时间
  • time_base:时间基
	//时间转换frame->pts = av_rescale_q(pts,AVRational{1, (int)time_base}, codec_ctx_->time_base);//不同之处,将yuv填充成需要的格式int ret_size = av_image_fill_arrays(frame_->data, frame_->linesize,yuv_data, (AVPixelFormat)frame_->format,frame_->width,frame_->height,1);int ret = avcodec_send_frame(codec_ctx_,frame);AVPacket *packet = av_packet_alloc();ret = avcodec_receive_packet(codec_ctx_,packet); 

5. audioresampler类

在这里插入图片描述

1.初始化S16转FLTP:int InitFormS16ToFLTP(int in_channles,int in_sample_rate, int out_channles, int out_sample_rate);
2.重采样:int ResampleFormS16ToFLTP(uint8_t *in_data, AVFrame *out_frame);

InitFormS16ToFLTP():
参数:

  • in_channles_:输入通道数
  • in_sample_rate_:输入采样率
  • out_channles_:输出通道数
  • out_sample_rate_:输出采样率
	//重采样参数设置ctx_ = swr_alloc_set_opts(ctx_,av_get_default_channel_layout(out_channles_),AV_SAMPLE_FMT_FLTP,out_sample_rate_,av_get_default_channel_layout(in_channles_),AV_SAMPLE_FMT_S16,in_sample_rate_,0,NULL);//初始化一个重采样                            int ret = swr_init(ctx_);

ResampleFormS16ToFLTP():
参数:

  • in_data: pcm帧buffer
  • out_fream: fktp帧
int AudioResampler::ResampleFormS16ToFLTP(uint8_t *in_data, AVFrame *out_frame)
{const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};indata[0] = in_data;//进行转换int samples = swr_convert(ctx_, out_frame->data, out_frame->nb_samples,indata,out_frame->nb_samples);
}

6. mian函数:

实现流程:
1. 打开yuv、pcm文件
2. 初始化编码器,包括视频、音频编码器,分配 yuv、pcm的帧buffer,初始化重采样
3. mp4初始化,包括新建流,open io, send header
4. 处理时间戳,在while循环读取yuv、pcm进行编码然后发送给mp4 muxer
5. 释放资源

6.1 打开yuv、pcm文件

	// 打开YUV文件n_yuv_fd = fopen(in_yuv_name, "rb");// 打开PCM文件in_pcm_fd = fopen(in_pcm_name, "rb");

6.2 初始化编码器,包括视频、音频编码器,分配yuv、pcm的帧buffer

	//2.1 初始化video//初始化编码器video_encoder.InitH264(yuv_width, yuv_height, yuv_fps, video_bit_rate);//分配 yuv bufint y_frame_size = yuv_width * yuv_height;int u_frame_size = yuv_width * yuv_height / 4;int v_frame_size = yuv_width * yuv_height / 4;int yuv_frame_size = y_frame_size + u_frame_size + v_frame_size;uint8_t *yuv_frame_buf = (uint8_t *)malloc(yuv_frame_size);//2.2 初始化 audio//初始化音频编码器audio_encoder.InitAAC(pcm_channels,pcm_sample_rate, audio_bit_rate);//分配pcm buf// pcm_frame_size = 单个字节点占用的字节 * 通道数量 * 每个通道有多少给采样点int pcm_frame_size = av_get_bytes_per_sample((AVSampleFormat)pcm_sample_format)*pcm_channels * audio_encoder.GetFrameSize();uint8_t *pcm_frame_buf = (uint8_t *)malloc(pcm_frame_size);//2.3 初始化重采样AudioResampler audio_resampler;audio_resampler.InitFormS16ToFLTP(pcm_channels, pcm_sample_rate,audio_encoder.GetChannels(),audio_encoder.GetSampleRate());

6.3 mp4初始化,包括新建流,open io, send header

    Muxer mp4_muxer;mp4_muxer.Init(out_mp4_name);//创建视频流、音频流mp4_muxer.AddStream(video_encoder.GetCodecContext());mp4_muxer.AddStream(audio_encoder.GetCodecContext());mp4_muxer.Open();mp4_muxer.SendHeader();

6.4.在while循环读取yuv、pcm进行编码然后发送给mp4 muxer

	//1. 时间戳相关int64_t audio_time_base = AUDIO_TIME_BASE;int64_t video_time_base = VIDEO_TIME_BASE;double audio_frame_duration = 1.0 * audio_encoder.GetFrameSize()/pcm_sample_rate *audio_time_base;double video_frame_duration = 1.0 / yuv_fps * video_time_base;while(1){if(audio_finish && video_finish){break;}printf("apts:%0.0lf,vpts:%0.0lf\n",audio_pts/1000,video_pts/1000);if(video_finish != 1 && audio_pts > video_pts //audio和video都还有数据,优先audio(audio_pts > video_pts)|| (video_finish != 1 && audio_finish == 1)){read_len = fread(yuv_frame_buf, 1,yuv_frame_size,in_yuv_fd);if(read_len < yuv_frame_size){video_finish =1;printf("fread yuv_frame_buf finish\n");}if(video_finish != 1){ret = video_encoder.Encode(yuv_frame_buf,yuv_frame_size, video_index,video_pts, video_time_base, packets);}else{printf("flush video encoder\n");ret = video_encoder.Encode(NULL, 0, video_index,video_pts, video_time_base, packets);}video_pts += video_frame_duration; //叠加ptsif(ret >= 0){for(int i = 0; i<packets.size(); ++i){ret = mp4_muxer.SendPacket(packets[i]);}}packets.clear();}else if(audio_finish != 1){read_len = fread(pcm_frame_buf, 1, pcm_frame_size, in_pcm_fd);if(read_len < pcm_frame_size){audio_finish = 1;printf("fread pcm_frame_buf finish\n");}if(audio_finish != 1){AVFrame *fltp_frame = AllocFltpPcmFrame(pcm_channels, audio_encoder.GetFrameSize());ret = audio_resampler.ResampleFormS16ToFLTP(pcm_frame_buf, fltp_frame);if(ret < 0){printf("ResampleFormS16ToFLTP failed\n");}ret = audio_encoder.Encode(fltp_frame, audio_index,audio_pts, audio_time_base, packets);FreePcmFrame(fltp_frame);}else{printf("flush audio encoder\n");ret = audio_encoder.Encode(NULL, audio_index,audio_pts, audio_time_base, packets);}audio_pts += audio_frame_duration; //叠加ptsif(ret >= 0){for(int i = 0; i<packets.size(); ++i){ret = mp4_muxer.SendPacket(packets[i]);}}packets.clear();}}

注意点
一、时间基问题
音频编码中,时间基在avcodec_open2(codec_ctx_, NULL, NULL)执行后,会自动根据所打开的编码器设置改变。
视频编码,需要自己手动设置。
如果不主动设置报错: The encoder timebase is not set
codec_ctx_->time_base = {1,1000000}; //单位为微妙
2.有很大延迟,需要设置0延迟,进行如下修改。

	h.定义AVDictionary *dict_ =NULL;cpp修改av_dict_set(&dict_,"tune","zerolatency", 0);int ret = avcodec_open2(codec_ctx_, NULL, dict_);//释放内存if(dict_){av_dict_free(&dict_);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/522053.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】

目录 一&#xff0c;为什么需要智能指针 二&#xff0c;内存泄露的基本认识 1. 内存泄露分类 2. 常见的内存检测工具 3&#xff0c;如何避免内存泄露 三&#xff0c;智能指针的使用与原理 1. RAII思想 2. 智能指针 &#xff08;1. unique_ptr &#xff08;2. shared_…

[MYSQL]当数据库被攻破如何重新恢复

前情提要&#xff1a;mysql数据库默认密码、默认端口没有改&#xff0c;也没做安全防护&#xff0c;导致被攻破被索要比特币。 那我们自然是不能给他们的&#xff0c;下面罗列我的补救方法。 密码修改相关 第一步大家自然都会想到先去修改密码&#xff1a; mysqladmin -u roo…

二维码门楼牌管理系统应用场景:商业与零售业发展的助推器

文章目录 前言一、二维码门楼牌管理系统的基本功能二、商业和零售业中的应用场景三、二维码门楼牌管理系统的优势分析四、结论 前言 在数字化时代的浪潮中&#xff0c;二维码门楼牌管理系统凭借其独特的优势&#xff0c;正在逐步成为商业和零售业发展的新宠。它不仅能够为商家…

华为od机试C卷-开源项目热度榜单

1、题目描述 某个开源社区希望将最近热度比较高的开源项目出一个榜单&#xff0c;推荐给社区里面的开发者。 对于每个开源项目&#xff0c;开发者可以进行关注(watch)、收藏(star)、fork、提issue、提交合并请求(MR)等。 数据库里面统计了每个开源项目关注、收藏、fork、issue…

鸿蒙开发-UI-动画-页面内动画

鸿蒙开发-UI-组件2 鸿蒙开发-UI-组件3 鸿蒙开发-UI-气泡/菜单 鸿蒙开发-UI-页面路由 鸿蒙开发-UI-组件导航-Navigation 鸿蒙开发-UI-组件导航-Tabs 鸿蒙开发-UI-图形-图片 鸿蒙开发-UI-图形-绘制几何图形 鸿蒙开发-UI-图形-绘制自定义图形 文章目录 前言 一、概述 二、页面内…

基于springboot的某大学外卖系统的实现(源码+论文)

文章目录 目录 文章目录 前言 一、功能设计 二、功能实现 1 后台登录 2管理员界面 3员工信息管理 4客户信息管理 三、库表设计 四、论文 前言 如今&#xff0c;信息化不断的高速发展&#xff0c;社会也跟着不断进步&#xff0c;现今的社会&#xff0c;各种工作都离不开信息化技…

跨网络传输的大致过程+图解(软件虚拟层),ip地址介绍,ip地址和mac地址对比

目录 跨网络传输 引入​​​​​​​ 举例 -- 唐僧西天取经 结论 介绍 ip地址 引入 介绍 类型 公有ip 私有ip 版本 ipv4 ipv6 ip地址和mac地址的唯一性问题 数据包转发的过程 引入 思考 -- 如何跨子网 过程 图解 封装和解包 去掉差异 ip地址/协议的重要…

[每周一更]-第90期:认识Intel的CPU

市面上的CPU分类主要分有两大阵营&#xff0c;一个是Intel、AMD为首的复杂指令集CPU&#xff0c;另一个是以IBM、ARM为首的精简指令集CPU。 两个不同品牌的CPU&#xff0c;其产品的架构也不相同&#xff0c;例如&#xff0c;Intel、AMD的CPU是X86架构的&#xff0c;而IBM公司的…

mybatis plus 查询数据库 字段名 自动添加下划线

问题 mybatis plus 查询数据库 字段名 自动添加下划线 详细问题 笔者使用mybatis plus 查询数据库&#xff0c;执行查询语句报错。详细报错信息 2024-03-08 11:08:33.156 ERROR 4816 --- [nio-9090-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() …

Sora的双重边缘:视频生成的革新与就业的再思考

随着科技的日新月异&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;技术如潮水般涌入我们的日常生活&#xff0c;为各个领域带来了翻天覆地的变化。在这一浪潮中&#xff0c;Sora作为一款前沿的AI视频生成工具&#xff0c;凭借其高度逼真…

妇女节:打开AI视界,成就“她力量”

根据国内招聘平台猎聘发布的《2024女性人才数据洞察报告》&#xff0c;从2023年3月到2024年2月&#xff0c;女性在AIGC领域的求职人次同比增长了190.49%。随着人工智能时代的降临&#xff0c;女性正以前所未有的姿态&#xff0c;在技术的助力下&#xff0c;蜕变成为新生的力量。…

C++:Stack和Queue的模拟实现

创作不易&#xff0c;感谢三连&#xff01; 一、容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转换成客户希望的另外一个接口。 就如同是电源适配器将不适用的交流电…