1. 程式人生 > >最簡單的基於FFmpeg的封裝格式處理 視音訊分離器簡化版(demuxer-simple)

最簡單的基於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=-1const 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=-1for(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上已經更新。