免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2951 | 回复: 2

[Android] android 多媒体文件之mp4分析 [复制链接]

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
发表于 2015-10-19 14:34 |显示全部楼层
本帖最后由 baopbird2005 于 2015-10-19 14:39 编辑

我们讲多媒体,涉及到的最多的就是MP4文件和MP3文件了,但是我们对这两个文件的格式了解多少呢,它的由有哪些部分部分组成呢?它的核心部件是哪些?它哪些部分是供解码器去解析的呢?带着这些疑问,我们首先来探索下MP4文件。
我们首先用MP4Info这个工具来看下MP4的大貌:

QQ截图20151019142940.png

从上图我们可以看到MP4文件中的所有数据都装在box中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;一个moov可以由多个tracks组成。每个track就是一个随时间变化的媒体序列,例如,视频帧序列。track里的每个时间单位是一个sample,它可以是一帧视频,或者音频。sample按照时间顺序排列。注意,一帧音频可以分解成多个音频sample,所以音频一般用sample作为单位,而不用帧。MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。“free”类型的box,就是一些自由的信息,可以写,也可以不写。
box中的字节序为网络字节序,也就是大端字节序(Big-Endian),简单的说,就是一个32位的4字节整数存储方式为高位字节在内存的低端。Box由header和body组成,其中header统一指明box的大小和类型,body根据类型有不同的意义和格式。

BOX
标准的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是未定义的,应该将其忽略。
对应的代码片段为:framework/av/media/libstagefright/MPEG4Extrator.cpp
  1. status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
  2.     ALOGV("entering parseChunk %lld/%d", *offset, depth);
  3.     uint32_t hdr[2];
  4.     static const char* mQTMajorBrand = "qt  ";
  5.     if (mDataSource->readAt(*offset, hdr, 8) < 8) {
  6.         return ERROR_IO;
  7.     }
  8.     uint64_t chunk_size = ntohl(hdr[0]);---box size
  9.     uint32_t chunk_type = ntohl(hdr[1]);---box type
  10.     off64_t data_offset = *offset + 8;

  11.     if (chunk_size == 1) {
  12.         if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {---读取box size的大小
  13.             return ERROR_IO;
  14.         }
  15.         chunk_size = ntoh64(chunk_size); ---将64位的网络字节转换为主机字节
  16.         data_offset += 8;
  17. ……….
  18.     char chunk[5];
  19.     MakeFourCCString(chunk_type, chunk);  ----FOURCC全称Four-Character Codes,是在编程
  20. 中非常常用的东西,一般用作标示符。它是一个32位的标示符,其实就是typedef unsigned long FOURCC

  21. }
  22. …………
  23. }


  24. static void MakeFourCCString(uint32_t x, char *s) {
  25.     s[0] = x >> 24;
  26.     s[1] = (x >> 16) & 0xff;
  27.     s[2] = (x >> 8) & 0xff;
  28.     s[3] = x & 0xff;
  29.     s[4] = '\0';
  30. }
复制代码
File Type Box(ftyp)
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的字节实例如下:
对应的的代码如下:
framework/av/media/libstagefright/MPEG4Extrator.cpp
  1. status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
  2. switch(chunk_type) {
  3.         case FOURCC('f', 't', 'y', 'p'):
  4.         {
  5.             if (chunk_data_size < 4) {
  6.                 return ERROR_MALFORMED;
  7.             }

  8.             uint32_t ftype;
  9.             if (mDataSource->readAt(data_offset, &ftype, 4) < 4) {
  10.                 return ERROR_IO;
  11.             }

  12.             MakeFourCCString(ntohl(ftype), mMajorBrand); -----major brand

  13.             *offset += chunk_size;
  14.             break;
  15.         }
  16. }
复制代码
Movie Box(moov)
该box包含了文件媒体的metadata信息,“moov”是一个container box,具体内容信息由子box诠释。同File Type Box一样,该box有且只有一个,且只被包含在文件层。一般情况下,
“moov”会紧随“ftyp”出现。一般情况下, “moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现(对于其他container box来说,header box都应作为首个子box出现)。“trak”包含了一个track的相关信息,是一个container box。 结构图 上图

Movie Header Box(mvhd)

QQ截图20151019143305.png
  1. status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
  2.     ALOGV("entering parseChunk %lld/%d", *offset, depth);
  3.     uint32_t hdr[2];
  4.     static const char* mQTMajorBrand = "qt  ";
  5.     if (mDataSource->readAt(*offset, hdr, 8) < 8) {
  6.         return ERROR_IO;
  7.     }
  8.     uint64_t chunk_size = ntohl(hdr[0]);---box size
  9.     uint32_t chunk_type = ntohl(hdr[1]);---box type
  10. ………………….
  11. case FOURCC('m', 'v', 'h', 'd'):
  12.         {
  13.             if (chunk_data_size < 12) { //increase to 16?---
  14.                 return ERROR_MALFORMED;
  15.             }

  16.             uint8_t header[16];
  17.             if (mDataSource->readAt(
  18.                         data_offset, header, sizeof(header))
  19.                     < (ssize_t)sizeof(header)) {
  20.                 return ERROR_IO;
  21.             }

  22.             int64_t creationTime;
  23.             if (header[0] == 1) {
  24.                 creationTime = U64_AT(&header[4]);
  25.                 mFileMetaData->setInt64(kKeyEditOffset, 0 );
  26.             } else if (header[0] != 0) {
  27.                 return ERROR_MALFORMED;
  28.             } else {
  29.                 creationTime = U32_AT(&header[4]);-------创建时间,4个字节
  30.                 int32_t mvTimeScale = U32_AT(&header[12]);---时间刻度,4个字节

  31.                 mFileMetaData->setInt32(kKeyEditOffset, mvTimeScale );
  32.             }

  33.             String8 s;
  34.             convertTimeToDate(creationTime, &s);

  35.             mFileMetaData->setCString(kKeyDate, s.string());

  36.             *offset += chunk_size;
  37.             break;
  38.         }
复制代码
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。

  1. status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
  2.     ALOGV("entering parseChunk %lld/%d", *offset, depth);
  3.     uint32_t hdr[2];
  4.     static const char* mQTMajorBrand = "qt  ";
  5.     if (mDataSource->readAt(*offset, hdr, 8) < 8) {
  6.         return ERROR_IO;
  7.     }
  8.     uint64_t chunk_size = ntohl(hdr[0]);---box size
  9.     uint32_t chunk_type = ntohl(hdr[1]);---box type
  10. ……………………..
  11. if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
  12.                 isTrack = true;
  13.                 Track *track = new Track; --- 如果是Track,new 个track
  14.                 track->next = NULL;
  15.                 if (mLastTrack) {
  16.                     mLastTrack->next = track;
  17.                 } else {
  18.                     mFirstTrack = track;
  19.                 }
  20.                 mLastTrack = track;

  21.                 track->meta = new MetaData;
  22.                 track->includes_expensive_metadata = false;
  23.                 track->skipTrack = false;
  24.                 track->timescale = 0;
  25.                 track->meta->setCString(kKeyMIMEType, "application/octet-stream");
  26.             }

  27.             off64_t stop_offset = *offset + chunk_size;
  28.             *offset = data_offset;
  29.             while (*offset < stop_offset) {
  30.                 if (stop_offset - *offset >= 8) {
  31.                     status_t err = parseChunk(offset, depth + 1);
  32.                     if (err != OK) {
  33.                         if(chunk_type == FOURCC('u', 'd', 't', 'a')){
  34.                             ALOGW("error in udta atom, ignoring %llu bytes",stop_offset - *offset);
  35.                             *offset = stop_offset;
  36.                         } else {
  37.                             return err;
  38.                         }
  39.                     }
  40.                 }
  41. ………….
  42. }
复制代码
下图为总的概括:

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
发表于 2015-10-19 14:36 |显示全部楼层
Sample Table Box(stbl)
“stbl”几乎是普通的MP4文件中最复杂的一个box了。sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同。chunk是几个sample的集合。“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”包含了编码的信息,其存储的信息随媒体类型不同而不同。
  1. if (chunk_type == FOURCC('s', 't', 'b', 'l')) {
  2.                 ALOGV("sampleTable chunk is %d bytes long.", (size_t)chunk_size);

  3.                 if (mDataSource->flags()
  4.                         & (DataSource::kWantsPrefetching
  5.                             | DataSource::kIsCachingDataSource)) {
  6.                     sp<MPEG4DataSource> cachedSource =
  7.                         new MPEG4DataSource(mDataSource);

  8.                     if (cachedSource->setCachedRange(*offset, chunk_size) == OK) {
  9.                         mDataSource = cachedSource;
  10.                     }
  11.                 }

  12.                 mLastTrack->sampleTable = new SampleTable(mDataSource);----创建sampletable,每个track对应一个sampletable
  13.             }
复制代码
Sample Description Box(stsd)

box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。
  1. case FOURCC('s', 't', 's', 'd'):
  2.         {

  3. ,…………………………….
  4.             uint32_t entry_count = U32_AT(&buffer[4]);

  5.             off64_t stop_offset = *offset + chunk_size;
  6.             *offset = data_offset + 8;

  7.             if (entry_count > 1) {----针对3GPP,有可能有多个entry_count,但目前我们每个track支持单类型的media
  8.                 // For 3GPP timed text, there could be multiple tx3g boxes contain
  9.                 // multiple text display formats. These formats will be used to
  10.                 // display the timed text.
  11.                 const char *mime;
  12.                 CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
  13.                 if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
  14.                      ALOGV("Text track found");
  15.                      for (uint32_t i = 0; i < entry_count; ++i) {
  16.                      status_t err = parseChunk(offset, depth + 1);
  17.                         if (err != OK) {
  18.                             return err;
  19.                         }
  20.                      }
  21.                     // For now we only support a single type of media per track.
  22.                 }
  23.                 else {
  24.                      status_t err = mLastTrack->sampleTable->setSampleDescParams(entry_count, *offset, chunk_data_size);
  25.                      if (err != OK) {
  26.                          return ERROR_IO;
  27.                      }
  28.                      //视频的编码类型、宽高、长度,音频的声道、采样等信息
  29.                      mHasVideo = true;
  30.                      uint8_t avc1[86];//(avc1-avcc) which is fixed
  31.                      if (mDataSource->readAt(*offset, avc1, sizeof(avc1)) < (ssize_t)sizeof(avc1)) {
  32.                          return ERROR_IO;
  33.                      }
  34.                      uint32_t chunk_type = U32_AT(&avc1[4]);
  35.                      uint16_t data_ref_index = U16_AT(&avc1[14]);
  36.                      uint16_t width = U16_AT(&avc1[32]);
  37.                      uint16_t height = U16_AT(&avc1[34]);

  38.                      mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
  39.                      mLastTrack->meta->setInt32(kKeyWidth, width);
  40.                      mLastTrack->meta->setInt32(kKeyHeight, height);

  41.                      uint8_t *avcc;
  42.                      uint32_t avccSize;
  43.                      mLastTrack->sampleTable->getSampleDescAtIndex(1, &avcc, &avccSize);
  44.                      mLastTrack->meta->setData(kKeyAVCC, kTypeAVCC, avcc, avccSize);
  45.                      *offset = stop_offset;
  46.                 }
  47.             } else {
  48.                  for (uint32_t i = 0; i < entry_count; ++i) {
  49.                      status_t err = parseChunk(offset, depth + 1);
  50.                      if (err != OK) {
  51.                         return err;
  52.                      }
  53.                  } // end of for

  54.             }//end of entry count 1

  55.             if (*offset != stop_offset) {
  56.                 return ERROR_MALFORMED;
  57.             }
  58.             break;
  59.         }
复制代码

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
发表于 2015-10-19 14:38 |显示全部楼层
Time To Sample Box(stts)
“stts”存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。
  1. case FOURCC('s', 't', 't', 's'):
  2.         {
  3.             status_t err =
  4.                 mLastTrack->sampleTable->setTimeToSampleParams(---该方法在SampleTable.cpp,映射时间和sample序号
  5.                         data_offset, chunk_data_size);

  6.             if (err != OK) {
  7.                 return err;
  8.             }

  9.             *offset += chunk_size;
  10.             break;
  11.         }
复制代码
Sample Size Box(stsz)
“stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。
  1. case FOURCC('s', 't', 's', 'z'):
  2.         case FOURCC('s', 't', 'z', '2'):
  3.         {
  4.             status_t err =
  5.                 mLastTrack->sampleTable->setSampleSizeParams(-----该方法在SampleTable.cpp,设置sample大小
  6.                      chunk_type, data_offset, chunk_data_size);

  7.             if (err != OK) {
  8.                 return err;
  9.             }

  10.             size_t max_size;
  11.             err = mLastTrack->sampleTable->getMaxSampleSize(&max_size);

  12.             if (err != OK) {
  13.                 return err;
  14.             }

  15.             // Assume that a given buffer only contains at most 10 fragments,
  16.             // each fragment originally prefixed with a 2 byte length will
  17.             // have a 4 byte header (0x00 0x00 0x00 0x01) after conversion,
  18.             // and thus will grow by 2 bytes per fragment.
  19.             mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size + 10 * 2);
  20.             *offset += chunk_size;

  21.             // Calculate average frame rate.
  22.             const char *mime;
  23.             CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
  24.             if (!strncasecmp("video/", mime, 6)) {
  25.                 size_t nSamples = mLastTrack->sampleTable->countSamples();
  26.                 int64_t durationUs;
  27.                 if (mLastTrack->meta->findInt64(kKeyDuration, &durationUs)) {
  28.                     if (durationUs > 0) {
  29.                         int32_t frameRate = (nSamples * 1000000LL +
  30.                                     (durationUs >> 1)) / durationUs;
  31.                         mLastTrack->meta->setInt32(kKeyFrameRate, frameRate);
  32.                     }
  33.                 }
  34.             }

  35.             break;
  36.         }
复制代码
Sample To Chunk Box(stsc)
用chunk组织sample可以方便优化数据获取,一个chunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的chunk,从而找到这个sample。
  1. case FOURCC('s', 't', 's', 'c'):
  2.         {
  3.             status_t err =
  4.                 mLastTrack->sampleTable->setSampleToChunkParams(该方法在SampleTable.cpp,映射sample和chunk的关系,一个或多个sample组成一个chunk

  5.                         data_offset, chunk_data_size);

  6.             if (err != OK) {
  7.                 return err;
  8.             }

  9.             *offset += chunk_size;
  10.             break;
  11.         }
复制代码
Sync Sample Box(stss)
“stss”确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。“stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。
如何查找关键帧呢?
1:确定给定时间的sample序号检查sync sample atom来发现这个sample序号之后的key frame
2:检查sample-to-chunk atom来发现对应该sample的chunk
3:从chunk offset atom中提取该chunk的偏移量
4:利用sample size atom找到sample在trunk内的偏移量和sample的大小
  1. case FOURCC('s', 't', 's', 's'):
  2.         {
  3.             status_t err =
  4.                 mLastTrack->sampleTable->setSyncSampleParams(----设置关键帧
  5.                         data_offset, chunk_data_size);

  6.             if (err != OK) {
  7.                 return err;
  8.             }

  9.             *offset += chunk_size;
  10.             break;
  11.         }
复制代码
Chunk Offset Box(stco)
“stco”定义了每个chunk在媒体流中的位置。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释 box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。
  1. case FOURCC('s', 't', 'c', 'o'):
  2.         case FOURCC('c', 'o', '6', '4'):
  3.         {
  4.             status_t err =
  5.                 mLastTrack->sampleTable->setChunkOffsetParams(---设置chunk的偏移量
  6.                         chunk_type, data_offset, chunk_data_size);

  7.             if (err != OK) {
  8.                 return err;
  9.             }

  10.             *offset += chunk_size;
  11.             break;
  12.         }
复制代码
Free Space Box(free或skip)
“free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。
Meida Data Box(mdat)
该box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP