C++如何解析MP4文件的Metadata实现视频信息提取

来源:站长源码作者:沙月恵奈‌头衔:网络博主
导读:本期聚焦于小伙伴创作的《C++如何解析MP4文件的Metadata实现视频信息提取》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++如何解析MP4文件的Metadata实现视频信息提取》有用,将其分享出去将是对创作者最好的鼓励。

MP4文件是基于ISO Base Media File Format标准的容器格式,所有的数据都存储在称为Box的单元中,视频的Metadata信息也分散存储在不同的Box结构里,要提取这些信息首先需要理解Box的基本结构。

C++如何解析MP4文件的Metadata实现视频信息提取

MP4文件Box结构基础

每个Box由头部和数据部分组成,头部包含Box的大小和类型两个核心字段。Box的大小字段占4字节,表示整个Box的字节数,类型字段占4字节,用四个ASCII字符标识Box的类型,比如ftyp表示文件类型Box,moov表示媒体信息容器Box。

常见的存储视频Metadata的Box包括:

  • mvhd:存储整个媒体的基础信息,比如时长、创建时间
  • trak:存储单个媒体轨道的信息,视频轨道通常包含分辨率相关参数
  • tkhd:存储单个轨道的具体属性,比如视频的宽度和高度
  • stsd:存储媒体采样描述信息,包含编码格式等参数

解析前的准备工作

首先需要定义基础的数据结构来映射Box的头部信息,同时需要注意MP4文件采用大端字节序存储数据,在读取多字节数值时需要进行字节序转换。

#include <iostream>
#include <fstream>
#include <cstdint>
#include <cstring>
#include <string>
#include <arpa/inet.h> // 用于大端转小端函数

// Box头部结构
struct BoxHeader {
    uint32_t size;  // Box总大小
    char type[5];   // Box类型,多留1位存字符串结束符
};

// 转换大端字节序到主机字节序
uint32_t big_endian_to_host(uint32_t val) {
    return ntohl(val);
}

uint16_t big_endian_to_host(uint16_t val) {
    return ntohs(val);
}

// 读取Box头部的函数
bool read_box_header(std::ifstream& file, BoxHeader& header) {
    // 读取size字段
    if (!file.read(reinterpret_cast<char*>(&header.size), 4)) {
        return false;
    }
    header.size = big_endian_to_host(header.size);
    // 读取type字段
    if (!file.read(header.type, 4)) {
        return false;
    }
    header.type[4] = '';
    return true;
}

核心Metadata提取实现

提取视频时长

视频总时长存储在moov容器下的mvhd Box中,mvhd的结构中,前4字节是版本信息,版本0和版本1的时长字段偏移位置不同,需要分别处理。

// 解析mvhd获取时长,返回时长(秒),失败返回-1
double parse_mvhd(std::ifstream& file, uint32_t box_size) {
    // 跳过版本和标志字段,共8字节
    file.seekg(8, std::ios::cur);
    uint8_t version;
    file.read(reinterpret_cast<char*>(&version), 1);
    file.seekg(-9, std::ios::cur); // 回到mvhd数据起始位置

    uint32_t timescale;
    uint32_t duration;
    if (version == 0) {
        // 版本0:跳过创建时间、修改时间,各4字节,共8字节
        file.seekg(8, std::ios::cur);
        // 读取时间刻度
        if (!file.read(reinterpret_cast<char*>(&timescale), 4)) return -1;
        timescale = big_endian_to_host(timescale);
        // 读取时长
        if (!file.read(reinterpret_cast<char*>(&duration), 4)) return -1;
        duration = big_endian_to_host(duration);
    } else {
        // 版本1:跳过创建时间、修改时间,各8字节,共16字节
        file.seekg(16, std::ios::cur);
        // 读取时间刻度
        if (!file.read(reinterpret_cast<char*>(&timescale), 4)) return -1;
        timescale = big_endian_to_host(timescale);
        // 跳过8字节的时长(版本1时长是64位)
        uint64_t duration_64;
        if (!file.read(reinterpret_cast<char*>(&duration_64), 8)) return -1;
        duration = static_cast<uint32_t>(big_endian_to_host(duration_64) & 0xFFFFFFFF);
    }
    if (timescale == 0) return -1;
    return static_cast<double>(duration) / timescale;
}

提取视频分辨率

视频的宽度和高度存储在trak下的tkhd Box中,同样需要根据tkhd的版本判断字段偏移位置。

// 解析tkhd获取分辨率,返回是否成功
bool parse_tkhd(std::ifstream& file, uint32_t box_size, uint32_t& width, uint32_t& height) {
    // 跳过版本和标志,共8字节
    file.seekg(8, std::ios::cur);
    uint8_t version;
    file.read(reinterpret_cast<char*>(&version), 1);
    file.seekg(-9, std::ios::cur); // 回到tkhd起始位置

    if (version == 0) {
        // 版本0:跳过创建时间、修改时间、track ID、保留字段,共16字节
        file.seekg(16, std::ios::cur);
        // 跳过时长字段4字节
        file.seekg(4, std::ios::cur);
        // 跳过保留字段8字节
        file.seekg(8, std::ios::cur);
        // 跳过矩阵结构36字节
        file.seekg(36, std::ios::cur);
        // 读取宽度和高度,各4字节,是16.16定点数,取整数部分
        uint32_t width_raw, height_raw;
        if (!file.read(reinterpret_cast<char*>(&width_raw), 4)) return false;
        if (!file.read(reinterpret_cast<char*>(&height_raw), 4)) return false;
        width = big_endian_to_host(width_raw) >> 16;
        height = big_endian_to_host(height_raw) >> 16;
    } else {
        // 版本1:跳过创建时间、修改时间、track ID、保留字段,共24字节
        file.seekg(24, std::ios::cur);
        // 跳过时长字段8字节
        file.seekg(8, std::ios::cur);
        // 跳过保留字段8字节
        file.seekg(8, std::ios::cur);
        // 跳过矩阵结构36字节
        file.seekg(36, std::ios::cur);
        // 读取宽度和高度
        uint32_t width_raw, height_raw;
        if (!file.read(reinterpret_cast<char*>(&width_raw), 4)) return false;
        if (!file.read(reinterpret_cast<char*>(&height_raw), 4)) return false;
        width = big_endian_to_host(width_raw) >> 16;
        height = big_endian_to_host(height_raw) >> 16;
    }
    return true;
}

提取编码格式

编码格式存储在stsd Box下的子Box中,子Box的类型就是编码格式标识,比如avc1代表H.264编码,mp4a代表AAC音频编码。

// 解析stsd获取视频编码格式
std::string parse_stsd(std::ifstream& file, uint32_t box_size) {
    // 跳过版本、标志、条目数量,共8字节
    file.seekg(8, std::ios::cur);
    // 读取第一个子Box的类型,就是编码格式
    char codec[5];
    // 跳过子Box的size字段4字节
    file.seekg(4, std::ios::cur);
    if (!file.read(codec, 4)) return "";
    codec[4] = '';
    return std::string(codec);
}

完整解析流程示例

将上面的功能组合起来,实现完整的MP4 Metadata解析流程:

int main() {
    std::string file_path = "test.mp4";
    std::ifstream file(file_path, std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "无法打开文件" << std::endl;
        return -1;
    }

    double video_duration = -1;
    uint32_t video_width = 0, video_height = 0;
    std::string video_codec = "";

    // 遍历所有Box
    while (file.peek() != EOF) {
        BoxHeader header;
        if (!read_box_header(file, header)) break;
        // 处理moov容器
        if (strcmp(header.type, "moov") == 0) {
            uint32_t moov_size = header.size - 8;
            uint32_t read_moov = 0;
            while (read_moov < moov_size) {
                BoxHeader sub_header;
                if (!read_box_header(file, sub_header)) break;
                // 处理mvhd获取时长
                if (strcmp(sub_header.type, "mvhd") == 0) {
                    video_duration = parse_mvhd(file, sub_header.size - 8);
                }
                // 处理trak获取分辨率
                else if (strcmp(sub_header.type, "trak") == 0) {
                    uint32_t trak_size = sub_header.size - 8;
                    uint32_t read_trak = 0;
                    while (read_trak < trak_size) {
                        BoxHeader trak_sub_header;
                        if (!read_box_header(file, trak_sub_header)) break;
                        if (strcmp(trak_sub_header.type, "tkhd") == 0) {
                            parse_tkhd(file, trak_sub_header.size - 8, video_width, video_height);
                        } else if (strcmp(trak_sub_header.type, "mdia") == 0) {
                            // 进入mdia查找stsd获取编码格式
                            uint32_t mdia_size = trak_sub_header.size - 8;
                            uint32_t read_mdia = 0;
                            while (read_mdia < mdia_size) {
                                BoxHeader mdia_sub_header;
                                if (!read_box_header(file, mdia_sub_header)) break;
                                if (strcmp(mdia_sub_header.type, "minf") == 0) {
                                    uint32_t minf_size = mdia_sub_header.size - 8;
                                    uint32_t read_minf = 0;
                                    while (read_minf < minf_size) {
                                        BoxHeader minf_sub_header;
                                        if (!read_box_header(file, minf_sub_header)) break;
                                        if (strcmp(minf_sub_header.type, "stbl") == 0) {
                                            uint32_t stbl_size = minf_sub_header.size - 8;
                                            uint32_t read_stbl = 0;
                                            while (read_stbl < stbl_size) {
                                                BoxHeader stbl_sub_header;
                                                if (!read_box_header(file, stbl_sub_header)) break;
                                                if (strcmp(stbl_sub_header.type, "stsd") == 0) {
                                                    video_codec = parse_stsd(file, stbl_sub_header.size - 8);
                                                }
                                                // 跳过当前stbl子Box
                                                file.seekg(stbl_sub_header.size - 8 - (file.tellg() - (std::istream::pos_type)&stbl_sub_header), std::ios::cur);
                                                read_stbl += stbl_sub_header.size;
                                            }
                                        } else {
                                            file.seekg(minf_sub_header.size - 8, std::ios::cur);
                                        }
                                        read_minf += minf_sub_header.size;
                                    }
                                } else {
                                    file.seekg(mdia_sub_header.size - 8, std::ios::cur);
                                }
                                read_mdia += mdia_sub_header.size;
                            }
                        } else {
                            file.seekg(trak_sub_header.size - 8, std::ios::cur);
                        }
                        read_trak += trak_sub_header.size;
                    }
                } else {
                    file.seekg(sub_header.size - 8, std::ios::cur);
                }
                read_moov += sub_header.size;
            }
        } else {
            // 跳过非moov的Box
            file.seekg(header.size - 8, std::ios::cur);
        }
    }

    // 输出解析结果
    std::cout << "视频时长: " << video_duration << " 秒" << std::endl;
    std::cout << "视频分辨率: " << video_width << "x" << video_height << std::endl;
    std::cout << "视频编码格式: " << video_codec << std::endl;

    file.close();
    return 0;
}

注意事项

实际开发中需要注意几个问题,首先是MP4文件可能存在Box嵌套多层的情况,上面的示例只处理了常见的层级结构,复杂文件可能需要递归遍历所有Box。其次部分MP4文件可能采用碎片化的结构,Metadata可能存储在moof等Box中,需要额外适配。另外读取文件时要注意边界判断,避免读取超出文件范围导致程序崩溃。

C++MP4_metadata视频信息提取文件解析修改时间:2026-07-12 10:57:46

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。