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

news2024/11/19 20:23:13

本文主要记录实现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 Header
    codec_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.初始化一个视音频编解码器的 AVCodecContext
    int 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()首先判断编码器有没打开、是否为编码器。
	//发送AVFrame
	int ret = avcodec_send_frame(codec_ctx_,frame);
	//av_packet_alloc(),申请的AVPacket*
	AVPacket *packet = av_packet_alloc();
	//接受packet
    ret = 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 Header
    codec_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.初始化一个视音频编解码器的 AVCodecContext
    int 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 buf
    int 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; //叠加pts
            if(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; //叠加pts
            if(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.coloradmin.cn/o/1500665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024最新算法:冠豪猪优化算法(Crested Porcupine Optimizer,CPO)求解23个基准函数(提供MATLAB代码)

一、冠豪猪优化算法 冠豪猪优化算法(Crested Porcupine Optimizer&#xff0c;CPO)由Mohamed Abdel-Basset等人于2024年提出&#xff0c;该算法模拟冠豪猪的四种不同保护机制&#xff1a;视觉、听觉、气味和物理攻击。第一和第二防御技术&#xff08;视觉和听觉&#xff09;反…

安装nexus + 部署私有maven仓库

安装nexus 部署私有maven仓库 文章目录 安装nexus 部署私有maven仓库1.下载2.解压3.修改配置文件4.启动5.访问6.查看默认密码7.创建私库8.修改代码配置文件9.在maven 的setting.xml中配置私库的账号密码10.运行manve 【deploy】命令测试11.maven项目引用私库12. 重新加载mave…

C/C++内存管理【C++】

目录 一、 C/C内存分布1. C内存管理方式(1) new和delete操作内置类型(2) new和delete操作自定义类型 二、 operator new与operator delete函数三、 malloc/free和new/delete的区别四、内存泄漏 一、 C/C内存分布 C/C程序的内存布局会因编译器和操作系统而有所不同&#xff0c;但…

红帆ioffice-udfGetDocStep.asmx存在SQL注入漏洞

产品简介 红帆iOffice.net从最早满足医院行政办公需求&#xff08;传统OA&#xff09;&#xff0c;到目前融合了卫生主管部门的管理规范和众多行业特色应用&#xff0c;是目前唯一定位于解决医院综合业务管理的软件&#xff0c;是最符合医院行业特点的医院综合业务管理平台&am…

上门服务小程序|上门服务系统成品功能包含哪些?

随着移动互联网的快速发展&#xff0c;上门服务小程序成为了一种创新的家政服务模式。它不仅为用户带来了极大的便利&#xff0c;还能在提高服务效率和质量方面发挥作用。通过上门服务小程序&#xff0c;用户可以轻松预约按摩或理疗服务&#xff0c;无需繁琐操作&#xff0c;只…

9、JavaWeb-文件上传-配置文件

P146 案例-文件上传-简介 文件上传&#xff0c;将本地图片、视频等文件上传到服务器&#xff0c;供其他用户浏览下载的过程。 文件上传前端页面三要素&#xff1a; 如果前端表单的编码格式选择的默认编码方式x-www.form-urlencoded&#xff0c;则提交的文件仅仅是文件名&…

【Linux进阶之路】网络 —— “?“ (下)

文章目录 前言一、概念铺垫1.TCP2.全双工 二、网络版本计算器1. 原理简要2. 实现框架&&代码2.1 封装socket2.2 客户端与服务端2.3 封装与解包2.4 请求与响应2.5 对数据进行处理2.6 主程序逻辑 3.Json的简单使用 总结尾序 前言 在上文我们学习使用套接字的相关接口进行了…

Python实现广义泊松模型(GeneralizedPoisson算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 广义泊松模型&#xff08;Generalized Poisson Model&#xff09;是一种统计学中用于处理数据的分析方…

MATLAB环境下滚动轴承复合故障仿真信号及时频谱

滚动轴承是一种广泛应用于各类旋转机械的通用基础部件&#xff0c;其运行状态的正常与否往往会对于整台机器的寿命、可靠性和精度等性能产生直接的影响。据统计&#xff0c;旋转机械中30%的故障和大型异步电机中44%的故障是由故障轴承引起的&#xff0c;而位于轴承内圈和外圈的…

浏览器的缓存导致的问题

同一个网站 频繁回退进入&#xff0c;音频播放器失效等问题问题 1.打开浏览器-更多工具-任务管理器 2.可以看到缓存页面进程 3.频繁回退进入 就会出现问题 解决方法&#xff1a; try {var bfWorker new Worker(window.URL.createObjectURL(new Blob([1])));window.addEvent…

智慧城市如何助力疫情防控:科技赋能城市安全

目录 一、引言 二、智慧城市与疫情防控的紧密结合 三、智慧城市在疫情防控中的具体应用 1、智能监测与预警系统 2、智慧医疗与健康管理 3、智能交通与物流管理 4、智慧社区与基层防控 四、科技赋能城市安全的未来展望 五、结论 一、引言 近年来&#xff0c;全球范围内…

Python 匿名函数lambda()详解

一、匿名函数定义 lambda 函数是一个匿名函数&#xff08;即&#xff0c;没有名称定义&#xff09;&#xff0c;它可以接受任意数量的参数&#xff0c;但与普通函数不同&#xff0c;它只计算并返回一个表达式 二、匿名函数语法 lambda arguments&#xff08;参数&#xff09;: …

Qt初识 - 编写Hello World的两种方式 | 对象树

目录 一、通过图形化方式&#xff0c;在界面上创建出一个控件 二、通过代码方式&#xff0c;创建Hello World 三、Qt 内存泄漏问题 (一) 对象树 一、通过图形化方式&#xff0c;在界面上创建出一个控件 创建项目后&#xff0c;打开双击forms文件夹中的ui文件&#xff0c;可…

RFID-科技的“隐秘耳语者”

RFID-科技的“隐秘耳语者” 想象一下&#xff0c;你身处一个光线昏暗的环境中&#xff0c;周围的一切都被厚厚的阴影笼罩。这时&#xff0c;你需要识别并获取一个物体的信息&#xff0c;你会选择怎么做&#xff1f;是点亮灯光&#xff0c;用肉眼仔细观察&#xff0c;还是打开扫…

JetPack入门

先导入依赖 implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") 1.使用LifeCycle解耦页面与组件 Activity package com.tiger.lifecycle;import android.annotation.SuppressLint; import android.os.Bundle; import android.os.SystemClock; impo…

【粉丝福利】探秘内部审计数字化之道:精准解析转型方法与成功路径

&#x1f33c;前言 内部审计是一种独立的、客观的确认和咨询活动&#xff0c;包括鉴证、识别和分析问题以及提供管理建议和解决方案。狭义的数字化转型是指将企业经营管理和业务操作的各种行为、状态和结果用数字的形式来记录和存储&#xff0c;据此再对数据进行挖掘、分析和应…

力资源视角的数字化应用

人力资源视角的数字化应用 随着科技的不断发展&#xff0c;数字化应用已经渗透到我们生活的方方面面&#xff0c;而人力资源管理也不例外。从招聘到员工培训&#xff0c;从绩效管理到薪酬福利&#xff0c;数字化应用正在彻底改变人力资源的工作方式和流程。本文将从人力资源的…

【探索Linux】—— 强大的命令行工具 P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )

阅读导航 引言一、socket 常见API表二、函数详细介绍01. socket()02. bind()03. listen()04. accept()05. connect()06. send()07. recv()08. close()09. select()10. getaddrinfo()11. sendto()12. recvfrom()13. setsockopt()14. getsockopt()15. shutdown()16. inet_pton()1…

【C语言】InfiniBand驱动mlx4_init和mlx4_cleanup

一、中文注释 Linux内核模块的初始化和清理过程&#xff0c;针对一个称为mlx4的网络设备驱动。以下是代码的逐行中文注释&#xff1a; static int __init mlx4_init(void) {int ret;if (mlx4_verify_params())return -EINVAL; // 检查设备参数是否有效&#xff0c;无效则返回…

管理者和员工之间缺乏有效的沟通,导致双方对绩效目标和期望存在误解,怎么办?

管理者和员工之间缺乏有效的沟通&#xff0c;导致双方对绩效目标和期望存在误解&#xff0c;这是绩效管理中一个常见且棘手的问题。这种沟通障碍可能源于多个方面&#xff0c;例如双方对绩效管理的理解不同、沟通方式不当或缺乏定期的沟通机制等。 当管理者和员工对绩效目标和…