如何将视频文件.h264和音频文件.mp3复用为输出文件output.mp4?
一.初始化复用器
在这个部分我们可以分三步进行:(1)打开输入视频文件上下文句柄 (2)打开输入音频文件上下文句柄 (3)打开输出文件上下文句柄
1.打开输入视频文件上下文句柄
在这一步,我们主要用到两个重要的函数:av_find_input_format()和avformat_open_input()。我们先调用av_find_input_format函数得到输入视频文件的格式,然后将该格式和视频文件的路径传入avformat_open_input()函数,就可以打开输入视频文件的上下文句柄。下面给出代码:
#define STREAM_FRAME_RATE 25
static AVFormatContext *video_fmt_ctx= nullptr,*audio_fmt_ctx= nullptr,*output_fmt_ctx= nullptr;
static AVPacket *pkt;
static int32_t in_video_st_idx=-1,in_audio_st_idx=-1;
static int32_t out_video_st_idx=-1,out_audio_st_idx=-1;
static int32_t init_input_video(const char *video_input_file,const char *video_format){
int32_t result=0;
const AVInputFormat *video_input_format= av_find_input_format(video_format);
if(!video_input_format){
cerr<<"Error:av_find_input_format failed."<<endl;
return -1;
}
result= avformat_open_input(&video_fmt_ctx,video_input_file,video_input_format, nullptr);
if(result<0){
cerr<<"Error:avformat_open_input failed."<<endl;
return -1;
}
result=avformat_find_stream_info(video_fmt_ctx, nullptr);
if(result<0){
cerr<<"Error:avformat_find_stream_info failed."<<endl;
return -1;
}
return 0;
}
2.打开输入音频文件上下文句柄
打开输入音频文件上下文句柄的方法和上面的输入视频文件类似,直接上代码:
static int32_t init_input_audio(const char *audio_input_file,const char *audio_format){
int32_t result=0;
const AVInputFormat *audio_input_format= av_find_input_format(audio_format);
if(!audio_input_format){
cerr<<"Error:av_find_input_format failed."<<endl;
return -1;
}
result= avformat_open_input(&audio_fmt_ctx,audio_input_file,audio_input_format, nullptr);
if(result<0){
cerr<<"Error:avformat_open_input failed."<<endl;
return -1;
}
result=avformat_find_stream_info(audio_fmt_ctx, nullptr);
if(result<0){
cerr<<"Error:avformat_find_stream_info failed."<<endl;
return -1;
}
return 0;
}
3.打开输出文件上下文句柄
打开输出文件上下文句柄需要调用函数avformat_alloc_output_context2(),在创建了输出文件上下文句柄后,我们需要添加一路音频流和一路视频流,此时我们需要用到函数avformat_new_stream();在调用此函数后,我们会得到AVStream *类型的指针。然后,我们需要将输入视频文件和音频文件的编码器相关参数复制到输出的视频流和音频流编码器中。最后,打开输出文件,将文件的I/O结构对应到输出文件的AVFormatContext结构。代码如下:
static int32_t init_output(const char *output_file){
int32_t result=0;
avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr,output_file);
if(!output_fmt_ctx){
cerr<<"Error:avformat_alloc_output_context2 failed."<<endl;
return -1;
}
const AVOutputFormat *fmt=output_fmt_ctx->oformat;
cout<<"Default video codec id:"<<fmt->video_codec<<", audio codec id:"<<fmt->audio_codec<<endl;
AVStream *video_stream= avformat_new_stream(output_fmt_ctx, nullptr);
if(!video_stream){
cerr<<"Error:add video stream to output format context failed."<<endl;
return -1;
}
out_video_st_idx=video_stream->index;
in_video_st_idx= av_find_best_stream(video_fmt_ctx,AVMEDIA_TYPE_VIDEO,-1,-1, nullptr,0);
if(in_video_st_idx<0){
cerr<<"Error:find video stream in input video file failed."<<endl;
return -1;
}
result= avcodec_parameters_copy(video_stream->codecpar,video_fmt_ctx->streams[in_video_st_idx]->codecpar);
if(result<0){
cerr<<"avcodec_parameters_copy failed."<<endl;
return -1;
}
video_stream->id=output_fmt_ctx->nb_streams-1;
video_stream->time_base=AVRational {1,STREAM_FRAME_RATE};
AVStream *audio_stream= avformat_new_stream(output_fmt_ctx, nullptr);
if(!audio_stream){
cerr<<"Error:add audio stream to output format context failed."<<endl;
return -1;
}
out_audio_st_idx=audio_stream->index;
in_audio_st_idx= av_find_best_stream(audio_fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1, nullptr,0);
if(in_audio_st_idx<0){
cerr<<"Error:find audio stream in input audio file failed."<<endl;
return -1;
}
result= avcodec_parameters_copy(audio_stream->codecpar,audio_fmt_ctx->streams[in_audio_st_idx]->codecpar);
if(result<0){
cerr<<"Error:copy audio codec parameters failed."<<endl;
return -1;
}
audio_stream->id=output_fmt_ctx->nb_streams-1;
audio_stream->time_base=AVRational {1,audio_stream->codecpar->sample_rate};
av_dump_format(output_fmt_ctx,0,output_file,1);
cout<<"Output video idx:"<<out_video_st_idx<<",audio idx:"<<out_audio_st_idx<<endl;
if(!(fmt->flags&AVFMT_NOFILE)){//判断AVFMT_NOFILE标志位是否设置在fmt->flags中
result= avio_open(&output_fmt_ctx->pb,output_file,AVIO_FLAG_WRITE);
if(result<0){
cerr<<"Error:avio_open failed."<<endl;
return -1;
}
}
return 0;
}
下面,给出初始化复用器的完整代码:
int32_t init_muxer(const char *video_input_file,const char *audio_input_file,const char *output_file){
int32_t result= init_input_video(video_input_file,"h264");
if(result<0){
return -1;
}
result= init_input_audio(audio_input_file,"mp3");
if(result<0){
return -1;
}
result=init_output(output_file);
if(result<0){
return -1;
}
return 0;
}
二.复用音频流和视频流
在这里,我们也可以分三步进行:(1)写入输出文件的头结构 (2)循环写入音频包和视频包 (3)写入输出文件的尾结构
1.写入输出文件的头结构
这一步很简单,调用avformat_write_header()函数就可以轻松实现。
2.循环写入音频包和视频包
这一步比较复杂,我们首先需要确定音频包和视频包的时间戳,判断写入顺序;这里我们需要比较音频包和视频包的时间戳,如果当前记录的音频时间戳比视频时间戳新,则接下来就应该写入视频数据了。但是,从H.264格式的裸码流中读取的视频包中通常不包含时间戳数据,所以我们需要计算视频包的时间戳。我们可以先计算出每一帧的持续时长,然后乘以帧序号就可以得到这一帧的时间戳了。代码如下:
if(pkt->pts==AV_NOPTS_VALUE){
int64_t frame_duration=(double)AV_TIME_BASE/ av_q2d(in_video_st->r_frame_rate);
pkt->duration=(double)frame_duration/(double)(av_q2d(in_video_st->time_base)*AV_TIME_BASE);
pkt->pts=(double)video_frame_idx*pkt->duration;
pkt->dts=pkt->pts;
cout<<"frame_duration:"<<frame_duration<<",pkt->duration:"<<pkt->duration<<",pkt->pts:"<<pkt->pts<<endl;
}
还有一点需要注意的是,从输入文件读取的码流包中保存的时间戳是以输入流的time_base为基准的,在写入输出文件时,需要转换为以输出流的time_base为基准。代码如下:
pkt->pts= av_rescale_q_rnd(pkt->pts,input_stream->time_base,output_stream->time_base,(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt->dts=av_rescale_q_rnd(pkt->dts,input_stream->time_base,output_stream->time_base,(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt->duration=av_rescale_q(pkt->duration,input_stream->time_base,output_stream->time_base);
3.写入输出文件的尾结构
调用av_write_trailer()函数即可
完整代码如下:
int32_t muxing(){
int32_t result=0;
int64_t cur_video_pts=0,cur_audio_pts=0;
AVStream *in_video_st=video_fmt_ctx->streams[in_video_st_idx];
AVStream *in_audio_st=audio_fmt_ctx->streams[in_audio_st_idx];
AVStream *output_stream= nullptr,*input_stream= nullptr;
int32_t video_frame_idx=0;
result= avformat_write_header(output_fmt_ctx, nullptr);
if(result<0){
return result;
}
pkt=av_packet_alloc();
if(!pkt){
cerr<<"Error:av_packet_alloc failed."<<endl;
return -1;
}
cout<<"Video r_frame_rate:"<<in_video_st->r_frame_rate.num<<"/"<<in_video_st->r_frame_rate.den<<endl;
cout<<"Video time_base:"<<in_video_st->time_base.num<<"/"<<in_video_st->time_base.den<<endl;
while(true){
if(av_compare_ts(cur_video_pts,in_video_st->time_base,cur_audio_pts,in_audio_st->time_base)<=0){
input_stream=in_video_st;
result=av_read_frame(video_fmt_ctx,pkt);
if(result<0){
av_packet_unref(pkt);
break;
}
if(pkt->pts==AV_NOPTS_VALUE){
int64_t frame_duration=(double)AV_TIME_BASE/ av_q2d(in_video_st->r_frame_rate);
pkt->duration=(double)frame_duration/(double)(av_q2d(in_video_st->time_base)*AV_TIME_BASE);
pkt->pts=(double)video_frame_idx*pkt->duration;
pkt->dts=pkt->pts;
cout<<"frame_duration:"<<frame_duration<<",pkt->duration:"<<pkt->duration<<",pkt->pts:"<<pkt->pts<<endl;
}
video_frame_idx++;
cur_video_pts=pkt->pts;
pkt->stream_index=out_video_st_idx;
output_stream=output_fmt_ctx->streams[out_video_st_idx];
}
else{
input_stream=in_audio_st;
result= av_read_frame(audio_fmt_ctx,pkt);
if(result<0){
av_packet_unref(pkt);
break;
}
cur_audio_pts=pkt->pts;
pkt->stream_index=out_audio_st_idx;
output_stream=output_fmt_ctx->streams[out_audio_st_idx];
}
pkt->pts= av_rescale_q_rnd(pkt->pts,input_stream->time_base,output_stream->time_base,(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt->dts=av_rescale_q_rnd(pkt->dts,input_stream->time_base,output_stream->time_base,(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt->duration=av_rescale_q(pkt->duration,input_stream->time_base,output_stream->time_base);
cout<<"Final pts:"<<pkt->pts<<",duration:"<<pkt->duration<<",output_stream->time_base:"<<output_stream->time_base.num<<"/"<<output_stream->time_base.den<<endl;
if(av_interleaved_write_frame(output_fmt_ctx,pkt)<0){//写入码流包
cerr<<"Error:failed to mux packet!"<<endl;
break;
}
av_packet_unref(pkt);
}
result= av_write_trailer(output_fmt_ctx);
if(result<0){
return result;
}
return result;
}
三.释放复用器实例
void destroy_muxer(){
avformat_free_context(video_fmt_ctx);
avformat_free_context(audio_fmt_ctx);
if(!(output_fmt_ctx->oformat->flags&AVFMT_NOFILE)){
avio_closep(&output_fmt_ctx->pb);
}
avformat_free_context(output_fmt_ctx);
}
四.最终的main函数如下:
int main(){
int32_t result=0;
result= init_muxer("../input.h264","../input.mp3","../output.mp4");
if(result<0){
return result;
}
result=muxing();
if(result<0){
return result;
}
destroy_muxer();
return 0;
}
最后,使用以下指令可以播放输出的output.mp4文件:
ffplay -i output.mp4
如何将视频文件.h264和音频文件.mp3复用为输出文件output.mp4?的更多相关文章
- 从头開始写项目Makefile(七):统一目标输出文件夹
[版权声明:转载请保留出处:blog.csdn.net/gentleliu. Mail:shallnew at 163 dot com] 上一节我们把规则单独提取出来,方便了Makefile的 ...
- javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- Android 音视频开发(五):使用 MediaExtractor 和 MediaMuxer API 解析和封装 mp4 文件
一个音视频文件是由音频和视频组成的,我们可以通过MediaExtractor.MediaMuxer把音频或视频给单独抽取出来,抽取出来的音频和视频能单独播放: 一.MediaExtractor API ...
- 在Python中使用moviepy进行音视频剪辑混音合成时输出文件无声音问题
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在使用moviepy进行音视频剪辑时发现输出成功但 ...
- swift 录制多个音频 并将音频转换为mp3 并合成多个mp3文件为一个文件
我的需求是可以录制多个文件,最后生成的文件格式为mp3形式,查了下各种资料,因为swift无法直接将音频录制为mp3格式,所以最后我采取的解决方案为先将每个单独的文件转为mp3,最后逐一合并形成一个m ...
- 微信小程序存放视频文件到阿里云用到算法js脚本文件
peterhuang007/weixinFileToaliyun: 微信小程序存放视频文件到阿里云用到算法js脚本文件 https://github.com/peterhuang007/ ...
- C# NAudio录音和播放音频文件-实时绘制音频波形图(从音频流数据获取,而非设备获取)
NAudio的录音和播放录音都有对应的类,我在使用Wav格式进行录音和播放录音时使用的类时WaveIn和WaveOut,这两个类是对功能的回调和一些事件触发. 在WaveIn和WaveOut之外还有对 ...
- 在Python中使用moviepy进行视频剪辑时输出文件报错 ‘NoneType‘ object has no attribute ‘stdout‘问题
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 movipy输出文件时报错 'NoneType' ...
- 音视频编解码问题:javaCV如何快速进行音频预处理和解复用编解码(基于javaCV-FFMPEG)
前言: 前面我用了很多章实现了javaCV的基本操作,包括:音视频捕捉(摄像头视频捕捉和话筒音频捕捉),推流(本地音视频或者摄像头话筒混合推流到服务器),转流(rtsp->rtmp),收流(录制 ...
- MP4大文件虚拟HLS分片技术,避免服务器大量文件碎片
MP4大文件虚拟HLS分片技术,避免点播服务器的文件碎片 本文主要介绍了通过虚拟分片技术,把MP4文件,映射为HLS协议中的一个个小的TS分片文件,实现了在不实际切分MP4文件的情况下,通过HLS协议 ...
随机推荐
- 学习关于JavaScript常用的8大设计模式
JavaScript 常用的8大设计模式有 工厂模式:工厂模式是一种创建对象的模式,可以通过一个共同的接口创建不同类型的对象,隐藏了对象的创建过程. 单例模式:单例模式是一种只允许实例化一次的对象模式 ...
- 12年经验的大龄程序员,都用什么写 API 文档?
写代码,程序员不害怕. 写文档,每个程序员都害怕! 为什么? 技术优先,我们更倾向于将技能和精力更多地放在编写代码上,如果 API 工具不好使,不便捷,同步麻烦,测试看不懂,更会大大地打击编写文档的积 ...
- 【Mybatis Plus】
引入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> ...
- Java学习笔记04
1. 循环进阶 1.1 无限循环 概念 循环一直停不下来,又叫死循环. for格式 for (;;) { 循环语句; } while格式 while (true) { 循环语句; } do...w ...
- Mysql8.0为什么取消了缓存查询的功能
首先我们介绍一下MySQL的缓存机制 [MySQL缓存机制]简单的说就是缓存sql文本及查询结果,如果运行完全相同的SQL,服务器直接从缓存中取到结果,而不需要再去解析和执行SQL. 但如果表中任何数 ...
- DeFi-W3
Gas Fee 每一笔交易都会产生Gas Fee. GWei ETH的最小单位 出价(gas fee)的高低会影响交易上联的速度,越快就价格越高. gas fee是跟具体的计算量有关的 Smart c ...
- 6个优化策略,助你降低K8S成本
Kubernetes 早已成为容器编排引擎的事实标准,而随着 Kubernetes 环境的复杂性持续增长,成本也在不断攀升.CNCF 发布的调查报告<Kubernetes 的 FinOps> ...
- [C++提高编程] 3.5 stack容器
文章目录 3.5 stack容器 3.5.1 stack 基本概念 3.5.2 stack 常用接口 3.5 stack容器 3.5.1 stack 基本概念 概念:stack是一种先进后出(Firs ...
- #Python基础 pandas索引设置
一:XMIND 二:设置索引 示例数据,假设我们有一个DataFrame对象,如下: import pandas as pd df = pd.DataFrame({ "name": ...
- 2022-11-19:第二高的薪水。表结构和数据的sql语句如下,输出200,因为200是第二大的。请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT
2022-11-19:第二高的薪水.表结构和数据的sql语句如下,输出200,因为200是第二大的.请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT ...