(转) mp4编码全介绍 (二)

Mp4文件格式

 

1. Mp4文件格式简介 

1.1概述

1.2媒体文件的物理结构

1.3常见的box的树结构图

2. Mp4文件存储结构

 2.1 Mp4文件组成 

 2.2. 文件类型包 

2.3媒体数据包

2.4影片包

2.5影片头包

2.6轨迹包

2.7轨迹头包

2.8媒体包

2.9媒体头包

2.10处理器引用包

2.11媒体信息包

2.12媒体信息头包

2.13数据信息包

2.14例子表包

3.Mp4文件信息获取及定位随机播放点

4.Mp4流文件的组成机制

5. 简单MP4制作文档的参考例子

6asfmp4

7参考文献

 

 

 

 

 

 

 

 

 

 

 

Mp4文件格式简介

 

 

1.1概述

 

MP4是遵循MPEG-4ISO 14496-14的官方容器格式定义的广义文件扩展名。

Mp4流媒体化并支持众多多媒体的内容(多音轨(multiple audio)、视频流(video)、字幕(subtitlestreams)、图片(pictures)、可变桢率(variable-framerates)、码率(bitrates)、采样率(samplerates)等)和高级内容(advanced content).

Mp4文件扩展名:  .mp4

1.2媒体文件的物理结构

MP4文件中所有的数据都封装在一些box中(以前叫atom)。Box定义了如何在sample table中找到媒体数据的排列。这包括data reference(数据引用), the sample size table, the sample to chunk table, and the chunk offset table. 这些表就可以找到track中每个sample在文件中的位置和大小。

data reference允许在第二个媒体文件中找到媒体的位置。

一个track的连续几个sample组成的单元就被称为chunk。每个chunk在文件中有一个偏移量,这个偏移量是从文件开头算起的,在这个chunk内,sample是连续存储的。

这样,如果一个chunk包含两个sample,第二个sample的位置就是chunk的偏移量加上第一个sample的大小。chunk offset table说明了每个chunk的偏移量,sample to chunk table说明了sample序号和chunk序号的映射关系。

 

1.3下面两张图是常见的box的结构图。

MP4文件的构造及存储访问。

通过moov box里的子box track,里面存放着音视频的属性描述以及每个sample的索引来找到这些音视频数据。

 

 

 

 

树状目录图:

很容易看清它的层次结构

 

 

 

Mp4文件存储结构 

 

Mp4按下面包层层包装存储

2.1 Mp4文件主要有包如下:

一个MP4有且只有一个“ftyp”类型的box,有且只有一个“moov”类型的box(Movie Box),MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中

 

树结构:

 

2.2文件类型包(file-type box)

文件类型包在ISO/IEC 14496-14中定义,相当于文件头,说明了文件所使用的协议版本,编码格式等信息,其结构如下:

 

 

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

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

 

 

2.3媒体数据包(media-data box)

媒体数据包存放了音频视频和其他的数据。其结构如下:

 

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

 

2.4影片包(moov)

“moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现(对于其他container box来说,header box都应作为首个子box出现)。“trak”包含了一个track的相关信息,是一个container box。

 

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

    下图为部分“moov”的字节实例,其中红色部分为box header,绿色为“mvhd”,黄色为一部分“trak”。

 

2.5影片头包(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”的字节实例如下图,各字段已经用颜色区分开:

 

2.6 轨迹包(trak)

“trak”必须包含一个“tkhd”和一个“mdia”,此外还有很多可选的box(略)。其中“tkhd”为track header box,“mdia”为media box,该box是一个包含一些track媒体数据信息box的container box。

 

 

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

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

 

2.7 轨迹头包(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”的字节实例如下图,各字段已经用颜色区分开:

 

2.8 媒体包(mdia)

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

 

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

2.9媒体头包(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”的字节实例如下图,各字段已经用颜色区分开:

 

2.10处理器引用包(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”的字节实例如下图,各字段已经用颜色区分开:

 

2.11媒体信息包(minf)

    “minf”存储了解释track媒体数据的handler-specific信息,media handler用这些信息将媒体时间映射到媒体数据并进行处理。“ 

一般情况下,“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”。

 

2.12媒体信息头包(vmhd、smhd、hmhd、nmhd)

2.12.1Video 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}

 

 

2.12.2Sound 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

 

 

Null Media Header Box(nmhd)

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

2.13数据信息包(dinf)

“dinf”解释如何定位媒体信息,是一个container box。“dinf”一般包含一个“dref”,即data reference box;“dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。

    “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”列表

不定

 

 

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

 

2.14例子表包(stbl)

sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同,如下图所示。

 

“stbl”包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“

sample table atom 包含track中media sample的所有时间和数据索引,利用这个表,就可以定位sample到媒体时间,决定其类型,大小,以及如何在其他容器中找到紧邻的sample。

 

2.14.1Sample Description Box(stsd)

box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,根据type不同sample description会提供不同的信息。

 

track里面的每个sample通过引用关联到一个sample description。这个sample descriptions定义了怎样解码这个sample,例如使用的压缩算法。

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

sample-to-chunk atom通过这个索引表,找到合适medai中每个sample的description。

 

字段

长度(字节)

描述

尺寸

4

这个atom的字节数

类型

4

stsd

版本

1

这个atom的版本

标志

3

这里为0

条目数目

4

sample descriptions的数目

Sample description

 

不同的媒体类型有不同的sample description,但是每个sample description的前四个字段是相同的,包含以下的数据成员

尺寸

4

这个sample description的字节数

数据格式

4

存储数据的格式。

保留

6

 

数据引用索引

2

利用这个索引可以检索与当前sample description关联的数据。数据引用存储在data reference atoms。

 

2.14.2Time To Sample Box(stts)

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

DT(n+1) = DT(n) + STTS(n)

其中STTS(n)是没有压缩的STTS第n项信息,DT是第n个sample的显示时间。Sample的排列是按照时间戳的顺序,这样偏移量永远是非负的。DT一般以0开始,如果不为0,edit list atom 设定初始的DT值。DT计算公式如下

DT(i) = SUM (for j=0 to i-1 of delta(j))

所有偏移量的和就是track中media的长度,这个长度不包括media的time scale,也不包括任何edit list。

 

字段

长度(字节)

描述

尺寸

4

这个atom的字节数

类型

4

stts

版本

1

这个atom的版本

标志

3

这里为0

条目数目

4

time-to-sample的数目

time-to-sample

 

Media中每个sample的duration。包含如下结构

Sample count

4

有相同duration的连续sample的数目

Sample duration

4

每个sample的duration

 

如果多个sample有相同的duration,可以只用一项描述所有这些samples,数量字段说明sample的个数。例如,如果一个视频媒体的帧率保持不变,整个表可以只有一项,数量就是全部的帧数。

2.14.3Sample Size Box(stsz)

“stsz” 定义了每个sample的大小.

 

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

字段

长度(字节)

描述

尺寸

4

这个atom的字节数

类型

4

stsz

版本

1

这个atom的版本

标志

3

这里为0

Sample size

4

全部sample的数目。如果所有的sample有相同的长度,这个字段就是这个值。否则,这个字段的值就是0。那些长度存在sample size表中

条目数目

4

sample size的数目

sample size

 

sample size表的结构。这个表根据sample number索引,第一项就是第一个sample,第二项就是第二个sample

大小

4

每个sample的大小

 

2.14.4Sample To Chunk Box(stsc)

用chunk组织sample可以方便优化数据获取,一个thunk包含一个或多个sample。

 

“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample。

 

字段

长度(字节)

描述

尺寸

4

这个atom的字节数

类型

4

stsc

版本

1

这个atom的版本

标志

3

这里为0

条目数目

4

sample-to-chunk的数目

sample-to-chunk

 

sample-to-chunk表的结构

First chunk

4

这个table使用的第一个chunk序号

Samples per chunk

4

当前trunk内的sample数目

Sample description ID

4

与这些sample关联的sample description的序号

 

2.14.5Sync Sample Box(stss)

“stss”确定media中的关键帧。

对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。“

stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。

 

 

2.14.6Chunk Offset Box(stco)

“stco”定义了每个thunk在媒体流中的位置。

在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。

 

字段

长度(字节)

描述

尺寸

4

这个atom的字节数

类型

4

stco

版本

1

这个atom的版本

标志

3

这里为0

条目数目

4

chunk offset的数目

chunk offset

 

字节偏移量从文件开始到当前chunk。这个表根据chunk number索引,第一项就是第一个trunk,第二项就是第二个trunk

大小

4

每个sample的大小

 

2.14.7Free Space Box(free或skip)

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

   

    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

如何获取Mp4文件格式的信息

 

[mp4文件格式]获取mp4文件信息1 - 计算电影长度

方法1

从mvhd - movie header atom中找到time scale和duration,duration除以time scale即是整部电影的长度。

time scale相当于定义了标准的1秒在这部电影里面的刻度是多少。

例如audio track的time scale = 8000, duration = 560128,所以总长度是70.016,video track的time scale = 600, duration = 42000,所以总长度是70

方法2

首先计算出共有多少个帧,也就是sample(从sample size atoms中得到),然后

整部电影的duration = 每个帧的duration之和(从Time-to-sample atoms中得出)

例如audio track共有547个sample,每个sample的长度是1024,则总duration是560128,电影长度是70.016;video track共有1050个sample,每个sample的长度是40,则总duration是42000,电影长度是70

 

[mp4文件格式]获取mp4文件信息2 - 计算电影图像宽度和高度

tkhd – track header atom中找到宽度和高度即是。

 

[mp4文件格式]获取mp4文件信息3 - 计算电影声音采样频率 

 从tkhd – track header atom中找出audio track的time scale即是声音的采样频率。

 

[mp4文件格式]获取mp4文件信息4 - 计算视频帧率  

首先计算出整部电影的duration,和帧的数目然后

帧率 = 整部电影的duration / 帧的数目

 

[mp4文件格式]获取mp4文件信息5 - 计算电影的比特率

整部电影的尺寸除以长度,即是比特率,此电影的比特率为846623/70 = 12094 bps

 

[mp4文件格式]获取mp4文件信息6 - 查找sample  

使用sample atoms来处理播放流程:

当播放一部电影或者一个track的时候,对应的media handler必须能够正确的解析数据流,对一定的时间获取对应的媒体数据。如果是视频媒体, media handler可能会解析多个atom,才能找到给定时间的sample的大小和位置。具体步骤如下:

1.确定时间,相对于媒体时间坐标系统

2.检查time-to-sample atom来确定给定时间的sample序号。

3.检查sample-to-chunk atom来发现对应该sample的chunk。

4.从chunk offset atom中提取该trunk的偏移量。

5.利用sample size atom找到sample在trunk内的偏移量和sample的大小。

 

例如,如果要找第1秒的视频数据,过程如下:

1.  第1秒的视频数据相对于此电影的时间为600

2.  检查time-to-sample atom,得出每个sample的duration是40,从而得出需要寻找第600/40 = 15 + 1 = 16个sample

3.  检查sample-to-chunk atom,得到该sample属于第5个chunk的第一个sample,该chunk共有4个sample

4.  检查chunk offset atom找到第5个trunk的偏移量是20472

5.  由于第16个sample是第5个trunk的第一个sample,所以不用检查sample size atom,trunk的偏移量即是该sample的偏移量20472。如果是这个trunk的第二个sample,则从sample size atom中找到该trunk的前一个sample的大小,然后加上偏移量即可得到实际位置。

6.  得到位置后,即可取出相应数据进行解码,播放

 

 

[mp4文件格式]获取mp4文件信息7 - 查找关键帧

查找过程与查找sample的过程非常类似,只是需要利用sync sample atom来确定key frame的sample序号

1. 确定给定时间的sample序号

2. 检查sync sample atom来发现这个sample序号之后的key frame

3. 检查sample-to-chunk atom来发现对应该sample的chunk

4. 从chunk offset atom中提取该trunk的偏移量

5. 利用sample size atom找到sample在trunk内的偏移量和sample的大小

 

[mp4文件格式]获取mp4文件信息8 - Random access  

Seeking主要是利用sample table box里面包含的子box来实现的,还需要考虑edit list的影响。

可以按照以下步骤seek某一个track到某个时间T,注意这个T是以movie header box里定义的time scale为单位的:

1. 如果track有一个edit list,遍历所有的edit,找到T落在哪个edit里面。将Edit的开始时间变换为以movie time scale为单位,得到EST,T减去EST,得到T',就是在这个edit里面的duration,注意此时T'是以movie的time scale为单位的。然后将T'转化成track媒体的time scale,得到T''。T''与Edit的开始时间相加得到以track媒体的time scale为单位的时间点T'''。

2. 这个track的time-to-sample表说明了该track中每个sample对应的时间信息,利用这个表就可以得到T'''对应的sample NT。

3. sample NT可能不是一个random access point,这样就需要其他表的帮助来找到最近的random access point。一个表是sync sample表,定义哪些sample是random access point。使用这个表就可以找到指定时间点最近的sync sample。如果没有这个表,就说明所有的sample都是synchronization points,问题就变得更容易了。另一个shadow sync box可以帮助内容作者定义一些特殊的samples,它们不用在网络中传输,但是可以作为额外的random access point。这就改进了random access,同时不会影响正常的传输比特率。这个表指出了非random access point和random access point之间的关系。如果要寻找指定sample之前最近的shadow sync sample,就需要查询这个表。总之,利用sync sample和shadow sync表,就可以seek到NT之前的最近的access point sample Nap。

4. 找到用于access point的sample Nap之后,利用sample-to-chunk表来确定sample位于哪个chunk内。

5. 找到chunk后,使用chunk offset找到这个chunk的开始位置。

6. 使用sample-to-chunk表和sample size表中的数据,找到Nap在此chunk内的位置,再加上此chunk的开始位置,就找到了Nap在文件中的位置。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Mp4流文件的组成机制

一个Mp4文件可以包含所有表达的数据元,并按照一个标准规范被格式化.如果该文件包含所有的媒体数据,则它就是一个自包含文件!否则它就是一个引用文件.自包含文件只包含当前使用的核心媒体数据和信息,不包含与基于流协议之上的流相关的信息,它对数据元文件的格式的约束只限于媒体文件中的媒体数据必须能够被媒体数据的定义所描述.其它的文件可能是ISO文件,图像文件或其它格式的文件,因此这些辅助文件的格式基本上是自由格式.

 

 

 

 

 

Mp4文件创建

 

 

                 自包含文件

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                         引用其它文件数据的文件

Mp4文件格式采用面向对象技术将许多分离的内容通过轨道链接在一起,但是文件的物理结构和媒体层都不和媒体的时间顺序捆绑在一起!视频帧并不需要按时间顺序存放在文件中.这意味着需要通过描述媒体放置和时间的文件结构来引用媒体数据,这些文件结构允许但并不要求文件有时间顺序.通过数据引用可以对辅助媒体文件中的媒体定位.这样就可以利用存在分离的文件中的”库”媒体合成一个文件,而不需要将这些媒体数据复制到一个文件中,这样更适合编辑.Mp4文件格式中,整体表达文件叫做一个视频/影片.逻辑上将它划分为轨道,每个轨道代表一个时间化的媒体序列(例如,视频帧).采用上述方式,就可以将Mp4文件组织为一个对象序列.文件中的对象序列应该精确地包含一个数据元外包的表达(视频区段).它通常存在于文件的开始和结束,这样就很容易找到它.其它的对象可以存在于自由空间区段或媒体数据区段.在对象中的域首先是使用大字节数来保存,也就是网络字节顺序或big endian格式[10].此外,Mp4文件中的媒体数据并不是简单地样本与样本的交替插入,而是按一定标准将许多样本放在一个轨道中,优点是(1)可以将每个基本流分别保存在不同的文件中;(2)可以单独对每个轨道的数据进行调用.由于在Mp4中用户的数据对象只能放在视频或轨道区段.使用扩展类型的对象可以被放在一些更多种类的区段中.为了增强文件的互操作能力和功能,需要按文献[8](中的规范使用和排列Mp4文件封装结构中区段的顺序.

 

 

 

Mp4文件主要包括下面三个部分场景描述符流(BIFS)[4],对象描述符

 

OD[5] ,流和流映射表(基本流IDURL与物理位置间的映射)。其中场景描述符不依赖于底层发送技术,一个BIFS可以被用于多个不同的上下文,理论上OD流也与传输协议无关;而流映射表则与发送机制密切相关,流文件Mp4主要采用轨道技术实现对流的管理。

Mp4文件的创建

简单MP4制作文档参考例子

 

建立一个 Directshow 的接口类,其中包括 Directshow的如下接口:IGraphicBuilder, IMediaControlm, IMediaEventEx,IVideoWindowIBasicAudio IBasicVideo 和 IMediaSeeking

CEricMediaControl (类名称)

l IGraphBuilder *m_pGB

l IMediaControl *m_pMC

l IMediaEventEx *m_pME

l IVideoWindow  *m_pVW

l IBasicAudio *m_pBA

l IBasicVideo   *m_pBV

l IMediaSeeking *m_pMS

l HWND m_hOwnerWnd

Ø BOOL InitDShow(LPCTSTR strFileName ,HWND hOwnerWnd  ,HWND hNotifyWnd);

BOOL VideoRun();//暂停视频

Ø BOOL VideoPause();//停止视频

Ø BOOL VideoStop();

Ø BOOL FitVideoWindow(FLOAT fScale); //设置视频显示比例  

Ø +BOOL FullScreen();    //全屏显示   

Ø +BOOL GetFullScreenStatus();       //得到是否是全屏显示

Ø +BOOL GetMediaEvent(long *lEventCode); //得到DShow播放事件//设置播放进度,单位: 秒

Ø BOOL SetPositions(DWORD dwPos /*设置当前播放进度*/);//得到视频播放当前的位置,单位:秒

Ø BOOL GetCurrentPos(DWORD &dwPos  /*out 当前播放进度*/);

BOOL GetDuration(DWORD &dwLength);

Ø BOOL GetFullScreenStatus();       //得到是否是全屏显示

Ø BOOL GetMediaEvent(long *lEventCode); //得到DShow播放事件

//设置播放进度,单位: 秒

Ø +BOOL SetPositions(DWORD dwPos /*设置当前播放进度*/);

//得到视频播放当前的位置,单位:秒

Ø +BOOL GetCurrentPos(DWORD &dwPos  /*out 当前播放进度*/);

CEricMediaControl (类头文件定义如下)

EricMediaControl.h(用如下代码规换掉原来的代码)

#pragma once

#include <dshow.h>

#include <Winbase.h>

//定义DSHOW 事件通知消息

#define WM_GRAPHNOTIFY  WM_USER + 101

class CEricMediaControl

{

public:

CEricMediaControl(void);

~CEricMediaControl(void);

private:

//DSHOW 接口

IGraphBuilder *m_pGB ;  

IMediaControl *m_pMC ;

IMediaEventEx *m_pME ;

IVideoWindow  *m_pVW ;

IBasicAudio *m_pBA ;

IBasicVideo   *m_pBV ;

IMediaSeeking *m_pMS ;

 

//显示视频的窗口句柄

HWND m_hOwnerWnd;

private:

//初始化DSHOW接口

BOOL InitDShow(LPCTSTR strFileName  /*视频文件名*/

,HWND hOwnerWnd  /*显示视频的窗口句柄*/

,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/

);

//释放DSHOW接口

BOOL UnInitDShow();

public:

//打开视频文件

BOOL OpenFile(LPCTSTR strFileName /*视频文件名*/

,HWND hOwnerWnd  /*显示视频的窗口句柄*/

,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/

);

//播放视频

BOOL VideoRun();

//暂停视频

BOOL VideoPause();

//停止视频

BOOL VideoStop();

public:

BOOL FitVideoWindow(FLOAT fScale); //设置视频显示比例  

BOOL FullScreen();   //全屏显示   

BOOL GetFullScreenStatus();       //得到是否是全屏显示

BOOL GetMediaEvent(long *lEventCode); //得到Dshow播放事件

//设置播放进度,单位: 秒

BOOL SetPositions(DWORD dwPos /*设置当前播放进度*/);

//得到视频播放当前的位置,单位:秒

BOOL GetCurrentPos(DWORD &dwPos  /*out 当前播放进度*/);

BOOL GetDuration(DWORD &dwLength); //得到视频文件时间长度,单位:秒

CEricMediaControl类实现文件

EricMediaControl.cpp文件当中添加如下源代码:

 

#include "StdAfx.h"

#include "EricMediaControl.h"

CEricMediaControl::CEricMediaControl(void) //构造函数

{

//将DSHOW接口置空

m_pGB = NULL;

m_pMC = NULL;

m_pME = NULL;

m_pVW = NULL;

m_pBA = NULL;

m_pBV = NULL;

m_pMS = NULL;

//初始化COM 环境

CoInitialize(NULL);//directshow 使用到了COM技术,在使用Directshow 变量时,我们首先要初始化COM环境。

}

//析构函数

Ø CEricMediaControl::~CEricMediaControl(void)

{

//释放DSHOW接口

UnInitDShow(); 

//释放COM 环境

CoUninitialize();

}

 

 

Directshow接口流程图:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/*

*函数介绍:初始化DShow接口,并渲染好视频文件

*入口参数:strFileName: 视频文件名

hOwnerWnd: 显示视频的窗口句柄

hNotifyWnd: 接收DSHOW事件消息的串口句柄

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::InitDShow(LPCTSTR strFileName  /*视频文件名*/

,HWND hOwnerWnd  /*显示视频的窗口句柄*/

,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/

)

{

HRESULT hResult;

//第1步:创建IGraphBuilder接口

hResult = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGB);

if (hResult != S_OK)

{

return FALSE;

}

//第2步:利用IGraphBuilder渲染视频文件

hResult = m_pGB->RenderFile(strFileName,NULL);

if (hResult != S_OK )

{

//

if (hResult == VFW_S_PARTIAL_RENDER)

{

TRACE(L"Some of the streams in this movie are in an unsupported format.\n");

}

else if (hResult == VFW_S_AUDIO_NOT_RENDERED)

{

TRACE(L"Partial success; the audio was not rendered.\n");

}

else if (hResult == VFW_S_DUPLICATE_NAME)

{

TRACE(L"Success; the Filter Graph Manager modified the filter name to avoid duplication..\n");

}

else if (hResult == VFW_S_VIDEO_NOT_RENDERED)

{

TRACE(L"Partial success; some of the streams in this movie are in an unsupported format.\n");

 

 

}

else

{

//释放DSHOW接口

UnInitDShow();

return FALSE;

}

}

//第3步:得到媒体播放控制接口

hResult = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC); 

if (hResult != S_OK)

{

//释放DSHOW接口

UnInitDShow();

return FALSE;

}

//第4步:得到媒体播放位置搜索接口

hResult = m_pGB->QueryInterface(IID_IMediaSeeking,(void**)&m_pMS);

if (hResult != S_OK)

{

//释放DSHOW接口

UnInitDShow();

return FALSE;

}

//设置查找定位的时间单位,这里设置:纳秒(十亿分之一秒)

//GUID guid_timeFormat = TIME_FORMAT_MEDIA_TIME;

//m_pMS->SetTimeFormat(&guid_timeFormat);

 

//第5步:得到Filter Graph媒体事件接口

hResult = m_pGB->QueryInterface(IID_IMediaEventEx,(void**)&m_pME);

if (hResult != S_OK)

{

//释放DSHOW接口

UnInitDShow();

return FALSE;

}

//设置媒体事件通知消息窗口

m_pME->SetNotifyWindow((OAHWND)hNotifyWnd, WM_GRAPHNOTIFY, 0);

//第6步:得到视频播放窗口接口

hResult = m_pGB->QueryInterface(IID_IVideoWindow, (void **)&m_pVW);

if (hResult != S_OK)

 {

//释放DSHOW接口

UnInitDShow();

return FALSE;

 }

 //设置视频播放窗口句柄

 m_pVW->put_Owner((OAHWND)hOwnerWnd);

 //设置视频窗口格式

     m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

 //第7步:得到基础视频流接口

 hResult = m_pGB->QueryInterface(IID_IBasicVideo, (void **)&m_pBV);

    if (hResult != S_OK)

 {

//释放DSHOW接口

UnInitDShow();

return FALSE;

 }

 //第8步:得到基础音频流接口

 hResult = m_pGB->QueryInterface(IID_IBasicAudio, (void **)&m_pBA);

    if (hResult != S_OK)

 {

//释放DSHOW接口

UnInitDShow();

return FALSE;

 }

return TRUE;

}

/*

*函数介绍:卸载DShow系列接口

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::UnInitDShow()

{

//1,释放媒体播放控制接口

if (m_pMC != NULL)

{

//停止视频播放

m_pMC->Stop(); 

m_pMC->Release();

m_pMC = NULL;

}

//2,释放媒体事件接口

if (m_pME != NULL)

{

//消息通知窗口置空

m_pME->SetNotifyWindow(NULL, 0, 0);

m_pME->Release();

m_pME = NULL;

}

 

//3,释放视频播放窗口接口

if (m_pVW != NULL)

{

//隐藏视频窗口

m_pVW->put_Visible(OAFALSE);

//设置视频播放窗口句柄为空

m_pVW->put_Owner(NULL);

m_pVW->Release();

m_pVW = NULL;

}

//4,释放基础音频流接口

if (m_pBA != NULL)

{

m_pBA->Release();

m_pBA = NULL;

}

//5,释放基础视频流接口

if (m_pBV != NULL)

{

m_pBV->Release();

m_pBV = NULL;

}

//6,释放媒体搜索接口

if (m_pMS != NULL)

{

m_pMS->Release();

m_pMS = NULL;

}

//7,最后释放FilterGpaph接口

if (m_pGB != NULL)

{

m_pGB->Release();

m_pGB = NULL;

}

return TRUE;

}

/*

*函数介绍:打开视频文件

*入口参数:strFileName: 视频文件名

hOwnerWnd: 显示视频的窗口句柄

hNotifyWnd: 接收DSHOW事件消息的串口句柄

*出口参数:(无)

*返回值:TRUE:成功打开视频文件;FALSE:打开视频文件失败

*/

BOOL CEricMediaControl::OpenFile(LPCTSTR strFileName /*视频文件名*/

,HWND hOwnerWnd  /*显示视频的窗口句柄*/

,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/

)

{

//存储显示视频窗口句柄

m_hOwnerWnd = hOwnerWnd;

//重置DSHOW接口

UnInitDShow();

//打开视频文件,并对DSHOW接口做初始化工作

return InitDShow(strFileName,hOwnerWnd,hNotifyWnd);

}

/*

*函数介绍:播放视频

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::VideoRun()

{

//检测IMediaControl控制接口有效性

if (m_pMC == NULL)

{

return FALSE;

}

//播放视频

HRESULT hResult = m_pMC->Run();

if (hResult != S_OK)

{

return FALSE;

}

return TRUE;

}

/*

*函数介绍:暂停视频

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::VideoPause()

{

//检测IMediaControl控制接口有效性

if (m_pMC == NULL)

{

return FALSE;

}

//暂停视频

HRESULT hResult = m_pMC->Pause();

if (hResult != S_OK)

{

return FALSE;

}

return TRUE;

}

/*

*函数介绍:停止视频

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::VideoStop()

{

HRESULT hResult ;

//检测IMediaControl控制接口有效性

if (m_pMC == NULL)

{

return FALSE;

}

//停止视频

hResult = m_pMC->Stop();

if (hResult != S_OK)

{

return FALSE;

}

//将当前播放位置置

    LONGLONG pos=0;

hResult = m_pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,

                                   NULL, AM_SEEKING_NoPositioning);

return TRUE;

}

/*

*函数介绍: 设置视频显示比例

*入口参数: fScale :显示比例, <= 1.0

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败,此处有点问题

*/

BOOL CEricMediaControl::FitVideoWindow(FLOAT fScale)

{

    LONG lHeight, lWidth;

int iSeek = 0;

double dblScaleX,dblScaleY;

    HRESULT hr = S_OK;

LONG lDeflateX = 0;

LONG lDeflateY = 0;

    CRect clientRect;

CRect dstRect;

//

if (m_pBV == NULL)

{

return FALSE;

}

//放缩比例必须小于等于

if (fScale > 1.0)

{

return FALSE;

}

//得到原始视频尺寸

    hr = m_pBV->GetVideoSize(&lWidth, &lHeight);

if (hr != S_OK)

{

return FALSE;

}

//设置拉伸后的尺寸

lWidth = lWidth * fScale;

lHeight = lHeight * fScale;

//得到视频播放窗口的尺寸

GetClientRect(m_hOwnerWnd, &clientRect);

lDeflateX = (clientRect.Width() - clientRect.Width() * fScale) / 2;

lDeflateY = (clientRect.Height() - clientRect.Height() * fScale) / 2;

//重新设置客户区域

clientRect.DeflateRect(lDeflateX,lDeflateY);

if ( (lWidth <= clientRect.Width())

&& (lHeight <= clientRect.Height()))

{

dstRect.left = (clientRect.right - clientRect.left - lWidth) /2;

dstRect.right = dstRect.left + lWidth;

dstRect.top  = (clientRect.bottom - clientRect.top - lHeight) /2;

dstRect.bottom = dstRect.top + lHeight;

}

else 

{

dblScaleX =double(clientRect.Width()) / double(lWidth) ;

dblScaleY =  double(clientRect.Height()) / double(lHeight) ;

if (dblScaleX <= dblScaleY)

{

dstRect.left = clientRect.left;

dstRect.right = clientRect.right;

iSeek =(clientRect.Height() - clientRect.Width()*(double(lHeight) / double(lWidth)))/2;

dstRect.top = clientRect.top + iSeek;

dstRect.bottom = dstRect.top + clientRect.Width()*(double(lHeight) / double(lWidth));

}

else

{

dstRect.top = clientRect.top;

dstRect.bottom = clientRect.bottom;

iSeek =(clientRect.Width() - clientRect.Height()*(double(lWidth) / double(lHeight)))/2;

dstRect.left = clientRect.left + iSeek;

dstRect.right = dstRect.left + clientRect.Height()*(double(lWidth) / double(lHeight));

}

}

//设置视频播放位置

    m_pVW->SetWindowPosition(dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height());

    return TRUE;

}

/*

*函数介绍: 全屏显示

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::FullScreen()

{

LONG lMode = 0;

static HWND hDrain=0;

if (m_pBV == NULL)

{

return FALSE;

}

//得到全屏状态

m_pVW->get_FullScreenMode(&lMode);

if (lMode == OAFALSE)

    {

// Save current message drain

        m_pVW->get_MessageDrain((OAHWND *) &hDrain);

        // Set message drain to application main window

        m_pVW->put_MessageDrain((OAHWND)m_hOwnerWnd );

//设置全屏幕

        lMode = OATRUE;

        m_pVW->put_FullScreenMode(lMode);

    }

    else

    {

        //切换到非全屏模式

        lMode = OAFALSE;

        m_pVW->put_FullScreenMode(lMode);

 // Undo change of message drain

        m_pVW->put_MessageDrain((OAHWND) hDrain);

        // Reset video window

        FitVideoWindow(1);

        m_pVW->SetWindowForeground(-1);

}

return TRUE;

}

/*

*函数介绍: 得到是否全屏幕播放

*入口参数:(无)

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::GetFullScreenStatus()

{

LONG lMode = 0;

if (m_pBV == NULL)

{

return FALSE;

}

m_pVW->get_FullScreenMode(&lMode);

if (lMode == OAFALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

/*

*函数介绍: 得到媒体事件

*入口参数:(无)

*出口参数:lEventCode

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::GetMediaEvent(long *lEventCode)

{

    LONG evCode, evParam1, evParam2;

    HRESULT hr=S_OK;

if (m_pME == NULL)

{

return FALSE;

}

hr = m_pME->GetEvent(&evCode, &evParam1, &evParam2, 0);

    if (SUCCEEDED(hr))

{

        *lEventCode = evCode;

// Spin through the events

        hr = m_pME->FreeEventParams(evCode, evParam1, evParam2);

return TRUE;

}

return FALSE;

}

/*

*函数介绍: 设置播放进度

*入口参数:dwPos :播放进度,单位秒

*出口参数:(无)

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::SetPositions(DWORD dwPos /*设置当前播放进度*/)

{

//设置绝对位置,转化成纳秒为单位

LONGLONG llPos = dwPos * 10000 * 1000;

if (m_pMS == NULL)

{

return FALSE;

}

//设置视频播放当前位置

HRESULT hr = m_pMS->SetPositions(&llPos,AM_SEEKING_AbsolutePositioning ,

                                   NULL, AM_SEEKING_NoPositioning);

if (hr == S_OK)

{

return TRUE;

}

else

{

return FALSE;

}

}

/*

*函数介绍: 得到视频文件播放长度,单位秒

*入口参数:dwLength :视频文件时间长度

*出口参数:dwLength :视频文件时间长度

*返回值:TRUE:成功;FALSE:失败

*/

BOOL CEricMediaControl::GetDuration(DWORD &dwLength)

{

dwLength = 0;

if (m_pMS == NULL)

{

return FALSE;

}

//得到视频总时间长度

LONGLONG llDuration;

HRESULT hResult = m_pMS->GetDuration(&llDuration);

if (hResult != S_OK)

{

return FALSE;

}

//转换成以秒为单位

llDuration = llDuration / 10000 ;

llDuration = llDuration / 1000;

dwLength  = (DWORD)llDuration;

return TRUE;

}

 

/*

*函数介绍: 得到媒体当前播放进度位置,单位秒

*入口参数:dwPos :当前播放进度,单位秒

*出口参数:dwPos :当前播放进度,单位秒

*返回值:TRUE:成功;FALSE:失败

BOOL CEricMediaControl::GetCurrentPos(DWORD &dwPos)

{

dwPos = 0;

LONGLONG llPos = 0;

if (m_pMS== NULL)

{

return FALSE;

}

//得到当前视频播放位置

HRESULT hResult = m_pMS->GetCurrentPosition(&llPos);

if (hResult != S_OK)

{

return FALSE;

}

//转换成以秒为单位

llPos = llPos / 10000 ;

llPos = llPos / 1000;

dwPos  = (DWORD)llPos;

return TRUE;

 

 

 

7asfmp4

ASF

ASF (Advanced Streaming format高级流格式)ASF 是 MICROSOFT 为了和现在的 Real player 竞争而发展出来的一种可以直接在网上观看视频节目的文件压缩格式。ASF使用了 MPEG4 的压缩算法,压缩率和图像的质量都很不错。因为 ASF 是以一个可以在网上即时观赏的视频格式存在的,所以它的图像质量比 VCD 差一点点并不出奇,但比同是视频格式的 RAM 格式要好。

 

MP4

Mp4使用MPEG-2 AAC技术,也就是简称为A2BAAC的技术。它的特点是音质更加完美而压缩比更大(15:1-20:1)。它增加了诸如对立体声的完美再现、比特流效果音扫描、多媒体控制、降噪等MP3没有的特性,使得在音频压缩后仍能完美的再现CD的音质。 

 

 

 

 

 

 

 

7参考文献:

1.郑翔均衡虚拟空间会议系统的混频时延和混频器数量的技术研究[期刊论文]-计算机研究与发展 2003(10)

2.Creating WWW Pages with RealAudio/RealVideo 2003

3.Singer D.Belknap W.Franceschini G ISO Media File format specification 2001

4.Hossein M.Georganas ND Suitability of MPEG4′ BIFS for development of collaborative virtual

environments 2001

5.Mulroy P VRML gets real the MPEG4 way,Teleconferencing Futures(Digest No:1997/121) 1997(01)

6.Herpel C.Eleftheriadis A MPEG4 Systems:Elementary Stream Management 2003

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值