如何将mp4文件解复用并且解码为单独的.yuv图像序列以及.pcm音频采样数据?
阅读原文时间:2023年08月16日阅读:1

一.初始化解复用器

  在音视频的解复用的过程中,有一个非常重要的结构体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 "<streams[stream_index];
//find decoder for the stream
dec= avcodec_find_decoder(st->codecpar->codec_id);
if(!dec){
cerr<<"Error:Failed to find codec:"<codecpar))<0){ cerr<<"Error:Failed to copy codec parameters to decoder context."<=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."<=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."<=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="<codec->type==AVMEDIA_TYPE_VIDEO){
write_frame_to_yuv(frame);
}
else{
write_samples_to_pcm(frame,audio_dec_ctx);
}
if(flushing){
cout<<"flushing"<=0){
cout<<"Read packet,pts:"<pts<<",stream:"<stream_index<<",size:"<size<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;jsample_fmt);
if(data_size<0){ cerr<<"Error:failed to calculate data size."<nb_samples;i++){
for(int ch=0;chchannels;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