首先非常感謝先行者LiaoJunXiong (參考網頁:https://blog.csdn.net/weixin_43549602/article/details/84571906)提供的使用gpac封裝mp4的代碼,大大簡化了gpac使用的難度。
不過很遺憾,原作者沒有對代碼做很多註釋,或者說基本上沒做註釋,因此在這裏我針對於各個函數加上詳細註釋,這樣可以使各函數、代碼的功能清晰明瞭,使人對這個工程有更爲深入的瞭解。由於代碼不算短,因此分系列,每一篇介紹一個函數。
先來說第一個函數ParseNalu,源碼如下:
int AF_MP4Writer::ParseNalu(unsigned char *pData, int nSize, int *pStart, int *pEnd)
{
int i = 0;
*pStart = 0;
*pEnd = 0;
while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
&& (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00 || pData[i + 3] != 0x01))
{
i++;
if (i + 4 >= nSize)
return 0;
}
if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
i++;
if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
return 0;
i += 3;
*pStart = i;
while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00)
&& (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01))
{
i++;
if (i + 3 >= nSize)
{
*pEnd = nSize;
return (*pEnd - *pStart);
}
}
*pEnd = i;
return (*pEnd - *pStart);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
&& (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00 || pData[i + 3] != 0x01))
{
i++;
if (i + 4 >= nSize)
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
這一段代碼的意思是:
從輸入數組pData[0](i初始值爲0)開始,依次查找0x00 0x00 0x01或0x00 0x00 0x00 0x01的序列(起始碼)。如果沒有找到,也就是說pData[0] pData[1] pData[2]不是0x00 0x00 0x01,並且pData[0] pData[1] pData[2] pData[3]也不是0x00 0x00 0x00 0x01,則使i自增1,也就是說從pData[1]開始,再繼續觀察,看是否滿足pData[1] pData[2] pData[3]爲0x00 0x00 0x01或pData[0] pData[1] pData[2] pData[3]爲0x00 0x00 0x00 0x01,如果還不滿足,則i再自增1,依次類推……
如果一直不滿足要求,直到i+4達到了傳入的最大長度,則認爲沒有找到起始碼,返回0,即數據長度爲0。
如果查找的過程中滿足條件了,不管是滿足0x00 0x00 0x01還是0x00 0x00 0x00 0x01,都跳出循環,無需再查找了,記錄下此時i的位置(此時pData[i]爲第一個0x00)。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
i++;
if (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01)
return 0;
i += 3;
*pStart = i;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
這段代碼的意思是:
經過上一段程序運行到這裏,說明無非就是2種情況,一種是pData[i] pData[i+1] pData[i+2]爲0x00 0x00 0x01,另一種是pData[i] pData[i+1] pData[i+2] pData[i+3]爲0x00 0x00 0x00 0x01。這段代碼就是要區分這2種情況。
如果不是0x00 0x00 0x01,則把i自增1,如果是就不用自增了。
如果是0x00 0x00 0x01的情況,上邊i沒有自增,還是維持pData[i] pData[i+1] pData[i+2]爲0x00 0x00 0x01,則繼續往下進行;如果是0x00 0x00 0x00 0x01的情況,上邊i自增了1,實際上是跳過了pData[i],即第一個0x00,而檢查後邊3個數據是否爲0x00 0x00 0x01,如果是,則繼續往下進行。注意,實際上此時對於2種情況,i的值是不同的,差了1。
如果上邊2個條件都不滿足,則說明出錯了,返回數據長度爲0。當然,實際上這種情況正常情況下不可能出現,屬於增強代碼健壯性。
i += 3這句話代碼的意思是不管是0x00 0x00 0x01還是0x00 0x00 0x00 0x01的哪一種情況,都跳過這個起始碼,而將i指向起始碼之後緊跟着的第一個字節數據(nal頭),並使*pStart保存i值。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
while ((pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x00)
&& (pData[i] != 0x00 || pData[i + 1] != 0x00 || pData[i + 2] != 0x01))
{
i++;
if (i + 3 >= nSize)
{
*pEnd = nSize;
return (*pEnd - *pStart);
}
}
*pEnd = i;
return (*pEnd - *pStart);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
這段代碼的意思是:
繼續按上邊的方法查找下一個起始碼(因爲i此時已經跳過了第一個起始碼)。每個NAL前有一個起始碼 0x00 0x00 0x01(或者0x00 0x00 0x00 0x01),每個起始碼作爲一個NAL的起始標識,當檢測到下一個起始碼時,當前NAL結束。因此實際上是在找這一個NAL的結束位置,從而獲得本NAL的大小。如果沒有再找到新的起始碼,則將結尾處值設置爲nSize(說明到結尾都是屬於這一個NAL的),否則設置爲下一個起始碼的第1個0x00處的位置值(這不難理解,1-5共5個,一定是6-1)。返回數據長度爲結尾處位置值減去起始處位置值,不用再加1了,因爲已經指向了後一個字節。
至此,ParseNalu函數就解析完了,經過詳細解釋,應該覺得這個函數好理解多了吧。