如何将视频文件.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程序,实现以管理员方式运行程序,也就是提升程序权限
			quest UAC elevation from within a Python script? 我希望我的Python脚本能够在Vista上复制文件. 当我从普通的cmd.exe窗口运行它时,不会生 ... 
- sip消息拆包原理及组包流程
			操作系统 :CentOS 7.6_x64 freeswitch版本 :1.10.9 sofia-sip版本: sofia-sip-1.13.14 freeswitch使用sip协议进行通 ... 
- zookeeper重启,线上微服务全部掉线,怎么回事?
			注册中心zookeeper被重启,线上微服务全部掉线,怎么回事?! 最近因为一次错误的运维操作,导致线上注册中心zk被重启.而zk重启后发现所有线上微服务开始不断掉线,造成了持续30分钟的P0故障. ... 
- Java中方法的定义和使用
			方法的定义和使用 注意事项: 1.方法与方法之间是 平级关系 不可以嵌套定义 2.方法的位置 可以在类{}中任意位置 3.方法定义之后 之后被调用 才能被执行 4.return 关键字的作用 返回关 ... 
- SpringBoot应用集成微服务组件Nacos
			目录 springboot与微服务组件nacos Nacos服务快速启动 STS4 开发工具 Maven 环境配置 STS4开发工具引入Maven配置 Maven Repo配置阿里云镜像源 Sprin ... 
- Typecho<=1.2.0 存储型XSS 复现
			Typecho<=1.2.0 存储型XSS 影响版本 漏洞影响版本:Typecho <= 1.2.0 漏洞复现 cookie.js // 定义一个全局变量 website,值为一个具体的网 ... 
- 白嫖GitHub Action实现开源项目CICD
			什么是CI/CD?让你的项目变得更加敏捷! 在今天这个快速变化的时代,开发者们需要与时俱进,不断提升自己的工作效率.在这篇文章里,将一起探讨如何使用CI/CD和Github Action让你的项目更加 ... 
- C# 反射 操作列表类型属性
			本文介绍对列表进行创建及赋值的反射操作 我们现在有TestA.TestB类,TestA中有TestB类型列表的属性List,如下: 1 public class TestA 2 { 3 public ... 
- 【opencv-c++】4.1-4.2 图像和大型数组类型
			4.1-4.2 图像和大型数组类型 动态可变的存储 我们将进入大型数组类型的世界,它们之中最主要的当属c v::Mat,这个结构可以视为是OpenCV所有C++实现的核心,OpenCV所有主要函数都或 ... 
- Selenium 元素定位方式封装的实际应用
			一.定位方式 二.实际应用 1.项目结构 2.locator_base.py 文件 # -*- coding: utf-8 -*- from selenium.webdriver.common.by ... 
