MP4/MP3解封装ffmpeg(十三)
前言
解封裝包括很多層步驟,包括協議的解析,封裝格式的解析。ffmpeg中,本地文件當做file://協議來解析,遠程文件采用的傳輸協議有http(s),rtsp等等。封裝格式比如MP4,MOV,TS,MPEG等等。對于ffmpeg來說,只需要調用一個借口函數即可完成解封裝的所有步驟,非常簡單
解封裝相關流程
image.png
解封裝相關函數介紹
- 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_ioAVIOContext *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_ioif (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函數
項目代碼
示例地址
示例代碼位于cppsrc目錄下文件
demuxer.hpp
demuxer.cpp
項目下示例可運行于iOS/android/mac平臺,工程分別位于demo-ios/demo-android/demo-mac三個目錄下,可根據需要選擇不同平臺
總結
以上是生活随笔為你收集整理的MP4/MP3解封装ffmpeg(十三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Vue][面试]谈一谈对vue的设计原
- 下一篇: 最简单的基于FFmpeg的解码器-纯净版