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參數。
評論
查看更多