首页 > 其他分享 >如何将mp4文件解复用并且解码为单独的.yuv图像序列以及.pcm音频采样数据?

如何将mp4文件解复用并且解码为单独的.yuv图像序列以及.pcm音频采样数据?

时间:2023-06-28 10:14:18浏览次数:39  
标签:stream frame nullptr yuv mp4 video pcm output audio

一.初始化解复用器

  在音视频的解复用的过程中,有一个非常重要的结构体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

相关文章

  • 使用libavcodec将mp3音频文件解码为pcm音频采样数据【[mp3float @ 0x561c1ec49940] He
    一.打开和关闭输入文件和输出文件想要解决上面提到的问题,我们需要对mp3文件的格式有个大致了解,为了方便讲解,我这里画了个示意图:ID3V2包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量。Frame一系列的帧,个数由文件大小和帧长决定ID3V1包含了作者,作曲,专......
  • 如何使用libavcodec将.yuv图像序列编码为.h264的视频码流?
    1.实现打开和关闭输入文件和输出文件的操作点击查看代码//io_data.cppstaticFILE*input_file=nullptr;staticFILE*output_file=nullptr;int32_topen_input_output_files(constchar*input_name,constchar*output_name){if(strlen(input_name)==0||strlen(ou......
  • B站缓存.m4s转成可以观看使用的.mp4
    Step1:寻找缓存文件首先把视频缓存到手机然后找到.m4s文件路径一般是这个(作者用的是安卓系统)一个文件夹就是一个缓存视频找到audio.m4s和video.m4sStep2:下载FFmpegFFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序——百度百科具体......
  • WAV与PCM
    1、PCM格式介绍: PCM(PulseCodeModulation)也被称为脉码编码调制。PCM中的声音数据没有被压缩,如果是单声道的文件,采样数据按时间的先后顺序依次存入。(它的基本组织单位是BYTE(8bit)或WORD(16bit)) 参考文献http://blog.csdn.net/ownwell/article/details/8114121/ 2、WAV格......
  • 基于FFmpeg的音频编码(PCM数据编码成AAC android)
    概述在Android上实现录音,并利用FFmpeg将PCM数据编码成AAC。详细之前做的一个demo,Android录音获取pcm数据(音频原始数据),然后利用FFmpeg将PCM数据编码成AAC。一、准备工作开发环境jdk1.8 EclipseLunaServiceRelease1(4.4.1)运行环境:华为荣耀6(Android4.4)、华为......
  • cmd合并多个ts文件,ffmpeg快速转ts为mp4文件,通过m3u8合并文件
    摘自:https://blog.csdn.net/qq_33957603/article/details/1227638891、如何合并多个ts文件进入目录执行如下cmd命令即可copy/b*.tsnew.ts  2、如何将ts转为mp4ffmpeg下载官网:https://ffmpeg.org/github下载稳定版:https://github.com/BtbN/FFmpeg-Builds/releases下......
  • 如何将word图片粘贴到PHPCMS里面
    ​ 1.编辑器修改(可选)1.1在 ueditor/config.json 中添加代码块    /* 上传word配置 */    "wordActionName":"wordupload",/* 执行上传视频的action名称 */    "wordFieldName":"upfile",/* 提交的视频表单名称 */    "wordPathFormat":"/p......
  • 使用VLC Media Player把mp4转化成mp3
    VLCMediaPlayer是一款免费的工具,它支持Windows、MacOS、Linux、iOS和Android。作为一款免费和跨平台公开资源的多媒体播放器,可播放大多数格式的媒体文件,并且支持转文件。则可以按照下面步骤将您的MP4影片转为MP3音频:步骤1.在计算机上启动VLCMediaPlayer,点击「媒体」并选择......
  • 如何将word公式粘贴到PHPCMS里面
    ​ 自动导入Word图片,或者粘贴Word内容时自动上传所有的图片,并且最终保留Word样式,这应该是Web编辑器里面最基本的一个需求功能了。一般情况下我们将Word内容粘贴到Web编辑器(富文本编辑器)中时,编辑器都无法自动上传图片。需要用户手动一张张上传Word图片。如果只有一张图片还能够接......
  • phpcms系统连接mysql失败
    phpcmsv9安装程序代码对提交的密码中特殊字符(如:&$^!@#)未进行escape转义处理。解决办法:1、修改install/step5.tpl.php127行为:'&dbpw='+escape($('#dbpw').val())2、修改install/step6.tpl.php55行为:vardbpw=escape('<?=$dbpw?>');......