前言:
大家好,之前有给大家分享过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都行。