天天看點

ffmpeg開發指南

FFmpeg是一個集錄制、轉換、音/視訊編碼解碼功能為一體的完整的開源解決方案。FFmpeg的開發是基于Linux作業系統,但是可以在大多數作業系統中編譯和使用。FFmpeg支援MPEG、DivX、MPEG4、AC3、DV、FLV等40多種編碼,AVI、MPEG、OGG、Matroska、ASF等90多種解碼.TCPMP, VLC, MPlayer等開源播放器都用到了FFmpeg。

    一、ffmpeg介紹

ffmpeg軟體包經編譯過後将生成三個可執行檔案,ffmpeg,ffserver,ffplay。其中ffmpeg用于對媒體檔案進行處理,ffserver是一個http的流媒體伺服器,ffplay是一個基于SDL的簡單點傳播放器。

ffmpeg中有五個庫檔案,libavcodec,libavformat,libavutil,libswscale,libpostproc,其中庫libavcodec,libavformat用于對媒體檔案進行處理,如格式的轉換;libavutil是一個通用的小型函數庫,該庫中實作了CRC校驗碼的産生,128位整數數學,最大公約數,整數開方,整數取對數,記憶體配置設定,大端小端格式的轉換等功能;libswscale,libpostproc暫時不知道何用。 

   ffmpeg下載下傳

tar zvxf subversion-1.3.2.tar.gz

cd subversion-1.3.2

./configure --with-apr=/usr/local/apr-httpd--with-apr-util=/usr/local/apr-util-httpd/

make 

make install

如果安裝了FC6,它已經帶了svn,不用裝了。

ffmpeg的下載下傳:我們就可以通過svn指令擷取最新的ffmpeg,指令如下:

svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg

windows下編譯ffmpeg源代碼

<a href="http://blog.csdn.net/jszj/article/details/4028716" target="_blank">http://blog.csdn.net/jszj/article/details/4028716</a>

該網站中的FFMPEG分為3個版本:Static,Shared,Dev。

前兩個版本可以直接在指令行中使用,他們的差別在于:Static裡面隻有3個應用程式:ffmpeg.exe,ffplay.exe,ffprobe.exe,每個exe的體積都很大,相關的Dll已經被編譯到exe裡面去了。Shared裡面除了3個應用程式:ffmpeg.exe,ffplay.exe,ffprobe.exe之外,還有一些Dll,比如說avcodec-54.dll之類的。Shared裡面的exe體積很小,他們在運作的時候,到相應的Dll中調用功能。

Dev版本是用于開發的,裡面包含了庫檔案xxx.lib以及頭檔案xxx.h,這個版本不包含exe檔案。

  二、ffmpeg編碼解碼

先給出幾個概念,以在後面的分析中友善了解

Container:在音視訊中的容器,一般指的是一種特定的檔案格式,裡面指明了所包含的

音視訊,字幕等相關資訊

Stream:這個詞有些微妙,很多地方都用到,比如TCP,SVR4系統等,其實在音視訊,你

可以了解為單純的音頻資料或者視訊資料等

Frames:這個概念不是很好明确的表示,指的是Stream中的一個資料單元,要真正對這

個概念有所了解,可能需要看一些音視訊編碼解碼的理論知識

Packet:是Stream的raw資料

Codec:Coded + Decoded

其實這些概念在在FFmpeg中都有很好的展現,我們在後續分析中會慢慢看到。

基本上來說,處理視訊和音頻流是很容易的:

10 從video.avi檔案中打開視訊流video_stream

20 從視訊流中讀取包到幀中

30 如果這個幀還不完整,跳到20

40 對這個幀進行一些操作

50 跳回到20

在這個程式中使用ffmpeg來處理多種媒體是相當容易的,雖然很多程式可能在對幀進行操作的時候非常的複雜。是以在這篇指導中,我們将打開一個檔案,讀取裡面的視訊流,而且我們對幀的操作将是把這個幀寫到一個PPM檔案中。

代碼:

#include &lt;inttypes.h&gt;  

#include &lt;stdint.h&gt;  

#ifdef __cplusplus  

extern "C"   

{  

    #include "libavutil/avutil.h"  

    #include "libavcodec/avcodec.h"  

    #include "libavformat/avformat.h"  

    #include "libavdevice/avdevice.h"  

    #include "libswscale/swscale.h"  

}  

#endif  

#pragma comment(lib,"avutil.lib")  

#pragma comment(lib,"avcodec.lib")  

#pragma comment(lib,"avformat.lib")  

#pragma comment(lib,"swscale.lib")  

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame);  

int img_convert2(AVPicture *dst, int dst_pix_fmt,AVPicture *src, int src_pix_fmt,int src_width, int src_height);  

int main(int argc, char* argv[])  

    av_register_all();  

    AVFormatContext *pFormatCtx;  

    // Open video file  

    if(av_open_input_file(&amp;pFormatCtx, argv[1], NULL, 0, NULL)!=0)  

        return -1; // Couldn't open file  

    if(av_find_stream_info(pFormatCtx)&lt;0)      

        return -1; // Couldn't find stream information  

    int i = 0;    

    int videoStream=-1;  

    AVCodecContext *pCodecCtx = NULL;  

    for(i=0; i &lt; pFormatCtx-&gt;nb_streams; i++)  

    {  

        if(pFormatCtx-&gt;streams[i]-&gt;codec-&gt;codec_type==CODEC_TYPE_VIDEO)  

        {  

            videoStream = i;  

            break;  

        }  

    }  

    if(videoStream==-1)       

        return -1; // Didn't find a video stream  

    // Get a pointer to the codec context for the video stream  

    pCodecCtx=pFormatCtx-&gt;streams[videoStream]-&gt;codec;  

    AVCodec *pCodec = NULL;  

    // Find the decoder for the video stream  

    pCodec=avcodec_find_decoder(pCodecCtx-&gt;codec_id);  

    if(pCodec==NULL)  

    {     

        fprintf(stderr, "Unsupported codec!\n");      

        return -1; // Codec not found     

    // Open codec     

    if(avcodec_open(pCodecCtx, pCodec)&lt;0)      

        return -1; // Could not open codec  

    AVFrame *pFrame,*pFrameRGB;   

    // Allocate video frame  

    pFrame=avcodec_alloc_frame();  

    pFrameRGB=avcodec_alloc_frame();      

    if(pFrameRGB==NULL)   

        return -1;  

    uint8_t *buffer;      

    int numBytes;     

    // Determine required buffer size and allocate buffer  

    numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx-&gt;width,pCodecCtx-&gt;height);  

    buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));  

    avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx-&gt;width, pCodecCtx-&gt;height);  

    int frameFinished;  

    AVPacket packet;  

    i=0;  

    while(av_read_frame(pFormatCtx, &amp;packet)&gt;=0)  

        // Is this a packet from the video stream?        

        if(packet.stream_index==videoStream)  

        {     

            // Decode video frame     

            avcodec_decode_video(pCodecCtx, pFrame, &amp;frameFinished,packet.data, packet.size);  

            // Did we get a video frame?      

            if(frameFinished)  

            {     

                // Convert the image from its native format to RGB    

                img_convert2((AVPicture *)pFrameRGB,PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx-&gt;pix_fmt,      

                    pCodecCtx-&gt;width, pCodecCtx-&gt;height);  

                // Save the frame to disk         

                if(++i&lt;=100)   

                    SaveFrame(pFrameRGB, pCodecCtx-&gt;width,pCodecCtx-&gt;height, i);  

            }  

        // Free the packet that was allocated by av_read_frame  

        av_free_packet(&amp;packet);      

    // Free the RGB image     

    av_free(buffer);  

    av_free(pFrameRGB);  

    // Free the YUV frame     

    av_free(pFrame);  

    // Close the codec    

    avcodec_close(pCodecCtx);  

    av_close_input_file(pFormatCtx);  

    return 0;  

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)  

    FILE *pFile;  

    char szFilename[32];  

    int y;  

    // Open file  

    sprintf(szFilename, "frame%d.ppm", iFrame);  

    pFile=fopen(szFilename, "wb");  

    if(pFile==NULL)  

        return;  

    // Write header  

    fprintf(pFile, "P6\n%d %d\n255\n", width, height);  

    // Write pixel data  

    for(y=0; y&lt;height; y++)  

        fwrite(pFrame-&gt;data[0]+y*pFrame-&gt;linesize[0], 1, width*3, pFile);  

    // Close file  

    fclose(pFile);  

int img_convert2(AVPicture *dst, int dst_pix_fmt,                 

                AVPicture *src, int src_pix_fmt,                  

                int src_width, int src_height)                

    int w;    

    int h;  

    SwsContext *pSwsCtx;  

    w = src_width;    

    h = src_height;   

    pSwsCtx = sws_getContext(w, h, src_pix_fmt, w, h, dst_pix_fmt,SWS_BICUBIC, NULL, NULL, NULL);  

    sws_scale(pSwsCtx,src-&gt;data, src-&gt;linesize,0, h, dst-&gt;data, dst-&gt;linesize);  

    //這裡釋放掉pSwsCtx的記憶體  

    sws_freeContext(pSwsCtx);  

    return 0;     

三、ffmpeg架構代碼

FFmpeg主目錄下主要有libavcodec、libavformat和libavutil等子目錄。其中libavcodec用于存放各個encode/decode子產品,libavformat用于存放muxer/demuxer子產品,libavutil用于存放記憶體操作等常用子產品。

以flash movie的flv檔案格式為例, muxer/demuxer的flvenc.c和flvdec.c檔案在libavformat目錄下,encode/decode的mpegvideo.c和h263de.c在libavcodec目錄下。

muxer/demuxer與encoder/decoder定義與初始化

muxer/demuxer和encoder/decoder在FFmpeg中的實作代碼裡,有許多相同的地方,而二者最大的差别是muxer和demuxer分别是不同的結構AVOutputFormat與AVInputFormat,而encoder和decoder都是用的AVCodec結構。

muxer/demuxer和encoder/decoder在FFmpeg中相同的地方有:

二者都是在main()開始的av_register_all()函數内初始化的。

二者都是以連結清單的形式儲存在全局變量中的。

muxer/demuxer是分别儲存在全局變量AVOutputFormat *first_oformat與AVInputFormat *first_iformat中的。

encoder/decoder都是儲存在全局變量AVCodec *first_avcodec中的。

二者都用函數指針的方式作為開放的公共接口。

demuxer開放的接口有:

int (*read_probe)(AVProbeData *);

int(*read_header)(struct AVFormatContext *,AVFormatParameters *ap);

int (*read_packet)(struct AVFormatContext*, AVPacket *pkt);

int (*read_close)(struct AVFormatContext*);

int (*read_seek)(struct AVFormatContext *,int stream_index, int64_t timestamp, int flags);

muxer開放的接口有:

int (*write_header)(struct AVFormatContext *);

int (*write_packet)(struct AVFormatContext *, AVPacket*pkt);

int (*write_trailer)(struct AVFormatContext *);

encoder/decoder的接口都是一樣的,隻不過二者分别隻實作encoder和decoder函數:

int (*init)(AVCodecContext *);

int (*encode)(AVCodecContext *, uint8_t *buf, intbuf_size, void *data);

int (*close)(AVCodecContext *);

int (*decode)(AVCodecContext *, void *outdata, int*outdata_size, uint8_t *buf, int buf_size);

仍以flv檔案為例來說明muxer/demuxer的初始化。

在libavformat/allformats.c檔案的av_register_all(void)函數中,通過執行

REGISTER_MUXDEMUX(FLV, flv);

将支援flv 格式的flv_muxer與flv_demuxer變量分别注冊到全局變量first_oformat與first_iformat連結清單的最後位置。

其中flv_muxer在libavformat/flvenc.c中定義如下:

AVOutputFormat flv_muxer = {

"flv",

"flv format",

"video/x-flv",

sizeof(FLVContext),

#ifdef CONFIG_LIBMP3LAME

CODEC_ID_MP3,

#else // CONFIG_LIBMP3LAME

CODEC_ID_NONE,

CODEC_ID_FLV1,

flv_write_header,

flv_write_packet,

flv_write_trailer,

.codec_tag= (const AVCodecTag*[]){flv_video_codec_ids,flv_audio_codec_ids, 0},

}

AVOutputFormat結構的定義如下:

typedef struct AVOutputFormat {

const char *name;

const char *long_name;

const char *mime_type;

const char *extensions; /**&lt; comma separated filenameextensions */

/** size of private data so that it can be allocated inthe wrapper */

int priv_data_size;

/* output support */

enum CodecID audio_codec; /**&lt; default audio codec */

enum CodecID video_codec; /**&lt;default video codec */

/** can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER,AVFMT_GLOBALHEADER */

int flags;

/** currently only used to set pixel format if notYUV420P */

int (*set_parameters)(struct AVFormatContext *,AVFormatParameters *);

int (*interleave_packet)(struct AVFormatContext *,AVPacket *out, AVPacket *in, int flush);

/**

* list of supported codec_id-codec_tag pairs, ordered by"better choice first"

* the arrays are all CODEC_ID_NONE terminated

*/

const struct AVCodecTag **codec_tag;

/* private fields */

struct AVOutputFormat *next;

} AVOutputFormat;

由AVOutputFormat結構的定義可知,flv_muxer變量初始化的第一、第二個成員分别為該muxer的名稱與長名稱,第三、第四個成員為所對應MIMIEType和字尾名,第五個成員是所對應的私有結構的大小,第六、第七個成員為所對應的音頻編碼和視訊編碼類型ID,接下來就是三個重要的接口函數,該muxer的功能也就是通過調用這三個接口實作的。

flv_demuxer在libavformat/flvdec.c中定義如下, 與flv_muxer類似,在這兒主要也是設定了5個接口函數,其中flv_probe接口用途是測試傳入的資料段是否是符合目前檔案格式,這個接口在比對目前demuxer的時候會用到。

AVInputFormat flv_demuxer = {

0,

flv_probe,

flv_read_header,

flv_read_packet,

flv_read_close,

flv_read_seek,

.extensions = "flv",

.value = CODEC_ID_FLV1,

};

在上述av_register_all(void)函數中通過執行libavcodec/allcodecs.c檔案裡的avcodec_register_all(void)函數來初始化全部的encoder/decoder。

因為不是每種編碼方式都支援encode和decode,是以有以下三種注冊方式:

#define REGISTER_ENCODER(X,x) /

if(ENABLE_##X##_ENCODER)register_avcodec(&amp;x##_encoder)

#define REGISTER_DECODER(X,x) /

if(ENABLE_##X##_DECODER)register_avcodec(&amp;x##_decoder)

#define REGISTER_ENCDEC(X,x)

REGISTER_ENCODER(X,x);REGISTER_DECODER(X,x)

如支援flv的flv_encoder和flv_decoder變量就分别是在libavcodec/mpegvideo.c和libavcodec/h263de.c中建立的。

目前muxer/demuxer的比對

在FFmpeg的檔案轉換過程中,首先要做的就是根據傳入檔案和傳出檔案的字尾名比對合适的demuxer和muxer。

比對上的demuxer和muxer都儲存在如下所示,定義在ffmpeg.c裡的全局變量file_iformat和file_oformat中:

static AVInputFormat *file_iformat;

static AVOutputFormat *file_oformat;

1. demuxer比對

在libavformat/utils.c中的static AVInputFormat *av_probe_input_format2(AVProbeData*pd, int is_opened, int *score_max)函數用途是根據傳入的probe data資料,依次調用每個demuxer的read_probe接口,來進行該demuxer是否和傳入的檔案内容比對的判斷。其調用順序如下:

void parse_options(int argc, char **argv,const OptionDef *options)

static void opt_input_file(const char *filename)

static void opt_input_file(const char*filename)

int av_open_input_file(…… )

AVInputFormat *av_probe_input_format(AVProbeData *pd, intis_opened)

static AVInputFormat*av_probe_input_format2(……)

opt_input_file函數是在儲存在const OptionDef options[]數組中,用于void parse_options(int argc, char **argv,const OptionDef *options)中解析argv裡的“-i” 參數,也就是輸入檔案名時調用的。

2. muxer比對

與demuxer的比對不同,muxer的比對是調用guess_format函數,根據main( ) 函數的argv裡的輸出檔案字尾名來進行的。

void parse_arg_file(const char *filename)

static void opt_output_file(const char*filename)

AVOutputFormat *guess_format(const char*short_name, const char *filename,

const char *mime_type)

目前encoder/decoder的比對

在main( )函數中除了解析傳入參數并初始化demuxer與muxer的parse_options( )函數以外,其他的功能都是在av_encode( )函數裡完成的。

在libavcodec/utils.c中有如下二個函數。

AVCodec *avcodec_find_encoder(enum CodecID id)

AVCodec *avcodec_find_decoder(enum CodecID id)

他們的功能就是根據傳入的CodecID,找到比對的encoder和decoder。

在av_encode( )函數的開頭,首先初始化各個AVInputStream和AVOutputStream,然後分别調用上述二個函數,并将比對上的encoder與decoder分别儲存在AVInputStream-&gt;AVStream*st-&gt;AVCodecContext *codec-&gt;struct AVCodec *codec與AVOutputStream-&gt;AVStream*st-&gt;AVCodecContext *codec-&gt;struct AVCodec *codec變量中。

其他主要資料結構

1. AVFormatContext

AVFormatContext是FFMpeg格式轉換過程中實作輸入和輸出功能、儲存相關資料的主要結構。每一個輸入和輸出檔案,都在如下定義的指針數組全局變量中有對應的實體。

static AVFormatContext *output_files[MAX_FILES];

static AVFormatContext *input_files[MAX_FILES];

對于輸入和輸出,因為共用的是同一個結構體,是以需要分别對該結構中如下定義的iformat或oformat成員指派。

struct AVInputFormat *iformat;

struct AVOutputFormat *oformat;

對一個AVFormatContext來說,二個成員不能同時有值,即一個AVFormatContext不能同時含有demuxer和muxer。

在main( )函數開頭的parse_options( )函數中找到了比對的muxer和demuxer之後,根據傳入的argv參數,初始化每個輸入和輸出的AVFormatContext結構,并儲存在相應的output_files和input_files指針數組中。

在av_encode( )函數中,output_files和input_files是作為函數參數傳入後,在其他地方就沒有用到了。

2. AVCodecContext

儲存AVCodec指針和與codec相關的資料,如video的width、height,audio的sample rate等。AVCodecContext中的codec_type,codec_id二個變量對于encoder/decoder的比對來說,最為重要。

enum CodecType codec_type; /* see CODEC_TYPE_xxx */

enum CodecID codec_id; /* see CODEC_ID_xxx */

如上所示,codec_type儲存的是CODEC_TYPE_VIDEO,CODEC_TYPE_AUDIO等媒體類型,

codec_id儲存的是CODEC_ID_FLV1,CODEC_ID_VP6F等編碼方式。

以支援flv格式為例,在前述的av_open_input_file(…… ) 函數中,比對到正确的AVInputFormatdemuxer後,通過av_open_input_stream( )函數中調用AVInputFormat的read_header接口來執行flvdec.c中的flv_read_header( )函數。在flv_read_header( )函數内,根據檔案頭中的資料,建立相應的視訊或音頻AVStream,并設定AVStream中AVCodecContext的正确的codec_type值。codec_id值是在解碼過程中flv_read_packet( )函數執行時根據每一個packet頭中的資料來設定的。

3. AVStream

AVStream結構儲存與資料流相關的編解碼器,資料段等資訊。比較重要的有如下二個成員:

AVCodecContext *codec; /**&lt; codec context */

void *priv_data;

其中codec指針儲存的就是上節所述的encoder或decoder結構。priv_data指針儲存的是和具體編解碼流相關的資料,如下代碼所示,在ASF的解碼過程中,priv_data儲存的就是ASFStream結構的資料。

AVStream *st;

ASFStream *asf_st;

… …

st-&gt;priv_data = asf_st;

4. AVInputStream/ AVOutputStream

根據輸入和輸出流的不同,前述的AVStream結構都是封裝在AVInputStream和 AVOutputStream結構中,在av_encode( )函數中使用。

AVInputStream中還儲存的有與時間有關的資訊。

AVOutputStream中還儲存有與音視訊同步等相關的資訊。

5. AVPacket

AVPacket結構定義如下,其是用于儲存讀取的packet資料。

typedef struct AVPacket {

int64_t pts; ///&lt; presentation time stamp in time_baseunits

int64_t dts; ///&lt; decompression time stamp intime_base units

uint8_t *data;

int size;

int stream_index;

int duration; ///&lt; presentation duration in time_baseunits (0 if not available)

void (*destruct)(struct AVPacket *);

void *priv;

int64_t pos; ///&lt; byte position in stream, -1 ifunknown

} AVPacket;

在av_encode( )函數中,調用AVInputFormat的(*read_packet)(struct AVFormatContext *,AVPacket *pkt);接口,讀取輸入檔案的一幀資料儲存在目前輸入AVFormatContext的AVPacket成員中。

av_encode函數主要流程

av_encode( )函數是FFMpeg中最重要的函數,編解碼和輸出等大部分功能都在此函數内完成,是以有必要較長的描述一下這個函數的主要流程。

1. input streams initializing

2. output streams initializing

3. encoders and decoders initializing

4. set meta data information from input file if required.

5. write output files header

6. loop of handling each frame

a. read frame from input file:

b. decode frame data

c. encode new frame data

d. write new frame to output file

7. write output files trailer

8. close each encoder and decoder

參考資料:

<a href="http://ffmpeg.org/" target="_blank">http://ffmpeg.org/</a>

<a href="http://www.ffmpeg.com.cn/" target="_blank">www.ffmpeg.com.cn</a>

<a href="http://www.libsdl.org/" target="_blank">http://www.libsdl.org/</a>

<a href="http://blog.csdn.net/siyingruoshui/article/details/6992029" target="_blank">FFmpeg tutorial</a>

英文原文位址:http://www.dranger.com/ffmpeg/

轉載翻譯部落格位址:http://blog.sina.com.cn/s/blog_46dc65a90100a91b.html

文中涉及的源碼打包位址:http://www.dranger.com/ffmpeg/ffmpegsource.tar.gz

<a href="http://blog.csdn.net/liangkaiming/article/details/5798898" target="_blank">FFmpeg介紹及參數詳細說明</a>

<a href="http://blog.csdn.net/liangkaiming/article/details/5798898" target="_blank">http://blog.csdn.net/liangkaiming/article/details/5798898</a>

ffmpeg編譯及使用

<a href="http://lvzun.iteye.com/blog/706121" target="_blank">http://lvzun.iteye.com/blog/706121</a>

FFMPEG+SDL最新庫檔案和源檔案

<a href="http://download.csdn.net/detail/sonicx24/3740122" target="_blank">http://download.csdn.net/detail/sonicx24/3740122</a>

<a href="http://blog.csdn.net/qiuchangyong/article/details/6623901" target="_blank">http://blog.csdn.net/qiuchangyong/article/details/6623901</a>

讓ffmpeg支援RMVB解碼

<a href="http://hi.baidu.com/jingxshi/blog/item/7aedb3d94e4818e539012fe3.html" target="_blank">http://hi.baidu.com/jingxshi/blog/item/7aedb3d94e4818e539012fe3.html</a>

<a href="http://blog.simophin.net/?p=825" target="_blank">使用ffmpeg進行攝像頭捕獲</a>

http://blog.simophin.net/?p=825&amp;cpage=1

繼續閱讀