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

news/2024/4/15 5:14:03/文章来源:https://blog.csdn.net/qq_45087381/article/details/136506088

本文主要记录实现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.luyixian.cn/news_show_999153.aspx

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

相关文章

linux——线程(1)

我们在使用各种面向对象语言的时候或多或少会使用到语言的线程库&#xff0c; 它可以让我们的一个程序分成多个执行流进行并发执行&#xff0c;这样的模式会 大大提高我们代码的运行效率&#xff0c;而Linux操作系统中也有着属于自己的线 程库。从今天开始就由我来开始介绍Linu…

UE4 Niagara 关卡4.1官方案例解析

we now directly supporting playing audio from arbitrary locations in particle systems.users have control over volume and pitch,and the system can directly play sound waves,or sound cues which have multiple waves in them.(我们现在直接支持在粒子系统中从任意…

小白跟做江科大51单片机之AD/DA

1.看原理图找接口 2.看时序图编写读取数据代码 XPT2046.c代码 #include <REGX52.H> //引脚定义 sbit XPY2046_DINP3^4; sbit XPY2046_CSP3^5; sbit XPY2046_DCLKP3^6; sbit XPY2046_DOUTP3^7; unsigned int XPT2046_ReadAD(unsigned char Command) { unsigned char …

【重温设计模式】中介者模式及其Java示例

中介者模式的基本概念 在我们的日常生活中&#xff0c;有许多事情是需要通过一个“中介”来完成的&#xff0c;比如租房、买房、找工作等。在软件设计中&#xff0c;也有一种名为“中介者模式”的设计模式&#xff0c;它的作用和我们生活中的“中介”有着异曲同工之妙。 中介者…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Divider)

提供分隔器组件&#xff0c;分隔不同内容块/内容元素。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Divider() 从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使…

微服务超大Excel文件导出方案优化

1、在导出Excel时经常会碰到文件过大&#xff0c;导出特别慢 2、微服务限制了请求超时时间&#xff0c;文件过大情况必然超时 优化思路&#xff1a; 1、文件过大时通过文件拆分、打包压缩zip&#xff0c;然后上传到oss,并设置有效期&#xff08;30天过期&#xff09; 2、把…

selenium爬取房价收入比可视化

数据来源&#xff1a;聚合数据 from selenium import webdriver from bs4 import BeautifulSoup import csv from selenium import webdriver from fake_useragent import UserAgent import random import subprocess from selenium import webdriver from selenium.webdrive…

Java高频面试之消息队列与分布式篇

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家 消息队列的基本作用&#xff1f; 异步通信&#xff1a;消息队列提供了异步通信的能力&#xff0c;发送方可以将消息发送到队列中&#xff0c;而无需等待接收方立即处理。发送方和接收方可以解耦&#x…

window vscode安装node.js

window vscode安装node.js 官网下好vscode 和nodejs 选.msi的安装 点这个安装 下载完 继续安装 完毕后倒杯水喝个茶等2分钟 重启VScode 或者在cmd 运行 npm -v node -v 显示版本号则成功

波卡 Alpha 计划启动,呼唤先锋创新者重新定义 Web3 开发

原文&#xff1a;https://polkadot.network/blog/the-polkadot-alpha-program-a-new-era-for-decentralized-building-collaboration/ 编译&#xff1a;OneBlock 区块链领域不断发展&#xff0c;随之而来的是发展和创新机会的增加。而最新里程碑是令人振奋的 Polkadot Alpha …

鸿蒙Harmony应用开发—ArkTS声明式开发(触摸交互控制:触摸热区设置)

适用于支持通用点击事件、通用触摸事件、通用手势处理的组件。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 responseRegion responseRegion(value: Array<Rectangle> | Rectangle) 设置一个或…

UI自动化测试使用场景及脚本录制

经常有人会问&#xff0c;什么样的项目才适合进行UI自动化测试呢&#xff1f;UI自动化测试相当于模拟手工测试&#xff0c;通过程序去操作页面上的控件。而在实际测试过程中&#xff0c;经常会遇到无法找到控件&#xff0c;或者因控件定义变更而带来的维护成本等问题。 哪些场…

《UE5_C++多人TPS完整教程》学习笔记26 ——《P27 在线会话测试(Testing An Online Session)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P27 在线会话测试&#xff08;Testing An Online Session&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff0…

K8s-MySQL主从集群

K8s-MySQL主从集群 引言 该案例代码均可从https://github.com/WeiXiao-Hyy/k8s_example 获取&#xff0c;欢迎Star&#xff01; 需求 一个“主从复制”的MySQL集群有一个主节点Master有多个从节点Slave从节点需要能水平扩展所以写操作只能在主节点上执行读操作可以在所有节点…

ACM32系列 MCU安全特性概述

随着物联网的发展&#xff0c;智能化产品的不断涌现&#xff0c;信息安全问题也日渐受到关注。因此&#xff0c;通用安全MCU产品也应运而生&#xff0c;能够更好地帮助客户在其产品设计中加强安全性&#xff0c;助力IoT的应用创新。 本文将详细介绍ACM32系列 MCU的安全特性。 …

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

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

面试经典150题【61-70】

文章目录 面试经典150题【61-70】61.旋转链表86.分隔链表104. 二叉树的最大深度100.相同的树226.翻转二叉树101.对称二叉树105.从前序与中序遍历序列构造二叉树106.从后序和中序遍历序列构造二叉树117.填充每个节点的下一个右侧节点指针II114.二叉树展开为链表 面试经典150题【…

怎么把视频变成gif动图?一招在线生成gif动画

MP4是一种常见的视频文件格式&#xff0c;它是一种数字多媒体容器格式&#xff0c;可以用于存储视频、音频和字幕等多种媒体数据。MP4格式通常用于在计算机、移动设备和互联网上播放和共享视频内容。要将MP4视频转换为GIF格式&#xff0c;您可以使用专门的视频转gif工具。这个工…

【Linux】第一个小程序--进度条

这篇博客要综合利用以前的知识&#xff0c;来实现一个进度条程序~ 目录 换行&回车 缓冲区 实现简单的倒计时 实现进度条 version1 version2 在开始写这个小程序之前&#xff0c;我们先学习一些预备知识&#xff1a; 换行&回车 缓冲区 在我们运行这个程序时&…

生活的色彩--爱摸鱼的美工(17)

题记 生活不如意事十之八九&#xff0c; 恶人成佛只需放下屠刀&#xff0c;善人想要成佛却要经理九九八十一难。而且历经磨难成佛的几率也很小&#xff0c;因为名额有限。 天地不仁以万物为刍狗&#xff01; 小美工记录生活&#xff0c;记录绘画演变过程的一天。 厨房 食…