首页 > 技术知识 > 正文

ffmpeg(九)MP4/MP3解封装

前言

解封装包括很多层步骤,包括协议的解析,封装格式的解析。ffmpeg中,本地文件当做file://协议来解析,远程文件采用的传输协议有http(s),rtsp等等。封装格式比如MP4,MOV,TS,MPEG等等。对于ffmpeg来说,只需要调用一个借口函数即可完成解封装的所有步骤,非常简单

解封装相关流程

ffmpeg(九)MP4/MP3解封装

解封装相关函数介绍

1、AVFormatContext重要字段介绍(针对解封装后的)

nb_streams:包含的流的个数 streams:每个流对象,流对象中包括音视频编码参数信息;具体存储在AVStream中AVCodecParameters对象里面 metadata:解封装对应格式的标签信息

AVCodecParameters codec_type:表示数据类型,音频数据或者视频数据 codec_id:音频或者视频采用的编码器 format:音频,采样格式;视频,像素格式 width/height:视频宽高 color_range:视频颜色范围;AVCOL_RANGE_MPEG:代表以BT601,BT709,BT2020等以广播电视系统类的颜色取值范围类型;AVCOL_RANGE_JPEG:代表以电脑等显示器类的颜色取值范围 color_space:颜色空间;比如RGB,YUV,CMYK等等 channel_layout:音频的声道类型 channels:音频的声道数 sample_rate:音频的采样率 frame_size:音频编码器设置的frame size大小

2、int avformat_open_input(AVFormatContext ps,const char url,ff_const59 AVInputFormat fmt,AVDictionary options)

根据给定的url和fmt,options参数进行执行解封装,最终解封装的相关参数填入到ps中去,成功则返回0,失败则返回负数;

3、int avformat_find_stream_info(AVFormatContext *ps,AVDictionary **options)

查找流信息,并尝试读取一个数据包,将编解码参数赋值给AVFormatContext; 成功返回0,失败返回负数

4、int av_dump_info(AVFormatContext ps,int index,const char url,int is_output)

打印解封装后的参数信息

5、AVFormatContext *avformat_alloc_context(void)

创建一个AVFormatContext对象,并赋值为初始值,不包含编码相关参数

6、AVIOContext avio_alloc_context( unsigned char buff, int buffer_size, int write_flag, void opaque, int (read_packet)(void opaque,uint8_t buffer,int buf_size), int (write_packet)(void opaque,uint8_t buffer,int buf_size), int64_t (seek)(void *opaque,int64_t offset,int whence) )

创建一个AVIOContext对象,该对象创建完毕后赋值给AVFormatContext的pb对象。该赋值必须再avformat_open_input()函数调用之前完成;默认情况下,avformat_open_input()函数内部会自己创建pb和AVFormatContext对象,也可以用步骤5和6提前自定义

7、avformat_close_input(AVFormatContext **ps)

关闭AVFormatContext上下文

8、avio_context_free()

关闭AVIOContext上下文

实现代码

头文件

// // demuxer.hpp // video_encode_decode // // Created by apple on 2020/3/24. // Copyright © 2020 apple. All rights reserved. // #ifndef demuxer_hpp #define demuxer_hpp #include <stdio.h> #include <string> #include “CLog.h” extern “C”{ #include <libavutil/imgutils.h> #include <libavformat/avformat.h> #include <libavutil/error.h> #include <libavutil/file.h> #include <libavformat/avio.h> } using namespace std; /** ffmpeg编译完成后支持的解封装器位于libavformat目录下的demuxer_list.c文件中,具体的配置再.configure文件中,如下: * print_enabled_components libavformat/demuxer_list.c AVInputFormat demuxer_list $DEMUXER_LIST * xxxx.mp4对应的解封装器为ff_mov_demuxer */ class Demuxer { public: Demuxer(); ~Demuxer(); void doDemuxer(); }; #endif /* demuxer_hpp */
<

实现文件

// // demuxer.cpp // video_encode_decode // // Created by apple on 2020/3/24. // Copyright © 2020 apple. All rights reserved. // #include “demuxer.hpp” struct buffer_data { uint8_t *ptr; uint8_t *ptr_start; size_t size; ///< buffer size }; Demuxer::Demuxer() { } Demuxer::~Demuxer() { } /** 参考ffmpeg源码file.c的file_seek方法。 * 1、该函数的作用有两个,第一个返回外部缓冲区的大小,类似于lstat()函数;第二个设置外部缓冲读取指针的位置(读取指针的位置 * 相对于缓冲区首地址来说的),类似于lseek()函数 * 2、AVSEEK_SIZE 代表返回外部缓冲区大小 * 3、SEEK_CUR 代表将外部缓冲区读取指针从目前位置偏移offset * 4、SEEK_SET 代表将外部缓冲区读取指针设置到offset指定的偏移 * 5、SEEK_END 代表将外部缓冲区读取指针设置到相对于尾部地址的偏移 * 6、3/4/5情况时返回当前指针位置相对于缓冲区首地址的偏移。offset 的值可以为负数和0。 */ static int64_t io_seek(void* opaque,int64_t offset,int whence) { struct buffer_data *bd = (struct buffer_data*)opaque; if (whence == AVSEEK_SIZE) { return bd->size; } if (whence == SEEK_CUR) { bd->ptr += offset; } else if (whence == SEEK_SET) { bd->ptr = bd->ptr_start+offset; } else if (whence == SEEK_END) { bd->ptr = bd->ptr_start + bd->size + offset; } return (int64_t)(bd->ptr – bd->ptr_start); } /** 参考ffmpeg file.c的file_read()源码 * 1、该函数的意思就是需要从外部读取指定大小buf_size的数据到指定的buf中;这里外部是一个内存缓存 * 2、每次读取完数据后需要将读取指针后移 * 3、如果外部数据读取完毕,则需要返回AVERROR_EOF错误 * 4、读取成功,返回实际读取的字节数 */ static int io_read(void *opaque, uint8_t *buf, int buf_size) { static int total = 0; struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, (int)(bd->ptr_start+bd->size-bd->ptr)); total += buf_size; if (buf_size <= 0) return AVERROR_EOF; // LOGD(“ptr:%p size:%zu buf_size %d total %d\n”, bd->ptr, bd->size,buf_size,total); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; return buf_size; } void Demuxer::doDemuxer() { string curFile(__FILE__); unsigned long pos = curFile.find(“1-video_encode_decode”); if (pos == string::npos) { LOGD(“can not find file”); return; } string recourDir = curFile.substr(0,pos)+”filesources/”; // mdata标签在moov之前 string srcPath = recourDir+”test_1280x720.MP4″; // mdata标签在moov之后 // string srcPath = “/Users/apple/Downloads/Screenrecorder-2020-03-31-16-36-12-749\(0\).mp4”; AVFormatContext *inFmtCtx = NULL; int ret = 0; #define Use_Custom_io 0 #if Use_Custom_io AVIOContext *ioCtx; uint8_t *io_ctx_buffer = NULL,*buffer = NULL; size_t io_ctx_buffer_size = 4096,buffer_size; buffer_data bd = {0}; ret = av_file_map(srcPath.c_str(),&buffer,&buffer_size,0,NULL); if (ret < 0) { LOGD(“av_file_map fail”); return; } bd.ptr = buffer; bd.ptr_start = buffer; bd.size = buffer_size; inFmtCtx = avformat_alloc_context(); if (inFmtCtx == NULL) { LOGD(“avformat_alloc_context fail”); return; } io_ctx_buffer = (uint8_t*)av_mallocz(io_ctx_buffer_size); /** 遇到问题:如果没有指定io_seek函数,对于MP4文件来说,如果mdata在moov标签的后面,采用自定义的AVIOContext时 * 候avformat_find_stream_info() 返回”Could not findcodec parameters for stream 0 ……..: * unspecified pixel formatConsider increasing the value for the analyzeduration and probesize options的错误 * av_read_frame()返回Invalid data found when processing input的错误 * 分析原因:在创建AVIOContext时没有指定seek函数 * 解决方案:因为创建AVIOContext时没有指定io_seek函数并正确实现io_read()和io_seek()相关逻辑;参考如上io_seek和io_read函数 */ ioCtx = avio_alloc_context(io_ctx_buffer,(int)io_ctx_buffer_size,0,&bd,&io_read,NULL,&io_seek); if (ioCtx == NULL) { LOGD(“avio_alloc_context fail”); return; } inFmtCtx->pb = ioCtx; #endif /** 遇到问题:avformat_open_input -1094995529 * 原因分析:这里要打开的文件是MP4文件,而对应的MP4的解封装器没有编译到ffmpeg中,–enable-demuxer=mov打开重新编译即可。 */ /** 参数1:AVFormatContext 指针变量,可以用avformat_alloc_context()先初始化或者直接初始化为NULL * 参数2:文件名路径 * 参数3:AVInputFormat,接封装器对象,传NULL,则根据文件名后缀猜测。非NULL,则由这个指定的AVInputFormat进行解封装 * 参数4:解封装相关参数,传NULL用默认即可 */ ret = avformat_open_input(&inFmtCtx,srcPath.c_str(),NULL,NULL); if (ret < 0) { LOGD(“avformat_open_input fail %d error:%s”,ret,av_err2str(ret)); return; } LOGD(“ddd probesize %d analyzeduration %d”,inFmtCtx->probesize,inFmtCtx->max_analyze_duration); ret = avformat_find_stream_info(inFmtCtx,NULL); if (ret < 0) { LOGD(“avformat_find_stream_info fail %d error:%s”,ret,av_err2str(ret)); return; } LOGD(“begin av_dump_format”); av_dump_format(inFmtCtx,0,NULL,0); LOGD(“end av_dump_format”); // 解析出封装格式中的标签 LOGD(“begin mediadata \n\n”); const AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(inFmtCtx->metadata, “”, tag, AV_DICT_IGNORE_SUFFIX))) { LOGD(“tag key:%s value:%s”,tag->key,tag->value); } LOGD(“end mediadata \n\n”); // 解析出封装格式中的编码相关参数 for (int i = 0;i<inFmtCtx->nb_streams;i++) { AVStream *stream = inFmtCtx->streams[i]; enum AVCodecID cId = stream->codecpar->codec_id; int format = stream->codecpar->format; if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { LOGD(“begin video AVStream \n\n”); LOGD(“code_id %s format “,avcodec_get_name(cId)); const AVPixFmtDescriptor *fmtDes = av_pix_fmt_desc_get((enum AVPixelFormat)format); LOGD(“AVPixFmtDescriptor name %s”,fmtDes->name); LOGD(“width %d height %d”,stream->codecpar->width,stream->codecpar->height); /** 颜色的取值范围 * AVCOL_RANGE_MPEG:代表以BT601,BT709,BT2020等以广播电视系统类的颜色取值范围类型 * AVCOL_RANGE_JPEG:代表以电脑等显示器类的颜色取值范围 */ LOGD(“color_range %d”,stream->codecpar->color_range); /** 所采用的颜色空间,比如RGB,YUV,CMYK等等 */ LOGD(“color_space %s”,av_get_colorspace_name(stream->codecpar->color_space)); LOGD(“video_delay %d\n\n”,stream->codecpar->video_delay); } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { LOGD(“begin audio AVStream \n\n”); LOGD(“code_id %s format “,avcodec_get_name(cId)); LOGD(“samplefmt %s”,av_get_sample_fmt_name((enum AVSampleFormat)format)); LOGD(“channel_layout %s channels %d”,av_get_channel_name(stream->codecpar->channel_layout),stream->codecpar->channels); LOGD(“sample_rate %d”,stream->codecpar->sample_rate); LOGD(“frame_size %d”,stream->codecpar->frame_size); } else { LOGD(“other type”); } } LOGD(“end AVStream\n\n”); AVPacket *packet = av_packet_alloc(); static int num = 0; LOGD(“begin av_read_frame”); while ((ret = av_read_frame(inFmtCtx, packet)) >= 0) { num++; // LOGD(“av_read_frame size %d num %d”,packet->size,num); av_packet_unref(packet); } LOGD(“end av_read_frame ret %s”,av_err2str(ret)); /** 释放内存 */ avformat_close_input(&inFmtCtx); // 对于自定义的AVIOContext,先释放里面的buffer,在释放AVIOContext对象 #if Use_Custom_io if (ioCtx) { av_freep(&ioCtx->buffer); } avio_context_free(&ioCtx); av_file_unmap(buffer, buffer_size); #endif }
<

备注:分别实现了自定义AVIOContext的方式和采用avformat_opent_input()函数默认方式进行解封装,Use_Custom_io为0代表采用默认方式

遇到问题

1、如果没有指定io_seek函数,对于MP4文件来说,如果mdata在moov标签的后面,采用自定义的AVIOContext时候avformat_find_stream_info() 返回”Could not findcodec parameters for stream 0 ……..:unspecified pixel formatConsider increasing the value for the analyzeduration and probesize options的错误av_read_frame()返回Invalid data found when processing input的错误 分析原因:在创建AVIOContext时没有指定seek函数 解决方案:因为创建AVIOContext时没有指定io_seek函数并正确实现io_read()和io_see()相关逻辑;参考如上io_seek和io_read函数

猜你喜欢