作者:
嵌入式領域著名講師 O(∩_∩)O 牛牛猛 華清遠見金牌講師
歡迎大家去我CSDN部落格上踩踩
在程式碼分析開始前需要對一個概念進行解釋,就是MFC。
Multi Format Codec的縮寫,是ARM微處理器內部一種支援多種硬體編碼方式的硬體電路,能夠編碼/解碼 MPEG-4/H.263/H.264(30fps)等多種格式的多媒體影像。
TOP6410開發板使用的是ARM11的核,我們現在要利用這個ARM內部的硬體編解碼電路來直接對攝像頭採集到的影像進行基於硬體的編解碼。首先在專案開始前需要對TOP6410的效能做大體的測試,現在我們使用的是三星提供的測試程式,通過對這個測試程式的分析可以很好地讓我們瞭解基於系統級的影像編解碼函式的呼叫機制,有利於我們順利的提取影像並且進行進一步的處理。
要使用MFC,首先要了解如何使用這種機制,在我們的程式碼中首先要定義一個MFC的控制程式碼(handle),所有的MFC操作都是需要通過傳遞這個handle作為引數來執行的,它的重要性就跟main 函式差不多,是整個編解碼過程的掌舵者。在原始碼中是這樣定義的:
/***************** MFC *******************/
static void *handle;
/* MFC functions */
static void *mfc_encoder_init(int width, int height, 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, int frame_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", "SsbSipH264EncodeInit Failed\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 between application 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);
其實這就是一個解除對映的過程。