最簡單的基於FFmpeg的封裝格式處理 視音訊分離器簡化版(demuxer-simple)
=====================================================
最簡單的基於FFmpeg的封裝格式處理系列文章列表:
=====================================================
簡介
打算記錄一下基於FFmpeg的封裝格式處理方面的例子。包括了視音訊分離,複用,封裝格式轉換。有關封轉格式轉換的例子在之前的文章:《最簡單的基於FFMPEG的封裝格式轉換器(無編解碼)》中已經有過記錄,不再重複。因此計劃寫3篇文章分別記錄視音訊的複用器(Muxer)和分離器(Demuxer)。其中視音訊分離器(Demuxer)記錄2篇:一篇簡單的,一篇標準的。簡單的版本更適合初學者學習。
本文是第1篇。首先記錄一個基於FFmpeg的視音訊分離器簡單版(Simplest FFmpeg Demuxer Simple)。視音訊分離器(Demuxer)即是將封裝格式資料(例如MKV)中的視訊壓縮資料(例如H.264)和音訊壓縮資料(例如AAC)分離開。如圖所示。在這個過程中並不涉及到編碼和解碼。
本文記錄的程式將一個FLV封裝的檔案(其中視訊編碼為H.264,音訊編碼為MP3)分離成為兩個檔案:一個H.264編碼的視訊碼流檔案,一個MP3編碼的音訊碼流檔案。需要注意的是,本文介紹的是一個簡單版的視音訊分離器(Demuxer)。該分離器的優點是程式碼十分簡單,很好理解。但是缺點是並不適用於一些格式。對於MP3編碼的音訊是沒有問題的。但是在分離MP4/FLV/MKV等一些格式中的AAC編碼的碼流的時候,得到的AAC碼流是不能播放的。原因是儲存AAC資料的AVPacket的data欄位中的資料是不包含7位元組ADTS檔案頭的“砍頭”的資料,是無法直接解碼播放的(當然如果在這些資料前面手工加上7位元組的ADTS檔案頭的話,就可以播放了)。
分離某些封裝格式中的H.264
分離某些封裝格式(例如MP4/FLV/MKV等)中的H.264的時候,需要首先寫入SPS和PPS,否則會導致分離出來的資料沒有SPS、PPS而無法播放。H.264碼流的SPS和PPS資訊儲存在AVCodecContext結構體的extradata中。需要使用ffmpeg中名稱為“h264_mp4toannexb”的bitstream filter處理。有兩種處理方式:
(1)使用bitstream filter處理每個AVPacket(簡單)
把每個AVPacket中的資料(data欄位)經過bitstream filter“過濾”一遍。關鍵函式是av_bitstream_filter_filter()。示例程式碼如下。
AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); while(av_read_frame(ifmt_ctx, &pkt)>=0){ if(pkt.stream_index==videoindex){ av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0); fwrite(pkt.data,1,pkt.size,fp_video); //... } av_free_packet(&pkt); } av_bitstream_filter_close(h264bsfc);
上述程式碼中,把av_bitstream_filter_filter()的輸入資料和輸出資料(分別對應第4,5,6,7個引數)都設定成AVPacket的data欄位就可以了。
需要注意的是bitstream filter需要初始化和銷燬,分別通過函式av_bitstream_filter_init()和av_bitstream_filter_close()。
經過上述程式碼處理之後,AVPacket中的資料有如下變化:
*每個AVPacket的data添加了H.264的NALU的起始碼{0,0,0,1}
*每個IDR幀資料前面添加了SPS和PPS
(2)手工新增SPS,PPS(稍微複雜)
將AVCodecContext的extradata資料經過bitstream filter處理之後得到SPS、PPS,拷貝至每個IDR幀之前。下面程式碼示例了寫入SPS、PPS的過程。
FILE *fp=fopen("test.264","ab");AVCodecContext *pCodecCtx=... unsigned char *dummy=NULL; int dummy_len; AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("h264_mp4toannexb"); av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, 0, 0); fwrite(pCodecCtx->extradata,pCodecCtx-->extradata_size,1,fp); av_bitstream_filter_close(bsfc); free(dummy);
然後修改AVPacket的data。把前4個位元組改為起始碼。示例程式碼如下所示。char nal_start[]={0,0,0,1};memcpy(packet->data,nal_start,4);
經過上述兩步也可以得到可以播放的H.264碼流,相對於第一種方法來說複雜一些。參考文章:使用FFMPEG類庫分離出多媒體檔案中的H.264碼流當封裝格式為MPEG2TS的時候,不存在上述問題。
流程
程式的流程如下圖所示。從流程圖中可以看出,將每個通過av_read_frame()獲得的AVPacket中的資料直接寫入檔案即可。簡單介紹一下流程中各個重要函式的意義:
avformat_open_input():開啟輸入檔案。
av_read_frame():獲取一個AVPacket。
fwrite():根據得到的AVPacket的型別不同,分別寫入到不同的檔案中。
程式碼
下面貼上程式碼:
/** * 最簡單的基於FFmpeg的視音訊分離器(簡化版) * Simplest FFmpeg Demuxer Simple * * 雷霄驊 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程式可以將封裝格式中的視訊碼流資料和音訊碼流資料分離出來。 * 在該例子中, 將FLV的檔案分離得到H.264視訊碼流檔案和MP3 * 音訊碼流檔案。 * * 注意: * 這個是簡化版的視音訊分離器。與原版的不同在於,沒有初始化輸出 * 視訊流和音訊流的AVFormatContext。而是直接將解碼後的得到的 * AVPacket中的的資料通過fwrite()寫入檔案。這樣做的好處是流程比 * 較簡單。壞處是對一些格式的視音訊碼流是不適用的,比如說 * FLV/MP4/MKV等格式中的AAC碼流(上述封裝格式中的AAC的AVPacket中 * 的資料缺失了7位元組的ADTS檔案頭)。 * * * This software split a media file (in Container such as * MKV, FLV, AVI...) to video and audio bitstream. * In this example, it demux a FLV file to H.264 bitstream * and MP3 bitstream. * Note: * This is a simple version of "Simplest FFmpeg Demuxer". It is * more simple because it doesn't init Output Video/Audio stream's * AVFormatContext. It write AVPacket's data to files directly. * The advantages of this method is simple. The disadvantages of * this method is it's not suitable for some kind of bitstreams. For * example, AAC bitstream in FLV/MP4/MKV Container Format(data in * AVPacket lack of 7 bytes of ADTS header). * */#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32//Windowsextern "C"{#include "libavformat/avformat.h"};#else//Linux...#ifdef __cplusplusextern "C"{#endif#include <libavformat/avformat.h>#ifdef __cplusplus};#endif#endif//'1': Use H.264 Bitstream Filter #define USE_H264BSF 1int main(int argc, char* argv[]){ AVFormatContext *ifmt_ctx = NULL; AVPacket pkt; int ret, i; int videoindex=-1,audioindex=-1; const char *in_filename = "cuc_ieschool.flv";//Input file URL const char *out_filename_v = "cuc_ieschool.h264";//Output file URL const char *out_filename_a = "cuc_ieschool.mp3"; av_register_all(); //Input if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) { printf( "Could not open input file."); return -1; } if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) { printf( "Failed to retrieve input stream information"); return -1; } videoindex=-1; for(i=0; i<ifmt_ctx->nb_streams; i++) { if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){ videoindex=i; }else if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){ audioindex=i; } } //Dump Format------------------ printf("\nInput Video===========================\n"); av_dump_format(ifmt_ctx, 0, in_filename, 0); printf("\n======================================\n"); FILE *fp_audio=fopen(out_filename_a,"wb+"); FILE *fp_video=fopen(out_filename_v,"wb+"); /* FIX: H.264 in some container format (FLV, MP4, MKV etc.) need "h264_mp4toannexb" bitstream filter (BSF) *Add SPS,PPS in front of IDR frame *Add start code ("0,0,0,1") in front of NALU H.264 in some container (MPEG2TS) don't need this BSF. */#if USE_H264BSF AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); #endif while(av_read_frame(ifmt_ctx, &pkt)>=0){ if(pkt.stream_index==videoindex){#if USE_H264BSF av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);#endif printf("Write Video Packet. size:%d\tpts:%lld\n",pkt.size,pkt.pts); fwrite(pkt.data,1,pkt.size,fp_video); }else if(pkt.stream_index==audioindex){ /* AAC in some container format (FLV, MP4, MKV etc.) need to add 7 Bytes ADTS Header in front of AVPacket data manually. Other Audio Codec (MP3...) works well. */ printf("Write Audio Packet. size:%d\tpts:%lld\n",pkt.size,pkt.pts); fwrite(pkt.data,1,pkt.size,fp_audio); } av_free_packet(&pkt); }#if USE_H264BSF av_bitstream_filter_close(h264bsfc); #endif fclose(fp_video); fclose(fp_audio); avformat_close_input(&ifmt_ctx); if (ret < 0 && ret != AVERROR_EOF) { printf( "Error occurred.\n"); return -1; } return 0;}
結果
輸入檔案為:cuc_ieschool.flv:FLV封裝格式資料。
輸出檔案為:
cuc_ieschool.h264:H.264視訊碼流資料。
cuc_ieschool.mp3:Mp3音訊碼流資料。
下載
simplest ffmpeg format
專案主頁
CSDN下載地址:工程中包含4個例子:
simplest_ffmpeg_demuxer_simple:視音訊分離器(簡化版)。
simplest_ffmpeg_demuxer:視音訊分離器。
simplest_ffmpeg_muxer:視音訊複用器。
simplest_ffmpeg_remuxer:封裝格式轉換器。
更新-1.1==================================================
修復了以下問題:
(1)Release版本下的執行問題
(2)simplest_ffmpeg_muxer封裝H.264裸流的時候丟失聲音的錯誤
CSDN下載
更新-1.2 (2015.2.13)=========================================
這次考慮到了跨平臺的要求,調整了原始碼。經過這次調整之後,原始碼可以在以下平臺編譯通過:
VC++:開啟sln檔案即可編譯,無需配置。
cl.exe:開啟compile_cl.bat即可命令列下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本里面的引數。編譯命令如下。
::VS2010 Environmentcall "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"::[email protected] INCLUDE=include;%INCLUDE%::[email protected] LIB=lib;%LIB%::compile and linkcl simplest_ffmpeg_demuxer_simple.cpp /link avcodec.lib avformat.lib avutil.lib ^avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF
MinGW:MinGW命令列下執行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
g++ simplest_ffmpeg_demuxer_simple.cpp -g -o simplest_ffmpeg_demuxer_simple.exe \-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil
GCC:Linux或者MacOS命令列下執行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
gcc simplest_ffmpeg_demuxer_simple.cpp -g -o simplest_ffmpeg_demuxer_simple.out \-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil
PS:相關的編譯命令已經儲存到了工程資料夾中
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8445303
SourceForge上已經更新。