存档

文章标签 ‘MP4’

MOV及MP4文件格式中几个重要的Table

2013年12月17日
MOV及MP4文件格式中几个重要的Table已关闭评论

MP4,全称是MPEG4 Part 14,是一种使用MPEG-4的多媒体文件格式,扩展名为.mp4。

MOV,是QuickTime影片格式,它是Apple公司开发的一种音频、视频文件格式,用于存储常用数字媒体类型。其扩展名为.mov。

在MOV和MP4文件格式中包括几个重要的Table,对应的atoms分别为:stts、ctts、stss、stsc、stsz以及stco/co64。

1、Sample时间表stts

stts:Time-To-Sample Atoms,存储了媒体sample的时常信息,提供了时间和相关sample之间的映射关系。该atom包含了一个表,关于time和sample号之间的索引关系。表的每个entry给出了具有相同时间间隔的连续的sample的个数和这些sample的时间间隔值。将这些时间间隔相加在一起,就可以得到一个完整的time与sample之间的映射。将所有的时间间隔相加在一起,就可以得到该track的时间总长。

每个sample的显示时间可以通过如下的公式得到:

D(n+1) = D(n) + STTS(n)

其中,STTS(n)是sample n的时间间隔,包含在表格中;D(n)是sample n的显示时间。

Time-To-Sample的table entry布局如图1-1所示:

图1-1 Time-To-Sample的table entry布局

Table entries根据每个sample在媒体流中的顺序和时长对他们进行描述。如果连续的samples有相同的时长,他们会被放在同一个table entry中。特别的,如果所有的sample具有相同的时长,那么table中就只有一个entry。

一个简单的例子如图1-2所示。这个媒体流包括9个samples,通过3个entries来描述。需要说明的一点是,这里的entry和 chunk不是对应的。比如,sample 4、5和6在同一个chunk中,但是,由于他们的时长不一样,sample 4的时长为3,而sample 5和6的时长为1,因此,通过不同的entry来描述。

图1-2 关于Time-To-Sample的一个简单例子

2、时间合成偏移表ctts

ctts:Composition Offset Atom。每一个视频sample都有一个解码顺序和一个显示顺序。对于一个sample来说,解码顺序和显示顺序可能不一致,比如H.264格式,因此,Composition Offset Atom就是在这种情况下被使用的。

(1)如果解码顺序和显示顺序是一致的,Composition Offset Atom就不会出现。Time-To-Sample Atoms既提供了解码顺序也提供了显示顺序,并能够计算出每个sample的开始时间和结束时间。

(2)如果解码顺序和显示顺序不一致,那么Time-To-Sample Atoms既提供解码顺序,Composition Offset Atom则通过差值的形式来提供显示时间。

Composition Offset Atom提供了一个从解码时间到显示时间的sample一对一的映射,具有如下的映射关系:

CT(n) = DT(n) + CTTS(n)

其中,CTTS(n)是sample n在table中的entry(这里假设一个entry只对应一个sample)可以是正值也可是负值;DT(n)是sample n的解码时间,通过Time-To-Sample Atoms计算获得;CT(n)便是sample n的显示时间。

Composition Offset Atom的table entry的布局和Time-To-Sample Atoms的一样,如图2-1所示:

图2-1 Composition Offset Atom的table entry布局

3、同步Sample表stss

stss:Sync Sample Atom,标识了媒体流中的关键帧,提供了随机访问点标记。Sync Sample Atom包含了一个table,table的每个entry标识了一个sample,该sample是媒体流的关键帧。Table中的sample号是严格按照增长的顺序排列的,如果该table不存在,那么每一个sample都可以作为随机访问点。换句话说,如果Sync Sample Atom不存在,那么所有的sample都是关键帧。

Sync Sample Table的布局如图3-1所示:

图3-1 Sync Sample Table的布局

4、Chunk中的Sample信息表stsc

stsc:Sample-To-Chunk Atom。为了优化数据访问,通常把sample封装到chunk中,一个chunk可能会包含一个或者几个sample。每个chunk会有不同的 size,每个chunk中的sample也会有不同的size。在Sample-To-Chunk Atom中包含了个table,这个table提供了从sample到chunk的一个映射,每个table entry可能包含一个或者多个chunk。Table entry包含的内容包括第一个chunk号、每个chunk包含的sample的个数以及sample的描述ID。Sample-To-Chunk Atom的table entry布局如图4-1所示。

图4-1 Sample-To-Chunk Atom的table entry布局

每个table entry包含一组chunk,enrty中的每个chunk包含相同数目的sample。而且,这些chunk中的每个sample都必须使用相同的 sample description。任何时候,如果chunk中的sample数目或者sample description改变,必须创建一个新的table entry。如果所有的chunk包含的sample数目相同,那么该table只有一个entry。

一个简单的例子,如图4-2所示。图中看不出来总共有多少个chunk,因为entry中只包含第一个chunk号,因此,对于最后一个entry,在某些情况下需要特殊的处理,因为无法判断什么时候结束。

图4-2 一个关于Sample-To-Chunk table的例子

5、Sample大小表stsz

stsz:Sample Size Atom,指定了每个sample的size。Sample Size Atom给出了sample的总数和一张表,这个表包含了每个sample的size。如果指定了默认的sampe size,那么这个table就不存在了。即每个sample使用这个默认的sample size。sample size table的布局如图5-1所示。

图5-1 sample size table的布局

6、Chunk的偏移量表stco/co64

stco/co64:Chunk Offset Atom,指定了每个chunk在文件中的位置。Chunk Offset Atom包含了一个table,表中的每个entry给出了每个chunk在文件中的位置。有两种形式来表示每个entry的值,即chunk的偏移量,32位和64位。如果Chunk Offset Atom的类型为stco,则使用的是32位的,如果是co64,那么使用的就是64位的。chunk offset table的布局如图6-1所示。

图6-1 chunk offset table的布局

需要注意的是,table中只是给出了每个chunk的偏移量,并没有给出每个sample的偏移量。因此,如果要获得每个sample的偏移量,还需要用到Sample Size Table和Sample-To-Chunk Table。

本文来自:http://blog.csdn.net/yu_yuan_1314/article/details/9078287

IT技术 , , , ,

mp4 格式 解析

2013年12月17日
mp4 格式 解析已关闭评论

感谢新浪播客可下人间!

转载地址:http://blog.sina.com.cn/s/blog_48f93b530100jz4b.html

目前MP4的概念被炒得很火,也很乱。最开始MP4指的是音频(MP3的升级版),即MPEG-2 AAC标准。随后MP4概念被转移到视频上,对应的是MPEG-4标准。而现在我们流行的叫法,多半是指能播放MPEG-4标准编码格式视频的播放器。但是这篇文章介绍的内容跟上面这些都无关,我们要讨论的是MP4文件封装格式,对应的标准为ISO/IEC 14496-12,即信息技术 视听对象编码的第12部分:ISO 基本媒体文件格式(Information technology Coding of audio-visual objects Part 12: ISO base media file format)。ISO/IEC组织指定的标准一般用数字表示,ISO/IEC 14496即MPEG-4标准。

MP4视频文件封装格式是基于QuickTime容器格式定义的,因此参考QuickTime的格式定义对理解MP4文件格式很有帮助。MP4文件格式是一个十分开放的容器,几乎可以用来描述所有的媒体结构,MP4文件中的媒体描述与媒体数据是分开的,并且媒体数据的组织也很自由,不一定要按照时间顺序排列,甚至媒体数据可以直接引用其他文件。同时,MP4也支持流媒体。MP4目前被广泛用于封装h.264视频和AAC音频,是高清视频的代表。

现在我们就来看看MP4文件格式到底是什么样的。

1、概述

MP4文件中的所有数据都装在box(QuickTime中为atom)中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将 box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。

下面是一些概念:

track  表示一些sample的集合,对于媒体数据来说,track表示一个视频或音频序列。

hint track  这个特殊的track并不包含媒体数据,而是包含了一些将其他数据track打包成流媒体的指示信息。

sample  对于非hint track来说,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频,它们统称sample。对于hint track,sample定义一个或多个流媒体包的格式。

sample table  指明sampe时序和物理布局的表。

chunk 一个track的几个sample组成的单元。

在本文中,我们不讨论涉及hint的内容,只关注包含媒体数据的本地MP4文件。下图为一个典型的MP4文件的结构树。

mp4 格式 解析(1) - mu_huilin - 坐看云起

2、Box

首先需要说明的是,box中的字节序为网络字节序,也就是大端字节序(Big-Endian),简单的说,就是一个32位的4字节整数存储方式为高位字节在内存的低端。Box由header和body组成,其中header统一指明box的大小和类型,body根据类型有不同的意义和格式。

标准的box开头的4个字节(32位)为box size,该大小包括box header和box body整个box的大小,这样我们就可以在文件中定位各个box。如果size为1,则表示这个box的大小为large size,真正的size值要在largesize域上得到。(实际上只有“mdat”类型的box才有可能用到large size。)如果size为0,表示该box为文件的最后一个box,文件结尾即为该box结尾。(同样只存在于“mdat”类型的box中。)

size后面紧跟的32位为box type,一般是4个字符,如“ftyp”、“moov”等,这些box type都是已经预定义好的,分别表示固定的意义。如果是“uuid”,表示该box为用户扩展类型。如果box type是未定义的,应该将其忽略。

3、File Type Box(ftyp)

该box有且只有1个,并且只能被包含在文件层,而不能被其他box包含。该box应该被放在文件的最开始,指示该MP4文件应用的相关信息。

“ftyp” body依次包括1个32位的major brand(4个字符),1个32位的minor version(整数)和1个以32位(4个字符)为单位元素的数组compatible brands。这些都是用来指示文件应用级别的信息。该box的字节实例如下:

mp4 格式 解析(1) - mu_huilin - 坐看云起

4、Movie Box(moov)

该box包含了文件媒体的metadata信息,“moov”是一个container box,具体内容信息由子box诠释。同File Type Box一样,该box有且只有一个,且只被包含在文件层。一般情况下,“moov”会紧随“ftyp”出现。

一般情况下(限于篇幅,本文只讲解常见的MP4文件结构),“moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现(对于其他container box来说,header box都应作为首个子box出现)。“trak”包含了一个track的相关信息,是一个container box。下图为部分“moov”的字节实例,其中红色部分为box header,绿色为“mvhd”,黄色为一部分“trak”。

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.1 Movie Header Box(mvhd)

“mvhd”结构如下表。

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

creation time

4

创建时间(相对于UTC时间1904-01-01零点的秒数)

modification time

4

修改时间

time scale

4

文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数

duration

4

该 track的时间长度,用duration和time scale值可以计算track时长,比如audio track的time scale = 8000, duration = 560128,时长为70.016,video track的time scale = 600, duration = 42000,时长为70

rate

4

推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16] 格式,该值为1.0(0x00010000)表示正常前向播放

volume

2

与rate类似,[8.8] 格式,1.0(0x0100)表示最大音量

reserved

10

保留位

matrix

36

视频变换矩阵

pre-defined

24

next track id

4

下一个track使用的id号

“mvhd”的字节实例如下图,各字段已经用颜色区分开:

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2 Track Box(trak)

“trak”也是一个container box,其子box包含了该track的媒体数据引用和描述(hint track除外)。一个MP4文件中的媒体可以包含多个track,且至少有一个track,这些track之间彼此独立,有自己的时间和空间信息。 “trak”必须包含一个“tkhd”和一个“mdia”,此外还有很多可选的box(略)。其中“tkhd”为track header box,“mdia”为media box,该box是一个包含一些track媒体数据信息box的container box。

“trak”的部分字节实例如下图,其中黄色为“trak”box的头,绿色为“tkhd”,蓝色为“edts”(一个可选box),红色为一部分“mdia”。

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.1 Track Header Box(tkhd)

“tkhd”结构如下表。

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

按位或操作结果值,预定义如下:

0x000001 track_enabled,否则该track不被播放;

0x000002 track_in_movie,表示该track在播放中被引用;

0x000004 track_in_preview,表示该track在预览时被引用。

一般该值为7,如果一个媒体所有track均未设置track_in_movie和track_in_preview,将被理解为所有track均设置了这两项;对于hint track,该值为0

creation time

4

创建时间(相对于UTC时间1904-01-01零点的秒数)

modification time

4

修改时间

track id

4

id号,不能重复且不能为0

reserved

4

保留位

duration

4

track的时间长度

reserved

8

保留位

layer

2

视频层,默认为0,值小的在上层

alternate group

2

track分组信息,默认为0表示该track未与其他track有群组关系

volume

2

[8.8] 格式,如果为音频track,1.0(0x0100)表示最大音量;否则为0

reserved

2

保留位

matrix

36

视频变换矩阵

width

4

height

4

高,均为 [16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示宽高

“tkhd”的字节实例如下图,各字段已经用颜色区分开:

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.2 Media Box(mdia)

“mdia”也是个container box,其子box的结构和种类还是比较复杂的。先来看一个“mdia”的实例结构树图

mp4 格式 解析(1) - mu_huilin - 坐看云起

总体来说,“mdia”定义了track媒体类型以及sample数据,描述sample信息。一般“mdia”包含一个“mdhd”,一个“hdlr”和一个“minf”,其中“mdhd”为media header box,“hdlr”为handler reference box,“minf”为media information box。下面依次看一下这几个box的结构。

4.2.2.1 Media Header Box(mdhd)

“mdhd”结构如下表。

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

creation time

4

创建时间(相对于UTC时间1904-01-01零点的秒数)

modification time

4

修改时间

time scale

4

同前表

duration

4

track的时间长度

language

2

媒体语言码。最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义)

pre-defined

2

“mdhd”的字节实例如下图,各字段已经用颜色区分开:

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.2.2 Handler Reference Box(hdlr)

“hdlr”解释了媒体的播放过程信息,该box也可以被包含在meta box(meta)中。“hdlr”结构如下表。

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

pre-defined

4

handler type

4

在media box中,该值为4个字符:

“vide”— video track

“soun”— audio track

“hint”— hint track

reserved

12

name

不定

track type name,以‘\0’结尾的字符串

“hdlr”的字节实例如下图,各字段已经用颜色区分开:

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.2.3 Media Information Box(minf)

“minf”存储了解释track媒体数据的handler-specific信息,media handler用这些信息将媒体时间映射到媒体数据并进行处理。“minf”中的信息格式和内容与媒体类型以及解释媒体数据的media handler密切相关,其他media handler不知道如何解释这些信息。“minf”是一个container box,其实际内容由子box说明。

一般情况下,“minf”包含一个header box,一个“dinf”和一个“stbl”,其中,header box根据track type(即media handler type)分为“vmhd”、“smhd”、“hmhd”和“nmhd”,“dinf”为data information box,“stbl”为sample table box。下面分别介绍。

下图为“minf”部分字节实例,其中红色为box header,蓝色为“smhd”,绿色为“dinf”,黄色为一部分“stbl”。

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.2.3.1 Media Information Header Box(vmhd、smhd、hmhd、nmhd)

Video Media Header Box(vmhd)

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

graphics mode

4

视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成

opcolor

2×3

{red,green,blue}

Sound Media Header Box(smhd)

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

balance

2

立体声平衡,[8.8] 格式值,一般为0,-1.0表示全部左声道,1.0表示全部右声道

reserved

2

Hint Media Header Box(hmhd)

Null Media Header Box(nmhd)

非视音频媒体使用该box,略。

4.2.2.3.2 Data Information Box(dinf)

“dinf”解释如何定位媒体信息,是一个container box。“dinf”一般包含一个“dref”,即data reference box;“dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。简单的说,track可以被分成若干段,每一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track。一般情况下,当数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的。

“dref”的字节结构如下表。

字段

字节数

意义

box size

4

box大小

box type

4

box类型

version

1

box版本,0或1,一般为0。(以下字节数均按version=0)

flags

3

entry count

4

“url”或“urn”表的元素个数

“url”或“urn”列表

不定

“url”或“urn”都是box,“url”的内容为字符串(location string),“urn”的内容为一对字符串(name string and location string)。当“url”或“urn”的box flag为1时,字符串均为空。

下面是一个“dinf”的字节实例图。其中黄色为“dinf”的box header,由红色部分我们知道包含的“url”或“urn”个数为1,红色后面为“url”box的内容。紫色为“url”的box header(根据box type我们知道是个“url”),绿色为box flag,值为1,说明“url”中的字符串为空,表示track数据已包含在文件中。

mp4 格式 解析(1) - mu_huilin - 坐看云起

4.2.2.3.3 Sample Table Box(stbl)

“stbl”几乎是普通的MP4文件中最复杂的一个box了,首先需要回忆一下sample的概念。sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同,如下图所示。

mp4文件格式解析(三)

“stbl”包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“stbl”是一个container box,其子box包括:sample description box(stsd)、time to sample box(stts)、sample size box(stsz或stz2)、sample to chunk box(stsc)、chunk offset box(stco或co64)、composition time to sample box(ctts)、sync sample box(stss)等。

“stsd”必不可少,且至少包含一个条目,该box包含了data reference box进行sample数据检索的信息。没有“stsd”就无法计算media sample的存储位置。“stsd”包含了编码的信息,其存储的信息随媒体类型不同而不同。

Sample Description Box(stsd)

box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。

视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。

Time To Sample Box(stts)

“stts” 存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。

Sample Size Box(stsz)

“stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。

Sample To Chunk Box(stsc)

用chunk组织sample可以方便优化数据获取,一个thunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample。

Sync Sample Box(stss)

“stss” 确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。 “stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个 sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。

Chunk Offset Box(stco)

“stco” 定义了每个thunk在媒体流中的位置。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。

5、Free Space Box(free或skip)

“free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。

6、Meida Data Box(mdat)

该box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。数据直接跟在box type字段后面,具体数据结构的意义需要参考metadata(主要在sample table中描述)。

普通MP4文件的结构就讲完了,可能会比较乱,下面这张图是常见的box的树结构图,可以用来大致了解MP4文件的构造。

mp4 格式 解析(1) - mu_huilin - 坐看云起

本文完。

再次感谢可下人间。

IT技术 , ,

使用mp4v2将H264+AAC合成mp4文件

2013年8月14日
使用mp4v2将H264+AAC合成mp4文件已关闭评论

摘自:http://www.cnblogs.com/chutianyao/archive/2012/04/13/2446140.html

录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是:

(1)接收并解析RTP包,分离出H264和AAC数据流;

(2)将H264视频和AAC音频以某种格式封装,最后存成文件,供用户查看。

第一步已经有部分代码可供参考,因此很快就完成了。

第二步,我们决定封装成mp4,查找了一些资料后,决定使用开源库mp4v2来合成mp4文件。

技术路线已确定,就开工干活。

(一)mp4格式的基础知识。

关于mp4格式,网上介绍的不少,有以下内容可供参考:

(1)两个ISO标准:

[ISO/IEC 14496-12]:ISO base media file format –”is a general format forming the basis for a number of other more specific file formats. This format contains the timing, structure, and media information for timed sequences of media data, such as audio-visual presentations ”

[ISO/IEC 14496-14]:MP4 file format –”This specification defines MP4 as an instance of the ISO Media File format [ISO/IEC 14496-12 and ISO/IEC
15444-12]. ”

定义了mp4文件格式标准。

(2)http://wenku.baidu.com/view/673482284b73f242336c5f4c.html

是上面两个标准的解释,建议先看这个,了解大概,具体细节再看ISO标准文件。

(二)技术验证。主要就是写验证代码,验证技术可行性。

去官网下载mp4v2源码、编译、安装过程略过不提。所有资料可以在http://code.google.com/p/mp4v2/找到。

先写部分验证代码,很快完成了,但封装出来的文件有问题,无法播放。

合成部分,代码如下:

复制代码

 1 static void* writeThread(void* arg)
 2 {
 3     rtp_s* p_rtp = (rtp_s*) arg;
 4     if (p_rtp == NULL)
 5     {
 6         printf("ERROR!\n");
 7         return;
 8     }
 9 
10     MP4FileHandle file = MP4CreateEx("test.mp4", MP4_DETAILS_ALL, 0, 1, 1, 0, 0, 0, 0);//创建mp4文件
11     if (file == MP4_INVALID_FILE_HANDLE)
12     {
13         printf("open file fialed.\n");
14         return;
15     }
16 
17     MP4SetTimeScale(file, 90000);
18 
19     //添加h264 track    
20     MP4TrackId video = MP4AddH264VideoTrack(file, 90000, 90000 / 25, 320, 240,
21                                             0x64, //sps[1] AVCProfileIndication
22                                             0x00, //sps[2] profile_compat
23                                             0x1f, //sps[3] AVCLevelIndication
24                                             3); // 4 bytes length before each NAL unit
25     if (video == MP4_INVALID_TRACK_ID)
26     {
27         printf("add video track failed.\n");
28         return;
29     }
30     MP4SetVideoProfileLevel(file, 0x7F);
31 
32     //添加aac音频
33     MP4TrackId audio = MP4AddAudioTrack(file, 48000, 1024, MP4_MPEG4_AUDIO_TYPE);
34     if (video == MP4_INVALID_TRACK_ID)
35     {
36         printf("add audio track failed.\n");
37         return;
38     }
39     MP4SetAudioProfileLevel(file, 0x2);
40 
41 
42     int ncount = 0;
43     while (1)
44     {
45         frame_t* pf = NULL; //frame
46         pthread_mutex_lock(&p_rtp->mutex);
47         pf = p_rtp->p_frame_header;
48         if (pf != NULL)
49         {
50             if (pf->i_type == 1)//video
51             {
52                MP4WriteSample(file, video, pf->p_frame, pf->i_frame_size, MP4_INVALID_DURATION, 0, 1);
53              }
54             else if (pf->i_type == 2)//audio
55             {
56                 MP4WriteSample(file, audio, pf->p_frame, pf->i_frame_size , MP4_INVALID_DURATION, 0, 1);
57             }
58 
59             ncount++;
60 
61             //clear frame.
62             p_rtp->i_buf_num--;
63             p_rtp->p_frame_header = pf->p_next;
64             if (p_rtp->i_buf_num <= 0)
65             {
66                 p_rtp->p_frame_buf = p_rtp->p_frame_header;
67             }
68             free_frame(&pf);
69             pf = NULL;
70 
71             if (ncount >= 1000)
72             {
73                 break;
74             }
75         }
76         else
77         {
78             //printf("BUFF EMPTY, p_rtp->i_buf_num:%d\n", p_rtp->i_buf_num);
79         }
80         pthread_mutex_unlock(&p_rtp->mutex);
81         usleep(10000);
82     }
83 
84     MP4Close(file);
85 }

复制代码

现象:没有图像,也没有声音,根本无法播放。

于是,艰苦的工作开始了:跟踪查找原因。

(1)使用 vlc播放合成的mp4文件,查看详细输出:

复制代码

1 vlc -vvv test.mp4
2 [0x8e9357c] mp4 stream debug: found Box: ftyp size 24 
3 [0x8e9357c] mp4 stream debug: found Box: free size 136 
4 [0x8e9357c] mp4 stream debug: skip box: "free" 
5 [0x8e9357c] mp4 stream debug: found Box: mdat size 985725 
6 [0x8e9357c] mp4 stream debug: skip box: "mdat" 
7 [0x8e9357c] mp4 stream debug: found Box: moov size 5187 
8 [0x8e9357c] mp4 stream debug: found Box: mvhd size 108 
9 [0x8e9357c] mp4 stream debug: read box: "mvhd" creation 734515d-06h:22m:03s modification 734515d-06h:22m:23s time scale 90000 duration 694977d-48h:00m:29s rate 1.000000 volume 1.000000 next track id 3 

复制代码

可以看到vlc(实际上是调用libmp4库)解析box都正确的,mdat的大小也是正确的。

但接下来一行:

skip box: "mdat"

就比较奇怪了,明明解析正确了,为什么要将mdat忽略掉呢?要知道,mdat里存放的可是真正的音视频数据阿?如果skip掉了,后面解码时没有数据,当然播放不了了?

(2)既然找到疑点,继续跟踪。

查看vlc的源代码,在文件modules/demux/mp4/libmp4.c中发现:skip信息是由MP4_ReadBoxSkip()函数打印的,而调用的地方在libmp4.c中2641行:

复制代码

 1 /* Nothing to do with this box */
 2 { FOURCC_mdat,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },
 3 { FOURCC_skip,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },{ FOURCC_free,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },
 4 { FOURCC_wide,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },
 5 
 6 而在libmp4.h中:
 7 #define FOURCC_mdat VLC_FOURCC( 'm', 'd', 'a', 't' )
 8 #define FOURCC_skip VLC_FOURCC( 's', 'k', 'i', 'p' )
 9 #define FOURCC_free VLC_FOURCC( 'f', 'r', 'e', 'e' )
10 #define FOURCC_wide VLC_FOURCC( 'w', 'i', 'd', 'e' )

复制代码

从代码看,vlc调用libmp4解析文件时,主动忽略了mdat,skip,free,wide这四种类型的box。

为什么呢?

(3)继续查看modules/demux/mp4/mp4.c中的Open()函数(解析模块的入口函数),可以看到本模块的主要工作是初始化一个demux_sys_t结构体,该结构体定义如下:

复制代码

 1 struct demux_sys_t
 2 {
 3     MP4_Box_t    *p_root;      /* container for the whole file */
 4     mtime_t      i_pcr;
 5     uint64_t     i_time;         /* time position of the presentation * in movie timescale */
 6     uint64_t     i_timescale;    /* movie time scale */
 7     uint64_t     i_duration;     /* movie duration */
 8     unsigned int i_tracks;       /* number of tracks */
 9     mp4_track_t  *track;         /* array of track */
10     float        f_fps;          /* number of frame per seconds */
11 
12     /* */
13     MP4_Box_t    *p_tref_chap;
14 
15     /* */
16     input_title_t *p_title;
17 };

复制代码

似乎只是为了获取mp4的tracks,moov,duration, timescale等基本信息,实际上并不解码数据,因此就不需要关注mdat这个box了。

综上:vlc的输出是正常的,libmp4忽略了mdat这个box也不是造成mp4文件无法播放的原因,只是因为libmp4这个模块并不真正解码数据,所以不需要关注这个box。

既然问题不在这,那在哪里呢?

(4)继续看vlc的输出:

AVC: nal size -1710483062

no frame!

[0x8e93eb4] avcodec decoder warning: cannot decode one frame (3595 bytes)

可以看到,vlc实际上是调用avcodec(ffmpeg)来解码数据的,我们的视频是AVC(H264)格式的。

从错误信息可以确定,是H264的NAL大小错误,似乎跟mp4文件本身关系不大。

不管那么多,先看看代码再说。

vlc是以lib的形式使用ffmpeg的,所以我们必须看ffmpeg的代码:

复制代码

libavcodec/h264.c:
static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size){

    ….

    for(;;){
        if(buf_index >= next_avc) {
            if(buf_index >= buf_size) break;
            nalsize = 0;
            for(i = 0; i < h->nal_length_size; i++)
                nalsize = (nalsize << 8) | buf[buf_index++];
            if(nalsize <= 0 || nalsize > buf_size - buf_index){
                av_log(h->s.avctx, AV_LOG_ERROR, "AVC: nal size %d\n", nalsize);
                break;
            }
            next_avc= buf_index + nalsize;
        } 

…

}

复制代码

可以看到,正是这里报错的。

但是,为什么报错呢?根据ffmpeg的信息,知道取出来的 nalsize为负数。

怀疑是h264流本身有问题,于是用Elecard查看了生成的mp4文件,视频播放又非常正常。似乎h264流是正常的?

愁呀愁。。。。

(5)在mp4v2的论坛上找到一个帖子,与我的情况非常类似:

http://groups.google.com/group/mp4v2/browse_thread/thread/a4ac6824e585f003/0169c48511f5827a?lnk=gst&q=avc#0169c48511f5827a

内容如下:

Ottavio Campana

“question about MP4AddH264VideoTrack。

What’s the meaning of the profile_compat and

sampleLenFieldSizeMinusOne fields?”

Jeremy Noring

"Usually an NALU is prefixed by the start code 0x00000001. To write it

as a sample in MP4 file format, just replace the start code with size

of the NALU(without 4-byte start code) in big endian. You also need to

specify how many bytes of the size value requires. Take libmp4v2 for

example, the last parameter in MP4AddH264VideoTrack(.., uint8_t

sampleLenFieldSizeMinusOne) indicate the number of byes minus one."

…so each sample you and to mp4v2 should be prefixed with a size code

(in big-endian, of course). I use a 4 byte size code, so

sampleLenFieldSizeMinusOne gets set to 3. This seems to work; my

files playback on just about everything. Perhaps one of the project

maintainers can clarify this, and it’d also be good to update the

documentation of that call to make this clear.”

Ottavio Campana

that’s the code I used as reference to write my program 🙁

but my doubt is that there must be something wrong somewhere, because

boxes seem to be correctly written, but when I try to decode them I

get errors like

[h264 @ 0xb40fa0]AVC: nal size -502662121

have you ever seen an error like this?

Ottavio Campana

> Not sure, but it looks you’re not converting it to big-endian before

> prefixing it to your sample.

well, eventually using ffmpeg to dump the read frames, I discovered

that I had to strip che NALU start code, i.e. the 0x00000001, and to

put the NALU size at its place.

It works perfectly now, but I still wonder why I had to put the size

at the begin of the data, since it is a parameter which is passed to

MP4WriteSample, so I expected the function to add it.

从中得到如下关键信息:

(1)h264流中的NAL,头四个字节是0x00000001;

(2)mp4中的h264track,头四个字节要求是NAL的长度,并且是大端顺序;

(3)mp4v2很可能针对此种情况并没有做处理,所以写到mp4文件中的每个NAL头四个字节还是0x00000001.

那好说,我将每个sample(也就是NAL)的头四个字节内容改成NAL的长度,且试试看:

复制代码

if(pf->i_frame_size >= 4)
{
    uint32_t* p = (&pf->p_frame[0]);
      *p = htonl(pf->i_frame_size -4);//大端,去掉头部四个字节
}
MP4WriteSample(file, video, pf->p_frame, pf->i_frame_size,     MP4_INVALID_DURATION, 0, 1);

复制代码

测试下来,果然OK了!

(6)视频已经解决了,音频还有问题:播放的声音太快。

尝试调整参数:

MP4TrackId audio = MP4AddAudioTrack(file, 48000, 1024, MP4_MPEG4_AUDIO_TYPE);

第三个参数sampleDuration,表示每个sample持续多少个duration,网上看到的都是1024。

我尝试了几个不同的值:128,256,512,4096都不行,最后发现设为2048就正常了。

(为什么是2048??????我不清楚,也许是因为我们的音频是双声道?有时间再研究。。。)

正确代码如下:

复制代码

MP4TrackId audio = MP4AddAudioTrack(file, 48000, 2048, MP4_MPEG4_AUDIO_TYPE);

复制代码

至此,已经成功的将rtp流合成了mp4文件,证明了技术上是可行的。

关于该demo,可以在这里 https://github.com/ZhengfengRao/rtp2mp4 看到。

注意:很多参数都是针对我们的具体应用写死的,仅供参考。

(三)将功能合并到录制程序中。

略。

IT技术 , , ,