ECN>> mp4>> 返回
项目作者: akanchi

项目描述 :
A simple implementation of mp4 demuxing.
高级语言: C++
项目地址: git://github.com/akanchi/mp4.git
创建时间: 2021-04-09T02:43:02Z
项目社区:https://github.com/akanchi/mp4

开源协议:MIT License

下载


MP4 Demuxer

CMake

A simple implementation of mp4 demuxing, supports AVC/HEVC/VP9/AAC/mp3/Opus.

Usage

Build Demuxer by:

  1. mkdir cmake-build-debug && cd cmake-build-debug && cmake .. && make
  2. # or
  3. sh build.sh

Run Demuxer by:

  1. ./mp4_demuxer ../docs/SampleVideo_360x240_5mb-base.mp4

After run demuxer, here is the box tree:

  1. [0, 4298693)
  2. ftyp[0, 32)
  3. major_brand: isom
  4. minor_version: 512
  5. compatible_brands: isomiso2avc1mp41
  6. free[32, 40)
  7. mdat[40, 4270608)
  8. moov[4270608, 4298693)
  9. mvhd[4270616, 4270724)
  10. version: 0
  11. flags: 0
  12. creation_time: 0
  13. modification_time: 0
  14. time_scale: 1000
  15. duration: 64896
  16. preferred_rate: 1.000000
  17. preferred_volume: 1.000000
  18. matrix:
  19. | a, b, u | | 1.000000, 0.000000, 0.000000 |
  20. | c, d, v | = | 0.000000, 1.000000, 0.000000 |
  21. | x, y, w | | 0.000000, 0.000000, 1.000000 |
  22. next_track_id: 3
  23. trak[4270724, 4279127)
  24. tkhd[4270732, 4270824)
  25. edts[4270824, 4270860)
  26. elst[4270832, 4270860)
  27. mdia[4270860, 4279127)
  28. mdhd[4270868, 4270900)
  29. version: 0
  30. flags: 0
  31. creation_time: 0
  32. modification_time: 0
  33. time_scale: 15360
  34. duration: 996352
  35. language: 21956
  36. quality: 0
  37. hdlr[4270900, 4270945)
  38. minf[4270945, 4279127)
  39. vmhd[4270953, 4270973)
  40. dinf[4270973, 4271009)
  41. dref[4270981, 4271009)
  42. stbl[4271009, 4279127)
  43. stsd[4271017, 4271187)
  44. avc1[4271033, 4271187)
  45. avcC[4271119, 4271171)
  46. stts[4271187, 4271211)
  47. stss[4271211, 4271279)
  48. stsc[4271279, 4271307)
  49. stsz[4271307, 4275219)
  50. stco[4275219, 4279127)
  51. trak[4279127, 4298595)
  52. tkhd[4279135, 4279227)
  53. edts[4279227, 4279263)
  54. elst[4279235, 4279263)
  55. mdia[4279263, 4298595)
  56. mdhd[4279271, 4279303)
  57. version: 0
  58. flags: 0
  59. creation_time: 0
  60. modification_time: 0
  61. time_scale: 48000
  62. duration: 3115008
  63. language: 21956
  64. quality: 0
  65. hdlr[4279303, 4279348)
  66. minf[4279348, 4298595)
  67. smhd[4279356, 4279372)
  68. dinf[4279372, 4279408)
  69. dref[4279380, 4279408)
  70. stbl[4279408, 4298595)
  71. stsd[4279416, 4279519)
  72. mp4a[4279432, 4279519)
  73. esds[4279468, 4279519) codec.0x40
  74. stts[4279519, 4279543)
  75. stsc[4279543, 4282499)
  76. stsz[4282499, 4294687)
  77. stco[4294687, 4298595)
  78. udta[4298595, 4298693)
  79. meta[4298603, 4298693)
  80. hdlr[4298615, 4298648)
  81. ilst[4298648, 4298693)

Play the extracted file by:

  1. ffplay aac_audio.aac

Key points

我的目的是将mp4文件中的音视频数据解析出来,并保存为对应媒体文件。

众所周知,mp4的媒体数据是保存在mdat中,但 mdat没有告诉我们哪些是音频/视频数据,所以需要依赖其它box(主要是stcostszstsc)去定位mdat中的音视频数据。

stco(Chunk Offset Box)

用于定位媒体数据中的块偏移,因为有多个块,所以是一个数组格式,chunk_offsets[number_of_entries]

stsz(Sample Size Box)

用于表示每个媒体sample的的大小,通常sample的大小是可变的,sample_sizes[number_of_entries]保存所有sample的size

stsc(Sample-to-Chunk Box)

如果只知道stcostsz是无法定位媒体数据的,所以需要samplechunk的对应表,才能解析出媒体数据

以下表格来自于docs/qtff.pdf

Figure 2-47 An example of a sample-to-chunk table

First chunk Samples per chunk Sample description ID
1 3 1
3 1 1
5 1 1

下面是展开之后的表格,可以看出可以通过stco去索引chunk,以及通过stsz去索引sample

First chunk Samples per chunk Sample description ID
1 3 1
* 2 3 1
3 1 1
* 4 1 1
5 1 1
Number of chunks = 5 Number of samples = 9

提取媒体数据

知道了所需要的信息就可以着手提取媒体数据了,相关代码可以参考int TrackContext::extract()

  1. uint32_t current_entry_index = 0;// stsc中,entries的下标
  2. uint32_t current_entry_sample_index = 0; // stsc中,对应current_entry_index的entry.sample的下标
  3. uint32_t chunk_logic_index = 0; // stco中,chunk_offsets的下标
  4. uint32_t sample_offset = 0;// sample的偏移量
  5. for (int i = 0; i < sample_sizes.count; i++) {
  6. // 1. 取出当前entry = stsc->entries[current_entry_index]
  7. // 2. 计算当前sample的起始位置pos = chunk_offsets[chunk_logic_index] + sample_offset
  8. // 3. 起始位置pos 和 sample_sizes[i]可以获取当前的sample数据
  9. // 4. current_entry_sample_index++
  10. // 5. 计算sample_offset += sample_sizes[i]
  11. // 6. 如果current_entry_sample_index >= entry.samples_per_chunk 则切换到下一个chunk,并重置sample相关标记
  12. }

提取avc的时候,一个sample可能含有多个nalu,格式是[nalu length(4bytes)][nalu data(nalu length bytes)]+[nalu length][nalu data]+...。这个规则同样适用于hevc的提取。

关于Opus

按照正常的音频提取的逻辑,提取出来的是Opus裸流,播放器无法播放

看了一下FFmpeg的处理,需要封装成Ogg格式,同时查阅了OggOpus的协议文档最终封装成可播放的音频文件

Ogg封装主要有几部分

  • Ogg是以page为单位封装数据的,pageOggS开头
  • OpusHead,这部分可以从dOps中读取,但需要手动添加OpusHead
  • CommentHeader,以OpusTags开头,主要放一些无关紧要的信息
  • 音频数据的封装,如果sample size > 255 bytes,则需要拆分成多个page,因为记录page payload大小的字段只有1个字节,不然播放器会报CRC mismatch!
  • 最后一个page的页码和前一个page的页码是一样的