如何将视频文件.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协议 ...
随机推荐
- python之zipfile应用
zipfile Python 中 zipfile 模块提供了对 zip 压缩文件的一系列操作. 1 f=zipfile.ZipFile("test.zip",mode=" ...
- 异常:java.io.FileNotFoundException:e:\demo(拒绝访间。)
禁止向目录中写数据,只能向文件写数据
- CSS3-页面布局基础一
一.CSS概要 web前端开发者最最注的内容是三个:HTML.CSS与JavaScript,他们分别在不同方面发挥自己的作用,HTML实现页面结构,CSS完成页面的表现与风格,JavaScript实现 ...
- SpringSecurity+Token实现权限校验
1.Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配 ...
- 论文解析 -- A Survey of AIOps Methods for Failure Management
此篇Survey是A Systematic Mapping Study in AIOps的后续研究 对于AIOPS中占比较高的Failure Management进行进一步的研究 Compared t ...
- cocos2dx返回Android游戏黑屏解决办法
用来解决返回Android游戏加载资源时黑屏的问题.帖子过些日子估计就沉了,所以转出来,以供后面查询. 需要修改三个文件: 1) cocos2dx/platform/CCPlatformMacros. ...
- 搭建SpringCloudAlibaba父工程
1.首先创建一个maven项目 删除src目录,当做一级目录用来管理第三方jar版本控制. 2.配置pom文件. SpringCloud.SpringCloudAlibaba.SpringBoot版本 ...
- Web服务器压力测试工具 - HULK
HULK是一种web的拒绝服务攻击工具.它能够在web服务器上产生许多单一的伪造流量,能绕开引擎的缓存,因此能够直接攻击服务器的资源池.hulk的特别之处在于:对于每一个请求都是独特的,能够绕开引擎的 ...
- [ZJOI2020] 序列 线性规划做法/贪心做法
线性规划做法 同时也作为线性规划对偶的一个小小的学习笔记. 以下 \(\cdot\) 表示点积,\(b,c,x,y\) 是行向量. \(A\) 是矩阵,对于向量 \(u,v\) 若 \(\forall ...
- 2020-11-23:go中,s是一个字符串,s[0]代表什么?是否等于固定字节数?
福个答案2020-11-23:Golang 的字符串(string)是合法的 UTF-8 序列,这就涉及到了两种不同的遍历方式,一种是按照 Unicode 的 codepoint 遍历,另一种是把 s ...