如何将视频文件.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协议 ...
随机推荐
- tkinter的标签和按钮以及输入和文本
一.标签和文本 import tkinter as tk #1.定义tk的实例对象,也就是窗口对象 window = tk.TK() #2.设置窗口大小无法缩小和放大 window.resiable( ...
- SELECT COUNT(*) 会造成全表扫描?回去等通知吧
本文已经收录到Github仓库,该仓库包含计算机基础.Java基础.多线程.JVM.数据库.Redis.Spring.Mybatis.SpringMVC.SpringBoot.分布式.微服务.设计模式 ...
- c++基本数据结构
基本数据结构: 一.线性表 1.顺序结构 线性表可以用普通的一维数组存储. 你可以让线性表可以完成以下操作(代码实现很简单,这里不再赘述): 返回元素个数. 判断线性表是否为空. 得到位置为p的元素. ...
- Network Science:巴拉巴西网络科学学习笔记3——第二章随机网络
第二章:随机网络Erdős-Rényi Network (ER网络) 随机网络的两种定义形式: \(G(N,L)\)模型:N个节点,L条边随机链接. \(G(N,p)\)模型:N个节点,每个节点之间以 ...
- 【LeetCode动态规划#12】详解买卖股票I~IV,经典dp题型
买卖股票的最佳时机 力扣题目链接(opens new window) 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格. 你只能选择 某一天 买入 ...
- 解决Godot使用VsCode编写C#代码,智能提示不见了[一问随笔]
问题: 我的项目采用了godot + visual studio code + C#,有天突然换引擎,从Godot4.0.0升级到Godot4.0.2,visual studio code 突然不给代 ...
- [C++核心编程] 4.6、继承
文章目录 4.6 继承 4.6.1 继承的基本语法 4.6.2 继承方式 4.6.3 继承中的对象模型 4.6.4 继承中构造和析构顺序 4.6.5 继承同名成员处理方式 4.6.6 继承同名静态成员 ...
- 特性介绍 | MySQL 测试框架 MTR 系列教程(二):进阶篇 - 内存/线程/代码覆盖率/单元/压力测试
作者:卢文双 资深数据库内核研发 序言: 以前对 MySQL 测试框架 MTR 的使用,主要集中于 SQL 正确性验证.近期由于工作需要,深入了解了 MTR 的方方面面,发现 MTR 的能力不仅限于此 ...
- #PowerBi 1分钟学会,powerbi中行列值拼接(COMBINEVALUES与CONCATENATEX)
在日常的工作中,我们往往需要对表格数据的拼接,用来生成一些复合数据列,如下图类似场景. 其实,在powerbi中,我们同样也可以对表格文本进行拼接.今天我们就介绍两个DAX函数,COMBINEVALU ...
- pycharm eslint should be on a new line
修改前: "vue/max-attributes-per-line": [2, { "singleline": 10, "multiline" ...