1. 程式人生 > >嵌入式Linux下基於FFmpeg的視訊硬體編解碼

嵌入式Linux下基於FFmpeg的視訊硬體編解碼

摘要: 對FFmpeg多媒體解決方案中的視訊編解碼流程進行研究。結合對S3C6410處理器視訊硬體編解碼方法的分析,闡述了嵌入式Linux下基於FFmpeg的H.264視訊硬體編解碼在S3C6410處理器上的實現方法,為嵌入式多媒體開發提供參考。

引言

目前,智慧手機、PDA和平板電腦等越來越多的嵌入式裝置支援高清視訊採集和播放功能,高清視訊的採集或播放功能正廣泛用於遊戲裝置、監控裝置、視訊會議裝置和數字網路電視等嵌入式系統中。這些功能的實現建立在高效能視訊硬體編解碼技術基礎之上。本文闡述了基於FFmpeg的H.264視訊硬體編解碼在S3C6410處理器上的實現方法,為數字娛樂、視訊監控和視訊通訊系統開發過程中的高清視訊硬體編解碼的實現提供參考。

FFmpeg[1]是一個開源免費跨平臺的視訊和音訊流方案,屬於自由軟體。它包含非常先進的音訊/視訊編解碼庫libavcodec,提供了錄製、轉換以及流化音視訊的完整解決方案。FFmpeg支援MPEG4、FLV等40多種編碼,以及AVI、ASF等90多種解碼。目前國內較為流行的播放器暴風影音和國外較為流行的Mplayer在音訊/視訊編解碼方面都用到了FFmpeg。

S3C6410[2]是三星公司推出的應用處理器晶片,基於ARM11架構,主頻最高可達800 MHz。它具有多媒體硬體加速功能,其中包括大於30 fps的MPEG4 SP、H.264/263 BP和VC1(WMV9)多種視訊硬體編解碼,可用於手機、平板電腦和遊戲機等手持

移動裝置和其他高效能嵌入式裝置。國產手機魅族M8的處理器使用的就是S3C6410。

雖然FFmpeg提供了簡單的應用程式程式設計介面(API),可以很方便地實現多種格式的視訊軟體編解碼[3],但是軟體編解碼在處理複雜視訊編解碼(如H.264)時無法運用到處理速度不快、記憶體空間不多的嵌入式環境中。為了在資源有限的嵌入式環境下使用FFmpeg實現複雜視訊編解碼,下面在分析FFmpeg視訊編碼流程和S3C6410處理器視訊編解碼方法的基礎上,闡述嵌入式Linux作業系統下基於FFmpeg的H.264硬體編解碼在S3C6410處理器上的實現方法。

1 FFmpeg視訊編解碼流程

FFmpeg主要有encode/decode、muxer/demuxer和記憶體操作3個模組。encode/decode模組用於音視訊的編碼和解碼,存放在libavcodec子目錄中;muxer/demuxer模組用於音訊和視訊的合併與分離(也稱混合器模組),存放在libavformat目錄中;記憶體等常用模組存放於libavutil目錄中。下面以解碼過程為例分析FFmpeg視訊編解碼流程。

解碼基本流程共分4步:

① 註冊所有可能用到的編解碼器和混合器。av_register_all(void)函式中通過執行 REGISTER_MUXDEMUX(X,x)和REGISTER_ENCDEC(X,x),把所有FFmpeg支援的混合器和編解碼器相關資訊以鏈式的結構存放在記憶體中。

② 開啟視訊檔案。av_open_input_file(AVFormatContext **ic_ptr,const char *filename,AVInputFormat *fmt,int buf_size,AVFormatParameters *ap)函式中偵測檔案的格式,根據檔案格式從鏈式的混合器中找到相對應的混合器(demuxer)並分離出視訊資訊。

③ 獲取視訊資訊。通過av_find_stream_info(AVFormatContext *ic)函式獲取視訊格式。根據視訊格式,在鏈式的視訊解碼器中找到相應的視訊解碼器,並通過avcodec_open(AVCodecContext *avctx,AVCodec *codec)函式將解碼器開啟用於下一步視訊的解碼。

④ 解碼一幀視訊,通過 avcodec_decode_video(AVCodecContext *avctx,AVFrame *picture,int *got_picture_ptr,const uint8_t *buf,int buf_size)函式解碼一幀視訊。

FFmpeg的編碼過程與解碼過程類似,不同的是第3步根據要求編碼的格式在鏈式的視訊編碼器中找到相應的視訊編碼器,並執行編碼過程。

通過以上對FFmpeg視訊編解碼流程分析可以知道,為了在FFmpeg中新增自定義的視訊編解碼器,並在程式執行時使用這個編解碼器,關鍵在於如下兩點:

① 根據FFmpeg對編解碼器的描述,實現自定義編解碼器。

② 通過REGISTER_ENCDEC(X,x)函式將自定義的視訊編解碼器新增到視訊編解碼器鏈中。在獲取視訊資訊時,保證需要編碼或解碼的視訊能找到視訊編解碼器鏈中自定義的視訊編解碼器。

2 S3C6410處理器視訊編解碼方法

S3C6410視訊編解碼軟體架構[4]如圖1所示。底層為作業系統空間,上層為使用者空間,視訊編解碼器通過驅動和作業系統以裝置檔案的形式使用,使用的方法和普通檔案一樣,包括檔案開啟和關閉、檔案讀寫和輸入/輸出控制(ioctl,input/output control)。

圖1 S3C6410視訊編解碼軟體架構

具體操作方法如下:

① 通過open函式開啟編解碼器裝置檔案;

② 使用mmap方法在使用者空間和驅動空間之間對映輸入/輸出快取空間,這樣做的好處是可以快速進行資料輸入/輸出;

③ 通過ioctl裝置編解碼引數,初始化編解碼器;

④ 輸入資料,通過ioctl執行編解碼過程,輸出資料;

⑤ 通過close方法關閉編解碼器裝置檔案。

值得注意的是,無論編碼還是解碼,處理的資料都是以一幀幀的形式操作的,所以第4步是一個不斷迴圈的過程,直到所有資料處理完成。另外,雖然編解碼器以裝置檔案的形式使用,但是它不能使用標準的檔案讀寫操作,檢視編解碼的裝置驅動可以發現,其檔案讀寫函式是空的,這一點三星公司的開發文件並沒有說明。

3 H.264硬體編解碼實現

FFmpeg的H.264硬體編解碼[5]實現就是自定義一個視訊編解碼器,加入到FFmpeg庫中。這個視訊編解碼器使用S3C6410處理視訊硬體編解碼功能來實現H.264的視訊編碼和解碼過程,這樣使用FFmpeg庫的多媒體程式可以用訪問FFmpeg其他編解碼器一樣的方法使用這個自定義的編解碼器。新增自定義編解碼器的關鍵是根據FFmpeg中對編解碼的描述定義編解碼器,並實現定義中的相關函式。

在libavcodec/avcodec.h中的AVCodec結構體是定義FFmpeg編解碼器的關鍵結構體,包括編解碼器的名字、型別(聲音/視訊)、編解碼器的識別號(CodecID)、支援格式和一些用於初始化、編碼、解碼和關閉的函式指標。

typedef struct AVCodec {

const char *name;

enum CodecType type;

enum CodecID id;

int priv_data_size;

int (*init)(AVCodecContext *);

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

int (*close)(AVCodecContext *);

int (*decode)(AVCodecContext *,void *outdata,int *outdata_size,

uint8_t *buf,int buf_size);

int capabilities;

struct AVCodec *next;

void (*flush)(AVCodecContext *);

const AVRational *supported_framerates;

const enum PixelFormat *pix_fmts;

} AVCodec;

H.264硬體編解碼器定義如下:

AVCodec s3cx264_encoder = {

.name="s3cx264",

.type=AVMEDIA_TYPE_VIDEO,

.id=CODEC_ID_H264,

.init=X264_init,

.encode=X264_frame,

.decode=X264_decode,

.close=X264_close,

};

解碼器的名字為s3cx264,型別為視訊。CodecID為H264,表示這個解碼器用於H.264視訊編解碼。初始化、編碼、解碼和關閉函式指標分別指向X264_init、X264_frame、X264_decodec和X264_close函式。

新增s3cx264編解碼器到編解器鏈中,關鍵是通過修改libavcodec/allcodecs.c檔案實現,修改如下:

REGISTER_ENCDEC (ASV1,asv1);

REGISTER_ENCDEC (S3CX264,s3cx264);

//新增s3cx264編解碼器

REGISTER_ENCDEC (ASV2,asv2);

這樣,在程式執行時呼叫av_register_all(void)函式後,就可以把自定義的編解碼器s3cx264新增到FFmpeg存放在記憶體中的解編碼器鏈中。值得提出的是,對同一個視訊格式FFmpeg有多個編解碼器與之相對應。如H.264格式的視訊,FFmpeg本身就帶有對應的軟解碼器,現在添加了硬解碼器,為了避免不確定是哪一個解碼器在執行,可以把自定義的硬體編解碼器在註冊時放在註冊過程的最前面,這樣編解碼器在新增到解編器鏈中時就會放在靠前的位置,查詢時就可以優於軟體解碼器找到硬解碼器。

把硬體編解碼器s3cx264註冊到編解碼器鏈後,還要完成X264_init、X264_frame、X264_decodec和X264_close函式,編解碼器才能正常工作。以下結合前面對S3C6410視訊編解碼過程的分析,以編碼為例詳細闡述實現過程。

定義X264Context結構體,儲存裝置檔案描述符、編碼引數和輸入/輸出地址等資訊,用於FFmpeg模組間資料的傳遞:

typedef struct X264Context {

int dev_fd;

uint8_t *addr;

s3c_mfc_enc_init_arg_t enc_init;

s3c_mfc_enc_exe_arg_t enc_exe;

s3c_mfc_get_buf_addr_arg_t get_buf_addr;

uint8_t *in_buf,*out_buf;

AVFrame out_pic;

} X264Context;

X264_init實現的是編碼器初始化過程, 用於編碼器裝置檔案的開啟、記憶體空間的對映、編碼引數設定和獲取編解碼資料輸入/輸出地址。

static av_cold int X264_init(AVCodecContext *avctx){

X264Context *x4 = avctx>priv_data;

//開啟編碼器裝置檔案

x4>dev_fd = open(MFC_DEV_NAME,O_RDWR|O_NDELAY);

//記憶體空間對映

x4>addr = (uint8_t *) mmap(0,BUF_SIZE,PROT_READ |PROT_WRITE,MAP_SHARED,x4>dev_fd,0);

//編碼引數設定

ioctl(x4>dev_fd,S3C_MFC_IOCTL_MFC_H264_ENC_INIT,&x4>enc_init);

//獲取輸入/輸出地址

x4>get_buf_addr.in_usr_data = (int)x4>addr;

ioctl(x4>dev_fd,S3C_MFC_IOCTL_MFC_GET_YUV_BUF_ADDR,&x4>get_buf_addr);

x4>in_buf = (uint8_t *)x4>get_buf_addr.out_buf_addr;

x4>get_buf_addr.in_usr_data = (int)x4>addr;

ioctl(x4>dev_fd,S3C_MFC_IOCTL_MFC_GET_LINE_BUF_ADDR,&x4>get_buf_addr);

x4>out_buf = (uint8_t *)x4>get_buf_addr.out_buf_addr;

return 0;

}

ioctl的引數為S3C_MFC_IOCTL_MFC_H264_ENC_INIT,表示使用H.264編碼。

X264_frame函式執行編碼過程。需要注意的是data引數儲存了需要編碼的資料,是一個四維的陣列,要把它轉換成一維陣列用於S3C6410編碼器輸入。另外,編碼資料存在空的情況,也就是空幀。這是需要處理的,方法是返回“0”,表示沒有輸出資料,否則程式執行時會出現段錯誤。

static int X264_frame(AVCodecContext *ctx,uint8_t *buf,int bufsize,void *data){

……

//空間轉換

if(frame){

memcpy(x4>in_buf,frame>data[0],ctx>width*ctx>height);

memcpy(x4>in_buf+ctx>width*ctx>height,frame>data[1],ctx>width*ctx>height/4);

memcpy(x4>in_buf+ctx>width*ctx>height+ctx>width*ctx>height/4,frame>data[2],

ctx>width*ctx>height/4);

}

else

return 0;//空幀,返回

//執行編碼過程

ioctl(x4>dev_fd,S3C_MFC_IOCTL_MFC_H264_ENC_EXE,&x4>enc_exe);

//編碼資料輸出

bufsize = x4>enc_exe.out_encoded_size;

memcpy(buf,x4>out_buf,bufsize);

……

return bufsize;

}

X264_close關閉函式用於編碼結束後的資源釋放,包括取消空間對映和關閉裝置檔案。

static av_cold int X264_close(AVCodecContext *avctx){

//取消空間對映

munmap(x4>addr,BUF_SIZE);

//關閉裝置檔案

close(x4>dev_fd);

return 0;

}

解碼函式的實現過程類似於編碼函式,包括空間轉換、執行解碼和解碼資料輸出。初始化時使用S3C_MFC_IOCTL_MFC_H264_DEC_INIT引數,執行時使用S3C_MFC_IOCTL_MFC_H264_ENC_EXE引數。

4 執行測試

s3cx264編解碼器新增到FFmpeg後,可以通過以下方式測試:

① 用如下命令編譯FFmpeg。

./configure enablecrosscompile

arch=armv6 cpu=armv6

targetos=linux crossprefix

=/usr/local/arm/4.3.2/bin/

armlinux

② 執行 ./ffmpeg codecs檢視可以找到s3cx264編解碼器,如圖2所示。

圖2 FFmpeg顯示s3cx264編解碼器資訊

③ 結合USB攝像頭測試s3cx264編碼。執行 ./ffmpeg s 320x240 r 50 f video4linux2 i /dev/video2 vcodec s3cx264 test.mp4 可以看到FFmpegg正使用s3cx264編碼器將USB攝像頭採集的資料編碼壓縮成test.mp4檔案。test.mp4能夠正常播放顯示。

以上測試說明已經成功地將s3cx264硬體視訊編碼器新增到了FFmpeg中,能夠編碼視訊資料,可以運用到其他使用FFmpeg庫的多媒體程式中。

結語

對於多媒體開發來說,編解碼時使用FFmpeg多媒體庫是一個不錯的選擇,支援較多的音視訊編解碼,程式設計介面簡單易用。瞭解FFmpeg編解碼過程,熟悉FFmpeg硬體編解碼器新增方法,對多媒體開發,尤其是資源有限的嵌入式多媒體開發有很大幫助。本文通過分析FFmpeg視訊編解碼過程和三星S3C6410處理器視訊硬體編解碼方法,在FFmpeg庫中成功新增S3C6410硬體編解碼器,使FFmpeg庫具有H.264視訊格式的硬體編解碼能力,可運用於遊戲裝置、監控裝置、視訊會議裝置和數字網路電視等嵌入式系統中,同時也為其他嵌入式裝置新增別的視訊格式的編解碼器到FFmpeg多媒體庫提供了參考。

參考文獻

[1] http://www.ffmpeg.org/.

[2] Samsung.S3C6410 Datasheet,2010.

[3] 李少春.基於FFMPEG的嵌入式視訊監控系統[J].電子技術,2007(3):3437.

[4] API Document S3C6400/6410 MultiFormat Codec,2008.

[5] FFmpeg codec HOWTO[EB/OL].2010[201101].http://wiki.multimedia.cx/index.php?title=FFmpeg_codec_HOWTO/.

劉建敏(碩士生)、楊斌(教授),主要研究方向為微控制器與嵌入式系統及應用。