一.初始化解复用器
在音视频的解复用的过程中,有一个非常重要的结构体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
标签:stream,frame,nullptr,yuv,mp4,video,pcm,output,audio From: https://www.cnblogs.com/luqman/p/demuxer.html