首页 > 其他分享 >超轻量级MP4封装方法介绍

超轻量级MP4封装方法介绍

时间:2023-12-27 13:45:33浏览次数:57  
标签:__ search 封装 0x00 MP4 file 轻量级 size

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 公众号

标签:__,search,封装,0x00,MP4,file,轻量级,size
From: https://www.cnblogs.com/liwen01/p/17925462.html

相关文章

  • stream的优化:java封装的拆箱与装箱的弊端
    authors.stream().map(author->author.getAge).map(age->age+10)//Stream<Integer>.filter(age>18)//Stream<Integer>.foreach(System.out::println);上述是一个简单的stream流的使用,当我们拆开第二个map,或者filter的时候会发现,传入和传出的参数都是Intege......
  • python opencv保存摄像头视频为.mp4格式
     importcv2#0代表的是电脑上的默认摄像头cap=cv2.VideoCapture(0)#创建VideoWriter对象,第二个参数是帧率,第三个参数是视频的宽度和高度,第四个参数是输出视频的格式out=cv2.VideoWriter('output.mp4',cv2.VideoWriter_fourcc(*'mp4v'),20.0,(640,480))while(cap......
  • 从零开始封装 vue 组件
    对于学习Vue的同学来说,封装vue组件是实现代码复用的重要一环。在Vue官网中非常详细地介绍了vue组件的相关知识,我这里简单摘取使用最频繁的几个知识点,带大家快速入门vue组件的使用。快速入门我们假设在页面上有很多地方都要用到一个计数器,与其在每个地方都实现计数器功能,......
  • axios之基本封装
    1.axios实例安装axios库npminstallaxiosoryarnaddaxiosorCDN<scriptsrc="https://unpkg.com/axios/dist/axios.min.js"></script>//引入importaxiosfrom'axios'//库importCookiesfrom'js-cookie'import{Notifi......
  • 【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志
    目录修整目前的系列目录(后面会根据实际情况变动):在windows11上编译python将python注入到其他进程并运行注入Python并使用ctypes主动调用进程内的函数和读取内存结构体调用汇编引擎实战发送文本和图片消息(支持32位和64位微信)允许Python加载运行py脚本且支持热加载利用......
  • EasyCVR如何添加fmp4播放流协议?
    近期有用户反馈,想在EasyCVR中加上MP4播放流,今天我们来分享一下具体步骤。主要操作如下:1)首先在easycvr.ini配置文件中添加MP4;2)在EasyCVR代码读写配置文件中添加fmp4,再将所有的all_media_type字段都修改,并添加fmp4的信息;3)根据以下前端请求接口,即可拿到fmp4的流配置参数:4)随后在接口中......
  • EasyCVR视频监控系统/智能监控方案平台如何添加fmp4播放流协议?
    视频监控/GB28181视频管理平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,视频安防监控汇聚平台EasyCVR可支持1、4、9、16个画面窗口播放,可同时播放多路视频流,也能支持视频定时轮播。视频监控汇聚平台EasyCVR支持多种播放协议,......
  • 轻量级用户模式线程
    Loom项目通过引入称为纤程的轻量级用户模式线程,在Java中引入了一种新颖的并发方法。与传统线程不同,纤维非常轻,并且可以大量生成,而不会产生相同的开销。这项创新旨在简化Java中的并发性,使开发人员更容易编写可扩展且高效的并发代码。纤维模型简单来说,纤程可以看作是在常规Ja......
  • Avalonia播放视频(mp4)
    https://blog.csdn.net/confused_kitten/article/details/134332261 1.Nuget添加类库Dove.Avalonia.Extensions.Media,项目路径https://github.com/michael-eddy/Avalonia.Extensions/2.Nuget添加VideoLAN.LibVLC.WindowsPlatformLibVLCPackageMinimumOSVersionWindows V......
  • Android平台RTMP推送|轻量级RTSP服务能力封装代码实现
    好多开发者问我们,有没有针对Android平台RTMP直播推送、轻量级RTSP服务模块的进一步封装,可以更便捷的调用大牛直播SDK接口。为此,我们分享下我们针对Android平台SmartPublisher做的二次封装代码:packagecom.daniulive.smartpublisher;importandroid.util.Log;importjava.nio.By......