如何将视频文件.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协议 ...
随机推荐
- 【论文阅读笔记】Class-Incremental Learning with Strong Pre-trained Models
Key_words: Continual learning, strong pretrained model, fix, fusion Create_time: April 14, 2022 6:32 ...
- defineProperty在数据劫持后是如何通知数据的更新和视图的更新的
vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和ge ...
- 迁移学习(COAL)《Generalized Domain Adaptation with Covariate and Label Shift CO-ALignment》
论文信息 论文标题:Generalized Domain Adaptation with Covariate and Label Shift CO-ALignment论文作者:Shuhan Tan, ...
- vue3的setup语法糖
https://blog.csdn.net/weixin_44922480/article/details/127337914 https://blog.csdn.net/m0_63108819/ar ...
- DP做题记录
P1140 相似基因 考虑如何设计状态. 设给出的两个串为串 \(A\) 和串 \(B\),长度分别为 \(n\) 和 \(m\). 我们用 \(f[i][j]\) 来表示前 \(i\) 个 \(A\ ...
- percona-server-rocksdb-8.0.32 安装
MyRocks是关系型数据库Mysql 基于RocksDB 的存储引擎,一个可嵌入的.持久的键值存储.Percona MyRocks 是集于 Percona Server for MySQL的. Ro ...
- 2020-10-06:java中垃圾回收器让工作线程停顿下来是怎么做的?
福大大答案2020-10-06: 简单回答:安全点,主动式中断. 中级回答:用户线程暂停,GC 线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化.所以 JVM 会在字节码 ...
- 2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据。数据库不能停,并且还有增删改操作。请问如何操作?
2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据.数据库不能停,并且还有增删改操作.请问如何操作?福哥答案2020-01-20: 陌陌答案:用pt_onl ...
- 2021-08-24:合并石头的最低成本。有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的
2021-08-24:合并石头的最低成本.有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头.每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的 ...
- Selenium - 元素操作(3) - 下拉框操作
Selenium - 元素操作 下拉框才做可以分为两类: select标签的下拉框:使用Select类进行操作: 非select标签的下拉框:一般是 ul,li, div 等标签组成,使用元素定位的方 ...