如何往mp4视频添加封面图和获取封面图?

前言:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值