一.初始化解复用器

  在音视频的解复用的过程中,有一个非常重要的结构体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音频采样数据?的更多相关文章

  1. 音视频编解码问题:javaCV如何快速进行音频预处理和解复用编解码(基于javaCV-FFMPEG)

    前言: 前面我用了很多章实现了javaCV的基本操作,包括:音视频捕捉(摄像头视频捕捉和话筒音频捕捉),推流(本地音视频或者摄像头话筒混合推流到服务器),转流(rtsp->rtmp),收流(录制 ...

  2. JavaCV的摄像头实战之六:保存为mp4文件(有声音)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. FFmpeg(2)-avformat_open_input()函数详解并示例打开mp4文件

    一. 解封装 pts 是显示的时间 dts是解码的时间, 这个时间是用来做同步. av_register_all(), 注册所有的格式.包括解封装格式和加封装格式. avformat_network_ ...

  4. mux复用 demux解复用

    保存音频包: 直接输出解复用之后的的音频数据码流.只需要在每次调用av_read_frame()之后将得到的音频的AVPacket存为本地文件即可. 但在分离AAC码流的时候,直接存储AVPacket ...

  5. 解复用-mpeg2

    http://blog.csdn.net/yipie/article/details/7612226 数字高清晰度电视(High Definition Television)简称HDTV,是继黑白电视 ...

  6. 嵌入式 使用mp4v2将H264+AAC合成mp4文件

    录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是: (1)接收并解析RTP包,分离出H264和AAC数据流: (2)将H26 ...

  7. 嵌入式 H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流

    一.MP4格式基本概念 MP4格式对应标准MPEG-4标准(ISO/IEC14496) 二.MP4封装格式核心概念 1  MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象 ...

  8. 使用mp4v2将H264+AAC合成mp4文件

    录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是: (1)接收并解析RTP包,分离出H264和AAC数据流: (2)将H26 ...

  9. 【转】使用ffmpeg转码的MP4文件需要加载完了才能播放的解决办法

    1.前一段时间做了一个ffmpeg转码MP4的项目,但是转出来的MP4部署在网站上需要把整个视频加载完成才能播放,到处找资料,最后找到解决方案记录于此备忘. FFMpeg转码由此得到的mp4文件中, ...

  10. [转载]为什么有些MP4文件在Chrome浏览器上播放不了?

    http://blog.sina.com.cn/s/blog_6bb7ebcc0101c2ja.html Chrome浏览器支持HTML5,它支持原生播放部分的MP4格式(不用通过Flash等插件). ...

随机推荐

  1. [Linux]常用命令之【nl/sed/awk/wc/xargs/perl】

    nl nl : 在linux系统中用来计算文件中行号. nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补 ...

  2. 计算机常用的快捷键以及常用的Dos命令

    计算机的常用快捷键有哪些? 今天我重温了Java基础的课程,计算机的快捷键大家肯定不陌生.   计算机的常用快捷键 ctrl+c 复制 ctrl+v 粘贴 ctrl+s 保存 ctrl+x 剪切 ct ...

  3. 迁移学习(MEnsA)《MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point Clouds》

    论文信息 论文标题:MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point ...

  4. Nginx配置https并监听80端口重定向到443

    1.进入nginx安装目录,进入config文件夹编辑nginx.conf文件 vim nginx.conf 配置端口  443 listen 443 http2 ssl default_server ...

  5. [Pytorch框架] 3.1 logistic回归实战

    文章目录 3.1 logistic回归实战 3.1.1 logistic回归介绍 3.1.2 UCI German Credit 数据集 3.2 代码实战 import torch import to ...

  6. 你知道Object类和Objects的常用方法吗

    文章目录 Object的常用方法 Objects的常用方法 hashCode hash isNull equals requireNonNull compare nonNull 大家好,Leo又来了! ...

  7. BugKu_never_give_up

    if(!$_GET['id']) { header('Location: hello.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b= ...

  8. OCR 文字检测(Differentiable Binarization --- DB)

    文本检测 基于分割的做法(如蓝色箭头所示): 传统的pipeline使用固定的阈值对于分割后的热力图进行二值化处理 首先,它们设置了固定的阈值,用于将分割网络生成的概率图转换为二进制图像 然后,用一些 ...

  9. ad-hoc实战

    ad-hoc实战 要求:利用Ansible搭建一个简易的作业网站,web端文件上传目录共享至nfs端,nfs的数据同步至backup 环境准备 主机名 主机角色 外网IP 内网IP m01 ansib ...

  10. STL------sort三种比较算子定义

    sort的三种比较算子的定义方式 例:一道细碎的细节模拟题: http://newoj.acmclub.cn/contests/1258/problem/3 1932: 2018蓝桥杯培训-STL应用 ...