5

超轻量级MP4封装方法介绍 - liwen01

 8 months ago
source link: https://www.cnblogs.com/liwen01/p/17925462.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

超轻量级MP4封装方法介绍



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


Recommend

  • 71
    • streamable.com 6 years ago
    • Cache

    2018-02-22_15-42-45.mp4 - Streamable

    Check out this video on Streamable using your phone, tablet or desktop.

  • 40
    • www.cocoachina.com 5 years ago
    • Cache

    iOS封装一个轻量级的顶部分类控件

    过年前后都好久没有写东西了,都在忙新的项目,目前新项目也快完了,所以准备把项目中写的部分控件封装出来,在这两个项目中都用到了顶部滚动的分类视图,所以我想把它封装出来方便以后使用,虽然这类控件网上应该也有不少,但是我觉得在能力范围内还是自己尝试一...

  • 8

    by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=8622 本文可全文转载,个人网站无需授权,但需要保留原作者、出处以及文中链接,...

  • 9

    Record iOS Simulator video as mp4 and GIF with Xcode Record a video or GIF of your app is an essential task than you might think. You might need it for a marketing team to make an

  • 48

    JavaScript MP4 Reader/FragmenterMP4Box.js / ISOBMFF Box Structure Viewer (see other demos) Client-side tool: files are processed locally in your browser and not uploaded to any...

  • 5

    Matrix 首页推荐Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。文章代...

  • 12

    Everyone at some point wanted or would like to convert mp4/mpv/WebM files to mp3 on Linux. If you would like to achieve that on any Linux system, don’t worry because I have a good solution for you....

  • 7
    • www.tmtpost.com 2 years ago
    • Cache

    FTNDEX--数字引擎时代机遇.mp4

    FTNDEX--数字引擎时代机遇.mp4 · 33分钟前 FTNDEX--数字引擎时代机遇

  • 6

    目前主要的测距方式有:光学测距,超声波和微波雷达测距。 光学测距又可以分为:双目,结构光,ToF。微波雷达,在消费类产品中,常见的是波长在毫米级别的毫米波雷达。超声波应用比较多的是在车载倒车雷达上。 它们...

  • 5

    在国内做产品设计开发,很难避免被抄袭,被仿照。在没有形成技术壁垒之前,如何防止产品被抄袭是一个不可回避的问题。 (一)常规设备 常规设备主要的防护手段有: 加密保护代码 持续更...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK