轉自:http://blog.sina.com.cn/s/blog_65f6a0520100rgg7.html
在代碼分析開始前需要對一個概念進行解釋,就是MFC。
Multi Format Codec的縮寫,是ARM微處理器內部一種支持多種硬件編碼方式的硬件電路,能夠編碼/解碼MPEG-4/H.263/H.264(30fps)等多種格式的多媒體影像。
TOP6410開發板使用的是ARM11的核,我們現在要利用這個ARM內部的硬件編解碼電路來直接對攝像頭採集到的圖像進行基於硬件的編解碼。首先在項目開始前需要對TOP6410的性能做大體的測試,現在我們使用的是三星提供的測試程序,通過對這個測試程序的分析可以很好地讓我們瞭解基於系統級的圖像編解碼函數的調用機制,有利於我們順利的提取圖像並且進行進一步的處理。
要使用MFC,首先要了解如何使用這種機制,在我們的代碼中首先要定義一個MFC的句柄(handle),所有的MFC操作都是需要通過傳遞這個handle作爲參數來執行的,它的重要性就跟main函數差不多,是整個編解碼過程的掌舵者。在源碼中是這樣定義的:
static void *handle;
static void *mfc_encoder_init(int width, intheight, int frame_rate, int bitrate, int gop_num);
static void *mfc_encoder_exe(void *handle,unsigned char *yuv_buf, int frame_size, int first_frame, long*size);
static void mfc_encoder_free(void*handle);
看到我們這裏定義了三個函數分別是初始化函數,執行函數,還有句柄釋放函數。我們就是要利用這三個函數進行我們的編解碼操作,我們再來看看init函數的實現:
void *mfc_encoder_init(int width, int height, intframe_rate, int bitrate, int gop_num)
{
int frame_size;
void *handle;
int ret;
frame_size = (width * height * 3) >>1;//這裏的意思是把width*height*3的值除以2
handle = SsbSipH264EncodeInit(width, height, frame_rate, bitrate,gop_num);
if (handle == NULL) {
LOG_MSG(LOG_ERROR, "Test_Encoder", "SsbSipH264EncodeInitFailed\n");
return NULL;
}
ret = SsbSipH264EncodeExe(handle);
return handle;
}
首先要注意的是傳入的參數,它們分別定義了每一幀圖像的長寬,幀的速度,比特率,GOP(Group of Pictures)策略影響編碼質量(設置編碼的質量係數)。
函數的作用是對整個MFC的參數進行設置
這裏有一個frame_size,有人問爲什麼要定義成那麼大,我們需要的圖像每一幀的大小是我們可以自己定義的,我們在代碼執行前一般都會開一個緩衝區來存放每一幀的數據,由於我們開的緩衝區給每幀的大小就是那麼大,所以這裏也好配合我們之前開闢的緩衝區大小進行編碼。
SsbSipH264EncodeInit()這個函數可以說是真正的開始進入編碼的初始化過程,現在讓我們進去看看。
現在我們來看看代碼裏的關鍵的幾個地方:
hOpen = open(MFC_DEV_NAME,O_RDWR|O_NDELAY);//打開設備節點
// mapping shared in/out buffer betweenapplication and MFC device driver
addr = (unsigned char *) mmap(0, BUF_SIZE,PROT_READ | PROT_WRITE, MAP_SHARED, hOpen, 0);
這裏是進行內存映射,我想做過攝像頭項目的人對這個肯定特別有體會,這個函數的作用其實就是把MFC設備工作後寫入的那部分內存映射到我的應用程序開的緩衝區中,也就是說我只要對應用程序中的buffer進行讀寫其實就是對部分內存的讀寫。內存映射是linux內核當中一個非常重要的機制,希望能夠引起大家足夠多的重視。
pCTX = (_MFCLIB_H264_ENC *)malloc(sizeof(_MFCLIB_H264_ENC));
看到這裏我想有必要展示一下這個_MFCLIB_H264_ENC的結構體:
typedef struct
{
int magic;
int hOpen;
int fInit;
int enc_strm_size;
int enc_hdr_size;
unsigned int width, height;
unsigned int framerate, bitrate;
unsigned int gop_num;
unsigned char *mapped_addr;
} _MFCLIB_H264_ENC;
這個是爲MFC設備定義的結構體,至於這樣定義主要是爲了能夠和內核中的定義進行匹配,相關代碼可以參看內核。這裏主要是定義了編碼需要的參數。
之後的工作就是要把這個結構體填滿(定義好各項初始化參數),主要的作用就是完成初始化的工作。到這裏SsbSipH264EncodeInit()結束,但是初始化工作並沒有完成。
其實這裏你會發現handle是什麼?handle其實在這裏被定義爲了指向_MFCLIB_H264_ENC這個結構體的指針。其實仔細的朋友你會發現handle這個指針在每一次函數調用過程當中都會指向不同的結構體或者是內存地址,讀者可以把它理解成貫穿於整個MFC硬件解碼的過程當中的中間變量,就相當於指向貫穿於我們程序主幹部分的指針,通過它可以得到整個硬解碼過程的清晰函數結構。
要執行SsbSipH264EncodeExe()函數。要做的是對MFC內部的一些結構體進行初始化,然後就到了很關鍵的一步:
r = ioctl(pCTX->hOpen,IOCTL_MFC_H264_ENC_INIT, &mfc_args);
這一步其實就是把我們剛纔設置好的關於MFC的初始化參數傳遞到我們內核的驅動程序中,使得驅動程序能夠根據我們提供的這些參數對設備進行相應的初始化工作。
在這裏IOCTL_MFC_H264_ENC_INIT是制定設備的編碼格式,pCTX->hOpen是設備的描述符,mfc_args裏轉載了MFC所有的參數。完成了這些纔算是真正的完成了初始化的工作。
總結一下我們剛纔經過的步驟:
1. 打開設備節點
2. 進行內存到應用的內存映射
3. 初始化關於MFC設備的機構體,並且提供相應的參數
4. 把_MFCLIB_H264_ENC參數傳入MFC跟深層次的結構體當中
5. 通過ioctl函數把這些參數傳入到內核當中
初始化完成以後我們就要正式開始編碼了,現在看一下mfc_encoder_exe()函數的實現大致過程。其實運行的過程非常的簡單:
SsbSipH264EncodeGetInBuf(handle, 0);
SsbSipH264EncodeExe(handle);
SsbSipH264EncodeGetConfig(handle,H264_ENC_GETCONF_HEADER_SIZE,&hdr_size);
SsbSipH264EncodeGetOutBuf(handle,size);
看以上這幾個函數,其作用通過讀函數名字我想就已經非常清楚了,步驟如下:
1. 首先得到輸入圖像的地址buffer
2. 然後進行編碼
3. 第一次的編碼需要傳入配置參數
4. 得到輸出的經過編碼的圖像的地址(通過內部結構體傳遞)和大小
分析完以上過程之後,最後就是完成編碼後的處理函數
static void mfc_encoder_free(void*handle);
其實它當中就是調用了一個SsbSipH264EncodeDeInit(handle);函數
裏面其實只是做了一件事:munmap(pCTX->mapped_addr,BUF_SIZE);
其實這就是一個解除映射的過程。