liwen01 2023.12.17

前言

MP4是目前非常常用的一种视频封装格式,关于MP4的介绍资料也非常多。我们常用的封装库或工具有:ffmpeglibmp4v2GPACMP4.js,它们的优点是功能基本上都是比较全面,缺点就是它们占用的资源相对来说也是非常多的。

在嵌入式系统中,不管是RAM还是FLASH空间,一般都是非常小,这个时候,如果要将音视频封装成MP4,或是解码MP4格式就会显得非常困难,因为上面介绍的那些库都放不下或是因为内存不够运行不起来,只能根据MP4协议自己去解析。

这里介绍一个轻量级的MP4封装方法(minimp4),集成MP4编码,解码,信息查询功能,整体执行文件大小如下:

biao@ubuntu:~/minimp4_test$ mips-linux-uclibc-gnu-size test
text data bss dec hex filename
354696 1460 13624 369780 5a474 t
biao@ubuntu:~/minimp4_test$

(一)功能需求介绍

在一般嵌入式设备上,我们一般只需要MP4的一些简单操作,比如封装,解封装及文件信息查看。具体功能要求如下:

  • 支持合入H.264 和H.265 视频格式视频
  • 支持合入AAC音频
  • 支持MP4格式文件解封装
  • 支持获取MP4文件信息

性能上的限制,我们希望:

  • 代码空间小于500K
  • 运行内存小于100K

要在有限的资源上实现上面这些功能,我们可以在minimp4的基础再进一步完善。

minimp4 的源代码可以直接在 github 上下载,官方有个minimp4_test.c,里面有些使用的demo,但不是非常完善,可以参考使用。

(二)支持H.264与H.265格式

(1)H.264与H.265的区别

使用工具打开官方自带的foreman.264文件,我们可以看到:

  • 该文件中包含SPS,PPS,IDR,P帧,在其它文件中,可能还会有B帧
  • 头标签为00 00 00 01,后面一个是帧类型
  • 视频分辨率352*288
  • 流类型AVC/H.264
  • 总共300帧

另外打开一个H.265文件

我们可以看到:

  • 该文件中包含VPS SPS,PPS,IDR,TRAIL_R,TRAIL_N帧
  • 头标签为00 00 00 01,后面是帧类型
  • 视频分辨率1280*720
  • 流类型HEVC/H.265
  • 总共770帧

从应用的角度看,H.265 是有多一个VPS帧,它主要是用来描述视频中各种类型帧(比如I帧、P帧、B帧)的使用和顺序,以及它们之间的相关性.

在实际使用的时候,需要注意VPS、SPS、PPS这些帧的封装和解析。

(2)手动添加VPS信息

正常使用minimp4 的时候,使用h.265进行封装的是没有问题的,但是在解封装的时候,它并不会去提取VPS信息,这里比较简单的做法是自己补充上相关的信息到文件头:

if(is_hevc){
staticunsignedchar h265_vps_sps_pps[84]={
0x00,0x00,0x00,0x01,0x40,0x01,0x0C,0x01,
0xFF,0xFF,0x00,0x80,0x00,0x00,0x03,0x00,
0x00,0x03,0x00,0x00,0x03,0x00,0x00,0x03,
0x00,0x00,0xB5,0x02,0x40,0x00,0x00,0x00,
0x01,0x42,0x01,0x01,0x00,0x80,0x00,0x00,
0x03,0x00,0x00,0x03,0x00,0x00,0x03,0x00,
0x00,0x03,0x00,0x00,0xA0,0x02,0x80,0x80,
0x2D,0x1F,0xE5,0xB5,0x92,0x46,0xD0,0xCE,
0x49,0x24,0xB7,0x24,0xAA,0x49,0xF2,0x92,
0xC8,0x00,0x00,0x00,0x01,0x44,0x01,0xC1,
0xA5,0x58,0x1E,0x48,
}; if(fwrite(h265_vps_sps_pps, 1, sizeof(h265_vps_sps_pps), foutV)!= sizeof(h265_vps_sps_pps)){
goto END;
}
}

h265_vps_sps_pps 里面的内容,需要根据自己实际的码流信息进行修改

(三)支持AAC音频格式

AAC与PCM之间的编码与转换,可以查看上一篇文章:嵌入式音频应用开发介绍

主要需要注意的是AAC的配置,需要根据实际参数进行修改

// unsigned char  conf[] = {0x11, 0x90};  //AAL-LC 48kHz 2 channle
staticunsignedchar aac_conf[4]={0x11, 0x90, 0x0, 0x0};
unsignedint length = 2;
MP4E_set_dsi(mux, audio_track_id, aac_conf,4);
printf("%s %d \r\n",__FUNCTION__,__LINE__);

(四)获取mp4文件信息

这部分需要对 mp4 协议比较熟悉,具体的协议介绍,可以去查询MP4标准。这里介绍几个简单概念:

概念 描述
Box MP4中的基本构建单元,也称为Atom。每个Box都有特定类型的标识符和长度字段,用于描述它自己的内容和大小。不同类型的Box包含不同类型的信息。
Sample 视频和音频编解码中的最小可操作单元。对于视频,每个样本代表一个画面或图像帧;对于音频,每个样本可能代表一小段声音。它们按特定顺序组成媒体文件。
Track 用于组织媒体数据的单独通道。MP4文件可以包含多个轨道,如视频轨道、音频轨道。每个轨道包含相应类型的媒体数据和描述信息,如视频轨道包含视频样本和元数据。
Chunk 一组样本的集合。在媒体文件中,样本可以组织成不同大小的块,可以是连续的或分散的。块可以包含一个或多个样本,物理上可以存储在文件的不同位置。

mp4中包含很多的Box,mp4的基本信息和索引是在moov box中的:

Box类型 描述
mvhd 影片整体信息
trak 包含一个或多个轨道的详细描述
udta 存储用户自定义的数据

其中 mvhd Box(Movie Header Box):这个Box包含了影片的整体信息,比如时长、时间刻度等。它描述了整个媒体文件的基本属性。

我们打开一个使用miniMP4封装的MP4文件,可以看到moov box 是后置的,也就是在文件的后面,而在一些其它的库中,moov box的偏移位置是在比较靠近文件开始的位置。

如果我们要快速的查看mp4文件的信息,比如视频时间长度,对于使用minimp4封装的Mp4文件,它的moov box后置,我们可以从后面开始解析。

#define CHUNK_SIZE (10*1024)

typedefstruct
{
unsignedchar mvhd[4];
unsignedchar version;
unsignedchar flags[3];
unsignedint creation_time;
unsignedint modification_time;
unsignedint timescale;
unsignedint duration;
}MVHD_ST; MVHD_ST g_stMVDHInfo; /**
* @brief 字节序翻转
* @param val
* @return unsigned int
*/
unsigned int videoinfo_flip(unsigned int val) {
unsignedlongnew = 0;
new += (val & 0x000000FF) << 24;
new += (val & 0xFF000000) >> 24;
new += (val & 0x0000FF00) << 8;
new += (val & 0x00FF0000) >> 8;
returnnew;
} /**
* @brief 查找特定ASCII码字符串位置
* @param file_name
* @param search_str
* @return long long
*/
long long find_string_position(const char *file_name, const char *search_str) {
FILE *file = NULL;
char *buffer =NULL;
int read_size = 0;
int length = 0;
longlong file_size = 0;
longlong search_position =0;
longlong position = 0;
if(file_name==NULL || search_str==NULL){
printf("%s %d input para error \r\n",__FUNCTION__,__LINE__);
return-1;
}
file = fopen(file_name, "rb");
if (file == NULL) {
printf("%s %d file open error\r\n",__FUNCTION__,__LINE__);
return-2;
}
length = strlen(search_str);
fseek(file, 0, SEEK_END);
file_size = ftell(file);
search_position = file_size - CHUNK_SIZE;
if (search_position < 0) {
search_position = 0;
} buffer = (char *)malloc(CHUNK_SIZE + 1);
if (buffer == NULL) {
fclose(file);
printf("%s %d malloc error\r\n",__FUNCTION__,__LINE__);
return-3;
} int cnt = 0;
while (search_position >= 0) {
/**限制检索最大值,避免输入文件损坏一直在检索**/
if(cnt ++>30){
break;
}
fseek(file, search_position, SEEK_SET);
size_t read_size = fread(buffer, 1, CHUNK_SIZE, file);
for (int i = read_size - 1; i >= 0; i--) {
int j;
for (j = 0; j < length; j++) {
if (i - j < 0 || buffer[i - j] != search_str[length - j - 1]) {
break;
}
}
if (j == length) {
position = search_position + i - length + 1;
break;
}
}
search_position = search_position - CHUNK_SIZE;
if(search_position<0){
search_position = 0;
}
if(position!=0){
break;
}
}
printf("read cnt=%d \r\n",cnt);
if(position>0){
fseek(file, position, SEEK_SET);
read_size = fread(&g_stMVDHInfo, 1, sizeof(g_stMVDHInfo), file);
}
fclose(file);
free(buffer);
return position;
}

MP4的详细格式定义,可以查看MP4标准: Text of ISO/IEC 14496-12 5th edition

(五)减少内存的使用

在minimp4源码中,它有个preload函数,它的主要作用是将输入的文件全部读取到内存中去,后面数据解析的时候直接去内存Buff中获取,这种方式的优点是处理速度快,缺点就是耗内存。

在嵌入式系统设备中,有些设备可能整个系统总共才几十M的内存,一次把整个文件读取到内存中去解析,如果要处理比较大的mp4文件,显然是有问题的,会直接导致系统内存不够用。

有一个处理方式是按需读取数据,需要多少就读取多少,这样的处理方式可以以速度换空间。将 preload 改为 preload_filewrite_callback 改 read_file_callback,同时,有对内存数据进行操作的地方也需要修改。

static FILE *preload_file(const char *path, ssize_t *data_size)
{
FILE *file = fopen(path, "rb");
uint8_t *data;
*data_size = 0;
if (!file){
return0;
} if (fseek(file, 0, SEEK_END)){
exit(1);
} *data_size = (ssize_t)ftell(file);
if (*data_size < 0){
exit(1);
} if (fseek(file, 0, SEEK_SET)){
exit(1);
} return file;
} static int read_file_callback(int64_t offset, void *buffer, size_t size, void *token)
{
staticint total_len = 0;
INPUT_FILE_BUF *buf = (INPUT_FILE_BUF*)token;
int read = 0;
int ret = 0;
size_t to_copy = MINIMP4_MIN(size, buf->size - offset - size);
fseek(buf->fin,offset,SEEK_SET);
read = fread(buffer,1,to_copy,buf->fin);
return to_copy != size;
}

(六)工程资料下载

如需上面介绍的工程,测试文件,以及查看工具,可以在公众号中回复 资源 获取,内容在音视频连接中。工程目录如下:

biao@ubuntu:~/test/minimp4/minimp4_test$ tree
.
├── adts.c
├── adts.h
├── file
│   ├── decode_audio.aac
│   ├── decode_foreman_.264
│   ├── decode_foreman.264
│   ├── decode_test.h265
│   ├── decode_video.h265
│   ├── faac_adts.aac
│   ├── foreman.264
│   ├── h264_aac.mp4
│   ├── h265_aac.mp4
│   └── surfing_aa.h265
├── Makefile
├── minimp4.h
├── obj
│   ├── adts.o
│   └── write_demux_mp4.o
├── test
└── write_demux_mp4.c 2 directories, 18 files
biao@ubuntu:~/test/minimp4/minimp4_test$

结尾

minimp4 适用于内存和存储空间都非常受限的嵌入式设备,很多功能并不十分完善,需要自己根据实际应用进行适配。如果只是在设备上进行简单的MP4封装和解封装,它的功能也是足够了的。

---------------------------End---------------------------如需获取更多内容请关注 liwen01 公众号

超轻量级MP4封装方法介绍的更多相关文章

  1. WebService服务调用方法介绍

    1 背景概述 由于在项目中需要多次调用webservice服务,本文主要总结了一下java调用WebService常见的6种方式,即:四种框架的五种调用方法以及使用AEAI ESB进行调用的方法. 2 ...

  2. 腾讯正式开源高性能超轻量级 PHP 框架 Biny

    概况 Biny是一款高性能的超轻量级PHP框架 遵循 MVC 模式,用于快速开发现代 Web 应用程序 Biny代码简洁优雅,对应用层,数据层,模板渲染层的封装简单易懂,能够快速上手使用 高性能,框架 ...

  3. Python中异步协程的使用方法介绍

    1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后 ...

  4. JSON-RPC轻量级远程调用协议介绍及使用

    这个项目能够帮助开发人员利用Java编程语言轻松实现JSON-RPC远程调用.jsonrpc4j使用Jackson类库实现Java对象与JSON对象之间的相互转换.jsonrpc4j包含一个JSON- ...

  5. python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)

     先来回顾一下昨天的内容 黏包现象粘包现象的成因 : tcp协议的特点 面向流的 为了保证可靠传输 所以有很多优化的机制 无边界 所有在连接建立的基础上传递的数据之间没有界限 收发消息很有可能不完全相 ...

  6. [转] HTML5 FormData 方法介绍以及实现文件上传

    XMLHttpRequest 是一个浏览器接口,通过它,我们可以使得 Javascript 进行 HTTP (S) 通信.XMLHttpRequest 在现在浏览器中是一种常用的前后台交互数据的方式. ...

  7. TransactionScope事务处理方法介绍及.NET Core中的注意事项 SQL Server数据库漏洞评估了解一下 预热ASP.NET MVC 的VIEW [AUTOMAPPER]反射自动注册AUTOMAPPER PROFILE

    TransactionScope事务处理方法介绍及.NET Core中的注意事项   作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/10170712.ht ...

  8. SQL Server中解决死锁的新方法介绍

    SQL Server中解决死锁的新方法介绍 数据库操作的死锁是不可避免的,本文并不打算讨论死锁如何产生,重点在于解决死锁,通过SQL Server 2005, 现在似乎有了一种新的解决办法. 将下面的 ...

  9. .Net基础——程序集与CIL HttpClient封装方法 .Net Core 编码规范 C#中invoke和beginInvoke的使用 WebServeice 动态代理类

    .Net基础——程序集与CIL   1. 程序集和CIL: 程序集是由.NET语言的编译器接受源代码文件产生的输出文件,通常分为 exe和dll两类,其中exe包含Main入口方法可以双击执行,dll ...

  10. Optional类包含的方法介绍及其示例

    Optional类的介绍 javadoc中的介绍 这是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回> 该对象. 使用场景 用于避免 ...

随机推荐

  1. 分享一个 SpringBoot + Redis 实现「查找附近的人」的小技巧

    前言 SpringDataRedis提供了十分简单的地理位置定位的功能,今天我就用一小段代码告诉大家如何实现. 正文 1.引入依赖 <dependency> <groupId> ...

  2. 在 Net7.0环境下通过反射创建泛型实例和调用泛型方法

    一.介绍 最近没事干,就用闲暇时间写点东西,也记录一下温习历程.老人说的好,好记性,不如烂笔头.时间一长,当时记忆的再清楚,都会变得模糊,索性就写博客记录下来,如果下次需要,直接打开博客就找到了,不用 ...

  3. elmentui表单重置初始值问题与解决方法

    背景 在做管理台项目时,我们会经常使用到表单+表格+弹窗表单的组合,以完成对数据的增.删.查.改. 在vue2+elementui项目中,使用弹窗dialog+表单form,实现对数据的添加和修改. ...

  4. SpringBoot进阶教程(七十七)WebSocket

    WebSocket是一种在单个TCP连接上进行全双工通信的协议.WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器和 ...

  5. u-boot启动流程

    U-Boot(Universal Bootloader)是一个通用的开源引导加载程序,通常用于嵌入式系统中,负责引导操作系统或加载 Linux 内核等任务.U-Boot的启动流程可以概括为以下几个关键 ...

  6. 【matplotlib 实战】--平行坐标系

    平行坐标系是一种统计图表,它包含多个垂直平行的坐标轴,每个轴表示一个字段,并用刻度标明范围.通过在每个轴上找到数据点的落点,并将它们连接起来形成折线,可以很容易地展示多维数据.随着数据增多,折线会堆叠 ...

  7. Debian12安装.NET7 SDK

    Debian,作为最受欢迎的 Linux 发行版之一,于 2023 年 6 月 10 日正式发布了其最新版本 Debian 12,代号"Bookworm".Debian 12 带来 ...

  8. MySQL快速导入千万条数据(1)

    目录 一.命令行导入方式 二.LOAD DATA导入方式 对于传统的关系数据库如oracle,在大量数据导入方面的效率,我们一般有一个大概的认知,即1分钟以内可以导入千万条数据,而对于MySQL数据库 ...

  9. 流水线中便捷迭代,鲲鹏DevKit 23.0新能力抢先看

    本文分享自华为云社区<鲲鹏DevKit 23.0:流水线中便捷迭代鲲鹏版本,迁移.开发.调优无缝衔接>,作者:华为云社区精选 . 数字时代,海量的行业应用驱动着多样性算力的飞速发展,以鲲鹏 ...

  10. poj2279

    Mr. Young's Picture Permutations Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 5841   ...