前言:
大家好,之前有給大家分享過mp4錄像的方案,今天給大家分享的內容是:如何在添加自定義的封面圖到mp4里面去,以及在進入回放mp4視頻列表的時候,怎么獲取mp4視頻里面的封面圖,當然這個獲取到的封面圖是有用的,有什么用呢,舉例一個比較容易理解的運用,如下圖所示:

在嵌入式設備上,做到顯示如上在windows上顯示的這這種效果。
那上面是如何實現獲取和添加封面圖的呢?下面我們先要了解一下mp4里面的udta box的作用。
udta box的作用:
首先我們先到iso/iec-14496-12標準里面查找一下udta box的語法結構介紹:

從這個定義來看,User Data Box 是一個container box,用于存放用戶信息。這些用戶信息以一組更具體的子box形式組織,每個子box用自己的類型來更明確地描述其內容;同時udta box可有可無,不會對mp4播放產生影響,他可以再嵌套在moov box、trak box、moof box、traf box等container box里面。下面是udta box嵌套在 moov box里面:

meta(Metadata Box):里面的元數據集合。
hdlr((Handler Reference Box):說明元數據的類型。比如 handler type 常見是 "mdir" / "mdta",用來說明這是“metadata”.
ilst(Item List Box):實際存放的元數據條目。具體的元數據,我們在下面再做詳細的介紹。
但是我們從iso/iec-14496-12標準里面找不到對ilst box的描述。
這里就需要了解一下背景,其實mp4的發展,最開始是從蘋果那邊來的,后面才有了iso/iec-14496-12標準,最開始對mp4內部組成叫法不是叫box,而是叫做atom,在蘋果官方mp4標準里面就可以找到ilst box的描述:
https://developer.apple.com/documentation/quicktime-file-format/metadata_item_list_atom

但是我們去這里查看,并沒有對ilst atom里面做具體的說明,也就是看到我們最開始說的封面圖box說明,這里只是說明了:
元數據項列表atom(ilst)保存著 實際的元數據值,這些值屬于 metadata atom的一部分;元數據項的組織形式是一個 項目列表;元數據項列表atom的類型是 ilst,它包含若干元數據項,而每個元數據項本身也是一個atom.
那該怎么查找這個封面圖atom呢?
在iTunes Tagging 規范(Apple 沒有在 Developer Docs 完全公開,而是通過 iTunes SDK、開源項目、社區文檔流傳出來的),比如說:
https://atomicparsley.sourceforge.net/mpeg-4files.html

其中有句話說:

用于 iTunes 的元數據位于 moov.udta.meta.ilst 這一層級結構中。
在 ilst atom下面的那些atom都有特定的名字,但它們本身并不直接保存數據。 這些具名atom的子atom(即 data atom)才真正存放了實際的信息。
上層父atom的 四字符代碼 (FourCC) 會列在下面,而在 data atom之后的 atom flags(標志位) 會顯示在 “Class” 一欄中。 正是這個 data atom的 class(類別),大體上決定了其中保存的數據是文本、數字還是二進制數據.
換句說,父 atom(如 ?nam, ?ART, covr):只是一個標簽,用來表明這是“標題”、“藝術家”、“封面”,子 atom data:才是真正放數據的地方:

如果 class=1 → UTF-8/UTF-16 文本
如果 class=21 → 整數
如果 class=0 或其他 → 二進制(如 covr 里存放 JPEG/PNG)
我們要在找的封面圖就是covr atom,可以看到里面支持JPEG和PNG圖片。
好了,現在我們明白了原理,那寫代碼該如何實現呢?
mp4v2實現mp4視頻文件添加封面圖:
主要實現接口步驟:
* ?<b>iTMF Generic add workflow:</b>** ? ? ?@li MP4ItmfItemAlloc()* ? ? ?@li MP4ItmfAddItem()* ? ? ?@li MP4ItmfItemFree()
相關結構體說明:
/** 基本數據類型(在規范中定義的枚舉值) */
typedef enum MP4ItmfBasicType_e
{MP4_ITMF_BT_IMPLICIT ?= 0, ? /**< 隱式類型,適用于不需要明確指明數據類型的標簽 */MP4_ITMF_BT_UTF8 ? ? ?= 1, ? /**< UTF-8 字符串(沒有長度前綴,也沒有 null 結尾) */MP4_ITMF_BT_UTF16 ? ? = 2, ? /**< UTF-16BE 編碼字符串 */MP4_ITMF_BT_SJIS ? ? ?= 3, ? /**< Shift-JIS 字符串(主要用于日文,已廢棄,除非必須兼容特殊日文字符) */MP4_ITMF_BT_HTML ? ? ?= 6, ? /**< HTML 格式字符串,文件頭需說明 HTML 版本 */MP4_ITMF_BT_XML ? ? ? = 7, ? /**< XML 格式字符串,頭部必須包含 DTD 或 Schema 聲明 */MP4_ITMF_BT_UUID ? ? ?= 8, ? /**< UUID/GUID,二進制存儲,占 16 字節,可作為唯一標識符 */MP4_ITMF_BT_ISRC ? ? ?= 9, ? /**< ISRC(國際標準錄音編碼),UTF-8 文本形式,作為 ID 使用 */MP4_ITMF_BT_MI3P ? ? ?= 10, ?/**< MI3P 標識符,UTF-8 文本形式,作為 ID 使用 */MP4_ITMF_BT_GIF ? ? ? = 12, ?/**< GIF 圖像(已廢棄) */MP4_ITMF_BT_JPEG ? ? ?= 13, ?/**< JPEG 圖像(二進制數據) */MP4_ITMF_BT_PNG ? ? ? = 14, ?/**< PNG 圖像(二進制數據) */MP4_ITMF_BT_URL ? ? ? = 15, ?/**< URL 地址,UTF-8 編碼的絕對路徑 */MP4_ITMF_BT_DURATION ?= 16, ?/**< 時長,毫秒單位,32 位整數表示 */MP4_ITMF_BT_DATETIME ?= 17, ?/**< 日期/時間,UTC 格式,從 1904-01-01 00:00:00 開始的秒數,支持 32/64 位 */MP4_ITMF_BT_GENRES ? ?= 18, ?/**< 音樂流派,枚舉值列表 */MP4_ITMF_BT_INTEGER ? = 21, ?/**< 大端有符號整數,長度可以是 {1,2,3,4,8} 字節 */MP4_ITMF_BT_RIAA_PA ? = 24, ?/**< RIAA 家長指引標簽:{-1=否, 1=是, 0=未指定},8 位整數 */MP4_ITMF_BT_UPC ? ? ? = 25, ?/**< UPC(通用產品碼),UTF-8 文本格式,作為 ID 使用 */MP4_ITMF_BT_BMP ? ? ? = 27, ?/**< BMP 圖像(二進制數據) */MP4_ITMF_BT_UNDEFINED = 255 ?/**< 未定義類型 */
} MP4ItmfBasicType;
/** 數據結構* ?用于表示 iTMF metadata item atom 中的 data atom。*/
typedef struct MP4ItmfData_s
{uint8_t ? ? ? ? ?typeSetIdentifier; /**< 類型集合標識符,固定為 0。 */MP4ItmfBasicType typeCode; ? ? ? ? ?/**< iTMF 基本數據類型(枚舉值 MP4ItmfBasicType)。 */uint32_t ? ? ? ? locale; ? ? ? ? ? ?/**< 本地化標識符,通常為 0(無區域信息)。 */uint8_t* ? ? ? ? value; ? ? ? ? ? ? /**< 實際數據指針(可能為 NULL)。 */uint32_t ? ? ? ? valueSize; ? ? ? ? /**< 數據長度(字節數)。 */
} MP4ItmfData;
/** 數據列表* ?表示一個 metadata item 中包含的多個 data(例如 covr 中可能有多個封面圖片)。*/
typedef struct MP4ItmfDataList_s
{MP4ItmfData* elements; /**< data 元素的數組指針。如果 size=0,則為 NULL。 */uint32_t ? ? size; ? ? /**< data 元素的數量。 */
} MP4ItmfDataList;
/** 元數據項結構* ?表示 ilst atom 下的一個 metadata item。*/
typedef struct MP4ItmfItem_s
{void* __handle; /**< 內部使用的句柄,用戶無需關心。 */char* ? ? ? ? ? code; ? ? /**< 四字符代碼 (FourCC),標識該 atom 類型(例如?"covr",?"?nam"),以 NULL 結尾。 */char* ? ? ? ? ? mean; ? ? /**< 可選字段,UTF-8 格式的“意義”描述,可以為 NULL。 */char* ? ? ? ? ? name; ? ? /**< 可選字段,UTF-8 格式的“名稱”,可以為 NULL。 */MP4ItmfDataList dataList; /**< data 列表,一個 item 可以有多個 data。可以為 0 個。 */
} MP4ItmfItem;
/** 元數據項列表* ?表示 ilst atom 下的一系列 metadata item。*/
typedef struct MP4ItmfItemList_s
{MP4ItmfItem* elements; /**< item 元素的數組指針。如果 size=0,則為 NULL。 */uint32_t ? ? size; ? ? /**< item 的數量。 */
} MP4ItmfItemList;
總結:
MP4ItmfBasicType:定義了 data 的數據類型(文本、整數、圖片、時間戳等)。
MP4ItmfData:表示一個具體的 data 原子(包含類型、區域、本體數據)。
MP4ItmfDataList:一個 metadata item 可以包含多個 data(比如 covr 可以有 JPEG + PNG 兩種封面)。
MP4ItmfItem:一個 ilst 下的 metadata item(如 ?nam = Title,covr = Cover)。
MP4ItmfItemList:整個 ilst 里的 item 列表。
下面看具體的接口說明:
/** 在堆上分配一個 metadata item。* ?@param code 四字符代碼 (FourCC),用于標識該 metadata atom 的類型。* ? ? ? ? ? ? ?- 必須是一個以 NULL 結尾的字符串,例如?"?nam"、"?ART"、"covr"。* ?@param numData 要為該 item 分配的 data 元素數量。* ? ? ? ? ? ? ? ? - 必須 >= 1。* ? ? ? ? ? ? ? ? - 每個 data 對應一個 MP4ItmfData 結構(存儲實際的值,比如文本或圖片)。* ?@return?返回新分配的 MP4ItmfItem 指針。* ? ? ? ? ?- 內部包含一個空的 MP4ItmfDataList(長度 = numData)。* ? ? ? ? ?- 使用完畢后需要調用 MP4ItmfItemFree() 來釋放。*/
MP4V2_EXPORT MP4ItmfItem*
MP4ItmfItemAlloc( const char* code, uint32_t numData );
/** 向 MP4 文件中添加一個 metadata item。* ?@param hFile MP4 文件句柄(通過 MP4Modify() 或 MP4Read() 獲得)。* ?@param item ?要添加的 metadata item 對象(通常由 MP4ItmfItemAlloc() 創建并填充)。* ? ? ? ? ? ? ? - item->code 決定寫入的是哪個 key(如?"covr"?表示封面)。* ? ? ? ? ? ? ? - item->dataList.elements 里存放真正的數據。* ?@return?如果成功寫入文件,返回?true;否則返回?false。** ?使用場景:* ? ?- 用于向 MP4 文件寫入一個新的 metadata 項目(如標題、藝術家、封面)。* ? ?- 如果文件中已有同類型 item,可能會追加或覆蓋,具體行為取決于庫實現。*/
MP4V2_EXPORT bool
MP4ItmfAddItem( MP4FileHandle hFile, const MP4ItmfItem* item );
/** 釋放一個 metadata item(深度釋放)。* ?@param item 要釋放的 item。* ? ? ? ? ? ? ?- 包括 item 本身,以及內部所有 data 元素、字符串內存都會被釋放。* ? ? ? ? ? ? ?- 釋放后指針不可再使用。** ?注意:* ? ?- 只釋放內存,不會影響已經寫入 MP4 文件的數據。* ? ?- 正確流程是:MP4ItmfItemAlloc() → 填充數據 → MP4ItmfAddItem() → MP4ItmfItemFree()*/
MP4V2_EXPORT void
MP4ItmfItemFree( MP4ItmfItem* item );
demo實現演示:
FILE* fp = fopen("./test.jpg",?"rb"?);if?(!fp) {printf(?"Failed to open JPEG file\n"?);//return?1;}fseek( fp, 0, SEEK_END );long size_jpeg = ftell( fp );fseek( fp, 0, SEEK_SET );MP4FileHandle file = MP4Modify(mp4_file_name , 0);?if( file == MP4_INVALID_FILE_HANDLE ) {printf(?"MP4Modify faile999999999999999999999999999d\n"?);return?-1;}?unsigned char buffer_jpeg[1024 *1024] = {0};size_t ret = fread( buffer_jpeg, 1, size_jpeg, fp );/* allocate item with 1 data element */MP4ItmfItem* preview = MP4ItmfItemAlloc(?"covr", 1 );MP4ItmfData* data = &preview->dataList.elements[0];data->typeCode = MP4_ITMF_BT_JPEG;data->valueSize = (uint32_t)size_jpeg;data->value = buffer_jpeg;/* add to mp4 file */MP4ItmfAddItem(file, preview );/*?caller?responsibility to free */MP4ItmfItemFree( preview );fclose( fp );MP4Close( file ,0 );
mp4v2讀取mp4視頻里面的封面圖實現:
實現步驟:
@li MP4ItmfGetItems()* ? ? ?@li inspect each item...* ? ? ?@li MP4ItmfItemListFree()
/** 從 MP4 文件中獲取所有 metadata item。* ?@param hFile 文件句柄(通過 MP4Read() 或 MP4Modify() 獲得)。* ?@return?成功時返回一個 MP4ItmfItemList* 指針,包含該文件中所有的 metadata 項;* ? ? ? ? ?失敗時返回 NULL。** ?返回的 MP4ItmfItemList 結構:* ? ?- elements 指向一個數組,每個元素是一個 MP4ItmfItem。* ? ?- size 表示 metadata item 的數量。* ? ?- 每個 MP4ItmfItem 可能包含多個 MP4ItmfData(比如 covr 里可以有多張圖片)。** ?內存管理:* ? ?- 返回的 MP4ItmfItemList 必須由調用者在使用完后調用 MP4ItmfItemListFree() 來釋放,* ? ? ?否則會產生內存泄漏。** ?使用場景:* ? ?- 遍歷并讀取 MP4 文件里的所有 iTunes/QuickTime metadata(標題、藝術家、封面等)。* ? ?- 例如獲取 ilst 下面的 ?nam (Title)、?ART (Artist)、covr (Cover Art)。*/
MP4V2_EXPORT MP4ItmfItemList*
MP4ItmfGetItems( MP4FileHandle hFile );
/** 釋放一個 metadata item 列表(深度釋放)。* ?@param itemList 要釋放的 itemList。* ? ? ? ? ? ? ? ? ?- itemList 指針本身、以及其中的每個 MP4ItmfItem、* ? ? ? ? ? ? ? ? ? ?每個 MP4ItmfDataList、字符串 (code/mean/name)、* ? ? ? ? ? ? ? ? ? ?data->value 內存都會被釋放。* ? ? ? ? ? ? ? ? ?- 調用后該指針不可再使用。** ?注意:* ? ?- 該操作僅釋放內存,不會影響 MP4 文件中實際存儲的數據。* ? ?- 一般在使用完 MP4ItmfGetItems() 的返回結果后調用。*/
MP4V2_EXPORT void
MP4ItmfItemListFree( MP4ItmfItemList* itemList );
demo演示:
MP4FileHandle h = MP4Read(mp4file);if?(h == MP4_INVALID_FILE_HANDLE) {fprintf(stderr,?"Failed to open file: %s\n", mp4file);return?-1;}MP4ItmfItemList* list = MP4ItmfGetItemsByCode(h,?"covr");if?(!list || list->size == 0) {fprintf(stderr,?"No cover art found!\n");MP4Close(h,0);return?-1;}// 取第一個 covr 元素MP4ItmfItem* item = list->elements;if?(item->dataList.size > 0) {MP4ItmfData* data = &item->dataList.elements[0];FILE* f = fopen(out_jpeg,?"wb");if?(!f) {fprintf(stderr,?"Failed to open output: %s\n", out_jpeg);MP4ItmfItemListFree(list);MP4Close(h,0);return?-1;}fwrite(data->value, 1, data->valueSize, f);fclose(f);printf("Cover art extracted to %s (%u bytes)\n", out_jpeg, data->valueSize);}MP4ItmfItemListFree(list);MP4Close(h,0);
總結:
以上就是往mp4視頻文件里面添加用戶自定義的封面圖和獲取具體的實現,文章是使用mp4v2方案實現,也可以用其他方案來實現,比如ffmpeg都行。