如何将mp4文件解复用并且解码为单独的.yuv图像序列以及.pcm音频采样数据?
一.初始化解复用器
在音视频的解复用的过程中,有一个非常重要的结构体AVFormatContext,即输入文件的上下文句柄结构,代表当前打开的输入文件或流。我们可以将输入文件的路径以及AVFormatContext **format_ctx 传入函数avformat_open_input(),就可以打开对应的音视频文件或流。接下来再调用avformat_find_stream_info()函数去解析输入文件中的音视频流信息,打开对应的解码器,读取文件头的信息进行解码, 然后在解码过程中将一些参数的信息保存到AVStream结构对应的成员中。之后,我们便可以通过AVStream去初始化编解码器的上下文结构,下面给出代码:
static AVFormatContext *format_ctx= nullptr;
static AVCodecContext *video_dec_ctx= nullptr,*audio_dec_ctx= nullptr;
static int32_t video_stream_index=-1;
static int32_t audio_stream_index=-1;
static AVStream *video_stream= nullptr,*audio_stream= nullptr;
static FILE *output_video_file= nullptr,*output_audio_file= nullptr;
static AVPacket *pkt= nullptr;
static AVFrame *frame= nullptr; static int open_codec_context(int32_t *stream_idx,AVCodecContext **dec_ctx,AVFormatContext *fmt_ctx,enum AVMediaType type){
int ret,stream_index;
AVStream *st= nullptr;
const AVCodec *dec= nullptr;
ret= av_find_best_stream(fmt_ctx,type,-1,-1, nullptr,0);
if(ret<0){
cerr<<"Error:Could not find "<<string(av_get_media_type_string(type))<<" stream in input file."<<endl;
return ret;
}
else{
stream_index=ret;
st=fmt_ctx->streams[stream_index];
//find decoder for the stream
dec= avcodec_find_decoder(st->codecpar->codec_id);
if(!dec){
cerr<<"Error:Failed to find codec:"<<string(av_get_media_type_string(type))<<endl;
return -1;
}
*dec_ctx= avcodec_alloc_context3(dec);
if(!*dec_ctx){
cerr<<"Error:Failed to alloc codec context:"<<string(av_get_media_type_string(type))<<endl;
return -1;
}
if((ret= avcodec_parameters_to_context(*dec_ctx,st->codecpar))<0){
cerr<<"Error:Failed to copy codec parameters to decoder context."<<endl;
return ret;
}
if((ret=avcodec_open2(*dec_ctx,dec, nullptr))<0){
cerr<<"Error:Could not open "<<string(av_get_media_type_string(type))<<" codec."<<endl;
return ret;
}
*stream_idx=stream_index;
}
return 0;
}
int32_t init_demuxer(const char *input_name,const char *video_output_name,const char *audio_output_name){
if(strlen(input_name)==0){
cerr<<"Error:empty input file name."<<endl;
exit(-1);
}
int32_t result= avformat_open_input(&format_ctx,input_name, nullptr, nullptr);
if(result<0){
cerr<<"Error:avformat_open_input failed."<<endl;
exit(-1);
}
result= avformat_find_stream_info(format_ctx, nullptr);
if(result<0){
cerr<<"Error:avformat_find_stream_info failed."<<endl;
exit(-1);
}
result= open_codec_context(&video_stream_index,&video_dec_ctx,format_ctx,AVMEDIA_TYPE_VIDEO);
if(result>=0){
video_stream=format_ctx->streams[video_stream_index];
output_video_file=fopen(video_output_name,"wb");
if(!output_video_file){
cerr<<"Error:failed to open video output file."<<endl;
return -1;
}
}
result= open_codec_context(&audio_stream_index,&audio_dec_ctx,format_ctx,AVMEDIA_TYPE_AUDIO);
if(result>=0){
audio_stream=format_ctx->streams[audio_stream_index];
output_audio_file=fopen(audio_output_name,"wb");
if(!output_audio_file){
cerr<<"Error:failed to open audio output file."<<endl;
return -1;
}
}
av_dump_format(format_ctx,0,input_name,0);
if(!audio_stream&&!video_stream){
cerr<<"Error:Could not find audio or video stream in the input,aborting"<<endl;
return -1;
}
pkt=av_packet_alloc();
if(!pkt){
cerr<<"Error:could not alloc packet."<<endl;
return -1;
}
frame=av_frame_alloc();
if(!frame){
cerr<<"Error:could not alloc frame."<<endl;
return -1;
}
if(video_stream){
cout<<"Demuxing video from file "<<string(input_name)<<" into "<<string(video_output_name)<<endl;
}
if(audio_stream){
cout<<"Demuxing audio from file "<<string(input_name)<<" into "<<string(audio_output_name)<<endl;
}
return 0;
}
二.循环读取码流包数据进行解码
在这里,我们需要调用一个非常重要的函数av_read_frame(),它可以从打开的音视频文件或流中依次读取下一个码流包结构,然后我们将码流包传入解码器进行解码即可,代码如下:
static int32_t decode_packet(AVCodecContext *dec,const AVPacket *pkt,bool flushing){
int32_t result=0;
result= avcodec_send_packet(dec,pkt);
if(result<0){
cerr<<"Error:avcodec_send_packet failed."<<endl;
return -1;
}
while(result>=0){
result=avcodec_receive_frame(dec,frame);
if(result<0){
if(result==AVERROR_EOF||result==AVERROR(EAGAIN)){
return 0;
}
cerr<<"Error:Error during decoding,result="<<result<<endl;
return result;
}
if(dec->codec->type==AVMEDIA_TYPE_VIDEO){
write_frame_to_yuv(frame);
}
else{
write_samples_to_pcm(frame,audio_dec_ctx);
}
if(flushing){
cout<<"flushing"<<endl;
}
av_frame_unref(frame);
}
return result;
}
int32_t demuxing(){
int32_t result=0;
while(av_read_frame(format_ctx,pkt)>=0){
cout<<"Read packet,pts:"<<pkt->pts<<",stream:"<<pkt->stream_index<<",size:"<<pkt->size<<endl;
if(pkt->stream_index==audio_stream_index){
result= decode_packet(audio_dec_ctx,pkt,false);
}
else if(pkt->stream_index==video_stream_index){
result= decode_packet(video_dec_ctx,pkt,false);
}
av_packet_unref(pkt);
if(result<0){
break;
}
}
if(video_dec_ctx){
decode_packet(video_dec_ctx, nullptr,true);
}
if(audio_dec_ctx){
decode_packet(audio_dec_ctx, nullptr,true);
}
cout<<"Demuxing succeeded."<<endl;
return 0;
}
三.将解码后的图像序列以及音频采样数据写入相应的文件
这个步骤比较简单,不解释,直接上代码:
int32_t write_frame_to_yuv(AVFrame* frame){
uint8_t** pBuf=frame->data;
int* pStride=frame->linesize;
for(size_t i=0;i<3;i++){
int32_t width=(i==0?frame->width:frame->width/2);
int32_t height=(i==0?frame->height:frame->height/2);
for(size_t j=0;j<height;j++){
fwrite(pBuf[i],1,width,output_video_file);
pBuf[i]+= pStride[i];
}
}
return 0;
}
int32_t write_samples_to_pcm(AVFrame* frame,AVCodecContext* codec_ctx){
int data_size= av_get_bytes_per_sample(codec_ctx->sample_fmt);
if(data_size<0){
cerr<<"Error:failed to calculate data size."<<endl;
return -1;
}
for(int i=0;i<frame->nb_samples;i++){
for(int ch=0;ch<codec_ctx->channels;ch++){
fwrite(frame->data[ch]+i*data_size,1,data_size,output_audio_file);
}
}
return 0;
}
四.销毁资源,释放内存
void destroy_demuxer(){
avcodec_free_context(&video_dec_ctx);
avcodec_free_context(&audio_dec_ctx);
avformat_close_input(&format_ctx);
if(output_audio_file!= nullptr){
fclose(output_audio_file);
output_audio_file= nullptr;
}
if(output_video_file!= nullptr){
fclose(output_video_file);
output_video_file= nullptr;
}
}
五.main函数
int main(){
int32_t result=init_demuxer("../input.mp4","../output.yuv","../output.pcm");
if(result<0){
return -1;
}
result=demuxing();
if(result<0){
return -1;
}
destroy_demuxer();
return 0;
}
到这里,就大功告成了,可以使用以下的命令去播放输出的音视频文件:
ffplay -ac 2 -ar 44100 -f f32le -i output.pcm
ffplay -f rawvideo -video_size 1920x1080 -i output.yuv
如何将mp4文件解复用并且解码为单独的.yuv图像序列以及.pcm音频采样数据?的更多相关文章
- 音视频编解码问题:javaCV如何快速进行音频预处理和解复用编解码(基于javaCV-FFMPEG)
前言: 前面我用了很多章实现了javaCV的基本操作,包括:音视频捕捉(摄像头视频捕捉和话筒音频捕捉),推流(本地音视频或者摄像头话筒混合推流到服务器),转流(rtsp->rtmp),收流(录制 ...
- JavaCV的摄像头实战之六:保存为mp4文件(有声音)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- FFmpeg(2)-avformat_open_input()函数详解并示例打开mp4文件
一. 解封装 pts 是显示的时间 dts是解码的时间, 这个时间是用来做同步. av_register_all(), 注册所有的格式.包括解封装格式和加封装格式. avformat_network_ ...
- mux复用 demux解复用
保存音频包: 直接输出解复用之后的的音频数据码流.只需要在每次调用av_read_frame()之后将得到的音频的AVPacket存为本地文件即可. 但在分离AAC码流的时候,直接存储AVPacket ...
- 解复用-mpeg2
http://blog.csdn.net/yipie/article/details/7612226 数字高清晰度电视(High Definition Television)简称HDTV,是继黑白电视 ...
- 嵌入式 使用mp4v2将H264+AAC合成mp4文件
录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是: (1)接收并解析RTP包,分离出H264和AAC数据流: (2)将H26 ...
- 嵌入式 H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流
一.MP4格式基本概念 MP4格式对应标准MPEG-4标准(ISO/IEC14496) 二.MP4封装格式核心概念 1 MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象 ...
- 使用mp4v2将H264+AAC合成mp4文件
录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是: (1)接收并解析RTP包,分离出H264和AAC数据流: (2)将H26 ...
- 【转】使用ffmpeg转码的MP4文件需要加载完了才能播放的解决办法
1.前一段时间做了一个ffmpeg转码MP4的项目,但是转出来的MP4部署在网站上需要把整个视频加载完成才能播放,到处找资料,最后找到解决方案记录于此备忘. FFMpeg转码由此得到的mp4文件中, ...
- [转载]为什么有些MP4文件在Chrome浏览器上播放不了?
http://blog.sina.com.cn/s/blog_6bb7ebcc0101c2ja.html Chrome浏览器支持HTML5,它支持原生播放部分的MP4格式(不用通过Flash等插件). ...
随机推荐
- [Linux]ln:软链接与硬链接
1 硬链接与软链接的[语法] 软链接:ln -s 源文件 目标文件 硬链接:ln 源文件 目标文件 [-s : symbolic,符号/代号] 2 软链接/硬链接的[比喻] / (编辑)同步性 [ro ...
- 机器学习(四):4层BP神经网络(只用numpy不调包)用于训练鸢尾花数据集|准确率96%
题目: 设计四层BP网络,以g(x)=sigmoid(x)为激活函数, 神经网络结构为:[4,10,6, 3],其中,输入层为4个节点,第一个隐含层神经元个数为10个节点:第二个隐含层神经元个数为6个 ...
- 【Note】贪心
感谢 $ \text{orzws/chy} $ 倾情授课. 目录 -1. 证明方式 0. 朴素贪心 AT2557 [ARC073C] Ball Coloring P2587 [ZJOI2008]泡泡堂 ...
- python:生成半年内的巡检日报execl
问题描述:使用脚本来生成半年内的数据,数据内容大概为每天的数据库巡检日报,临时抱佛脚.数据不可能是真实的,都是随机生成的,想要使用真实的数据后面直连操作系统或者数据库.后期可以慢慢实现自动化生成每天的 ...
- Nvidia Tensor Core初探
1 背景 在基于深度学习卷积网络的图像处理领域,作为计算密集型的卷积算子一直都是工程优化的重点,而卷积计算一般转化为矩阵乘运算,所以优化矩阵乘运算自然成为深度学习框架最为关心的优化方向之一.鉴于此,N ...
- 【机器学习入门与实践】数据挖掘-二手车价格交易预测(含EDA探索、特征工程、特征优化、模型融合等)
[机器学习入门与实践]数据挖掘-二手车价格交易预测(含EDA探索.特征工程.特征优化.模型融合等) note:项目链接以及码源见文末 1.赛题简介 了解赛题 赛题概况 数据概况 预测指标 分析赛题 数 ...
- python 类中的属性排序
可以使用Python中的类(class)来定义一个包含姓名和年龄的类.以下是一个示例代码: class Person: def __init__(self, name, age): self.name ...
- 23.oneOf
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') // 提取cs ...
- 笔记:网络IP数据包头部详解
笔记:网络IP数据包头部详解 传了无数次,每次图片都没了,真是郁闷,这编辑器,需要改下啊,各位CSDN的大神们!!! 由于最近看了一些相关的资料想起来这篇文章,所以补充 ...
- vue页面中展示markdown以及katex公式
场景 数据库中有markdown语法的字符串,需要展示为正常的页面,难点在于其中的katex数学公式 解决方式 使用showdown及其族系插件 npm i showdown npm i showdo ...