react-native-alpha-video 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +903 -2
  3. package/THIRD_PARTY_NOTICES.md +30 -0
  4. package/android/av1/build.gradle +67 -0
  5. package/android/av1/consumer-rules.pro +4 -0
  6. package/android/av1/src/main/cpp/Av1Codec_jni.cpp +8 -0
  7. package/android/av1/src/main/cpp/CMakeLists.txt +34 -0
  8. package/android/av1/src/main/java/dev/mediacache/alphavideo/av1/Av1Codec.java +21 -0
  9. package/android/build.gradle +85 -0
  10. package/android/consumer-rules.pro +2 -0
  11. package/android/gradle.properties +1 -0
  12. package/android/publish.gradle +82 -0
  13. package/android/settings.gradle +5 -0
  14. package/android/src/main/AndroidManifest.xml +1 -0
  15. package/android/src/main/cpp/CMakeLists.txt +59 -0
  16. package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoCodecs.java +73 -0
  17. package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoView.java +1966 -0
  18. package/android/src/main/java/dev/mediacache/alphavideo/BufferedDualStreamPlayer.java +978 -0
  19. package/android/src/main/java/dev/mediacache/alphavideo/Fetcher.java +37 -0
  20. package/android/src/main/java/dev/mediacache/alphavideo/HttpFetcher.java +156 -0
  21. package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecColor.java +47 -0
  22. package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecDecoders.java +135 -0
  23. package/android/src/main/java/dev/mediacache/alphavideo/RangeSourceLink.java +54 -0
  24. package/android/src/main/java/dev/mediacache/alphavideo/SourceReader.java +174 -0
  25. package/android/src/main/java/dev/mediacache/alphavideo/SurfaceTextureVideoDecoder.java +802 -0
  26. package/android/src/main/java/dev/mediacache/alphavideo/WebMAlphaPlayerNative.java +95 -0
  27. package/android/src/main/java/dev/mediacache/alphavideo/WebMFrame.java +54 -0
  28. package/android/src/main/java/dev/mediacache/alphavideo/WebMSource.java +160 -0
  29. package/android/src/rangesource/java/dev/mediacache/alphavideo/AppDataSource.java +50 -0
  30. package/android/vpx/build.gradle +67 -0
  31. package/android/vpx/consumer-rules.pro +4 -0
  32. package/android/vpx/src/main/cpp/CMakeLists.txt +34 -0
  33. package/android/vpx/src/main/cpp/VpxCodec_jni.cpp +8 -0
  34. package/android/vpx/src/main/java/dev/mediacache/alphavideo/vpx/VpxCodec.java +21 -0
  35. package/codecs/av1/Dav1dDecoderPlugin.cpp +157 -0
  36. package/codecs/av1/Dav1dDecoderPlugin.h +17 -0
  37. package/codecs/vpx/VpxDecoderPlugin.cpp +103 -0
  38. package/codecs/vpx/VpxDecoderPlugin.h +17 -0
  39. package/cpp/AlphaDemuxer.cpp +984 -0
  40. package/cpp/AlphaDemuxer.h +123 -0
  41. package/cpp/AlphaVideoDecoderPlugin.h +84 -0
  42. package/cpp/Av1SequenceHeader.cpp +175 -0
  43. package/cpp/Av1SequenceHeader.h +25 -0
  44. package/cpp/ByteSource.cpp +116 -0
  45. package/cpp/ByteSource.h +59 -0
  46. package/cpp/RangeSourceClient.h +40 -0
  47. package/cpp/VideoDecoder.cpp +157 -0
  48. package/cpp/VideoDecoder.h +83 -0
  49. package/cpp/WebMAlphaPlayer.cpp +1666 -0
  50. package/cpp/WebMAlphaPlayer.h +389 -0
  51. package/cpp/WebMAlphaPlayer_jni.cpp +1380 -0
  52. package/cpp/YuvToRgb.cpp +39 -0
  53. package/cpp/YuvToRgb.h +40 -0
  54. package/ios/Sources/AlphaVideo/AlphaVideoCodecs.mm +63 -0
  55. package/ios/Sources/AlphaVideo/AlphaVideoInternal.h +70 -0
  56. package/ios/Sources/AlphaVideo/AlphaVideoPlayer.mm +994 -0
  57. package/ios/Sources/AlphaVideo/AlphaVideoSource.mm +476 -0
  58. package/ios/Sources/AlphaVideo/AlphaVideoView.mm +374 -0
  59. package/ios/Sources/AlphaVideo/include/AlphaVideo.h +303 -0
  60. package/ios/Sources/AlphaVideoAV1/AlphaVideoAV1Codec.mm +16 -0
  61. package/ios/Sources/AlphaVideoAV1/include/AlphaVideoAV1.h +18 -0
  62. package/ios/Sources/AlphaVideoVPX/AlphaVideoVPXCodec.mm +16 -0
  63. package/ios/Sources/AlphaVideoVPX/include/AlphaVideoVPX.h +17 -0
  64. package/lib/commonjs/AlphaVideoView.js +187 -0
  65. package/lib/commonjs/AlphaVideoView.js.map +1 -0
  66. package/lib/commonjs/AlphaVideoViewNativeComponent.js +47 -0
  67. package/lib/commonjs/AlphaVideoViewNativeComponent.js.map +1 -0
  68. package/lib/commonjs/AlphaVideoViewNativeComponent.web.js +11 -0
  69. package/lib/commonjs/AlphaVideoViewNativeComponent.web.js.map +1 -0
  70. package/lib/commonjs/NativeAlphaVideoModule.js +10 -0
  71. package/lib/commonjs/NativeAlphaVideoModule.js.map +1 -0
  72. package/lib/commonjs/NativeAlphaVideoModule.web.js +9 -0
  73. package/lib/commonjs/NativeAlphaVideoModule.web.js.map +1 -0
  74. package/lib/commonjs/WebPlayer.js +12 -0
  75. package/lib/commonjs/WebPlayer.js.map +1 -0
  76. package/lib/commonjs/WebPlayer.web.js +92 -0
  77. package/lib/commonjs/WebPlayer.web.js.map +1 -0
  78. package/lib/commonjs/index.js +124 -0
  79. package/lib/commonjs/index.js.map +1 -0
  80. package/lib/module/AlphaVideoView.js +187 -0
  81. package/lib/module/AlphaVideoView.js.map +1 -0
  82. package/lib/module/AlphaVideoViewNativeComponent.js +47 -0
  83. package/lib/module/AlphaVideoViewNativeComponent.js.map +1 -0
  84. package/lib/module/AlphaVideoViewNativeComponent.web.js +11 -0
  85. package/lib/module/AlphaVideoViewNativeComponent.web.js.map +1 -0
  86. package/lib/module/NativeAlphaVideoModule.js +10 -0
  87. package/lib/module/NativeAlphaVideoModule.js.map +1 -0
  88. package/lib/module/NativeAlphaVideoModule.web.js +9 -0
  89. package/lib/module/NativeAlphaVideoModule.web.js.map +1 -0
  90. package/lib/module/WebPlayer.js +12 -0
  91. package/lib/module/WebPlayer.js.map +1 -0
  92. package/lib/module/WebPlayer.web.js +92 -0
  93. package/lib/module/WebPlayer.web.js.map +1 -0
  94. package/lib/module/index.js +124 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/typescript/react-native/src/AlphaVideoView.d.ts +189 -0
  97. package/lib/typescript/react-native/src/AlphaVideoView.d.ts.map +1 -0
  98. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts +109 -0
  99. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts.map +1 -0
  100. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts +5 -0
  101. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts.map +1 -0
  102. package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts +21 -0
  103. package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts.map +1 -0
  104. package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts +3 -0
  105. package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts.map +1 -0
  106. package/lib/typescript/react-native/src/WebPlayer.d.ts +3 -0
  107. package/lib/typescript/react-native/src/WebPlayer.d.ts.map +1 -0
  108. package/lib/typescript/react-native/src/WebPlayer.web.d.ts +9 -0
  109. package/lib/typescript/react-native/src/WebPlayer.web.d.ts.map +1 -0
  110. package/lib/typescript/react-native/src/index.d.ts +77 -0
  111. package/lib/typescript/react-native/src/index.d.ts.map +1 -0
  112. package/licenses/dav1d-COPYING.txt +23 -0
  113. package/licenses/libvpx-LICENSE.txt +31 -0
  114. package/licenses/libvpx-PATENTS.txt +23 -0
  115. package/licenses/libwebm-LICENSE.txt +30 -0
  116. package/licenses/libwebm-PATENTS.txt +23 -0
  117. package/package.json +150 -4
  118. package/prebuilt/android/arm64-v8a/lib/libdav1d.a +0 -0
  119. package/prebuilt/android/arm64-v8a/lib/libvpx.a +0 -0
  120. package/prebuilt/android/armeabi-v7a/lib/libdav1d.a +0 -0
  121. package/prebuilt/android/armeabi-v7a/lib/libvpx.a +0 -0
  122. package/prebuilt/android/x86_64/lib/libdav1d.a +0 -0
  123. package/prebuilt/android/x86_64/lib/libvpx.a +0 -0
  124. package/prebuilt/include/dav1d/common.h +94 -0
  125. package/prebuilt/include/dav1d/data.h +117 -0
  126. package/prebuilt/include/dav1d/dav1d.h +329 -0
  127. package/prebuilt/include/dav1d/headers.h +440 -0
  128. package/prebuilt/include/dav1d/picture.h +157 -0
  129. package/prebuilt/include/dav1d/version.h +50 -0
  130. package/prebuilt/include/vpx/vp8.h +136 -0
  131. package/prebuilt/include/vpx/vp8dx.h +228 -0
  132. package/prebuilt/include/vpx/vpx_codec.h +475 -0
  133. package/prebuilt/include/vpx/vpx_decoder.h +365 -0
  134. package/prebuilt/include/vpx/vpx_frame_buffer.h +83 -0
  135. package/prebuilt/include/vpx/vpx_image.h +209 -0
  136. package/prebuilt/include/vpx/vpx_integer.h +40 -0
  137. package/prebuilt/ios/lib/libdav1d.a +0 -0
  138. package/prebuilt/ios/lib/libvpx.a +0 -0
  139. package/prebuilt/ios-simulator/include/dav1d/common.h +94 -0
  140. package/prebuilt/ios-simulator/include/dav1d/data.h +117 -0
  141. package/prebuilt/ios-simulator/include/dav1d/dav1d.h +329 -0
  142. package/prebuilt/ios-simulator/include/dav1d/headers.h +440 -0
  143. package/prebuilt/ios-simulator/include/dav1d/picture.h +157 -0
  144. package/prebuilt/ios-simulator/include/dav1d/version.h +50 -0
  145. package/prebuilt/ios-simulator/include/vpx/vp8.h +136 -0
  146. package/prebuilt/ios-simulator/include/vpx/vp8dx.h +228 -0
  147. package/prebuilt/ios-simulator/include/vpx/vpx_codec.h +475 -0
  148. package/prebuilt/ios-simulator/include/vpx/vpx_decoder.h +365 -0
  149. package/prebuilt/ios-simulator/include/vpx/vpx_frame_buffer.h +83 -0
  150. package/prebuilt/ios-simulator/include/vpx/vpx_image.h +209 -0
  151. package/prebuilt/ios-simulator/include/vpx/vpx_integer.h +40 -0
  152. package/prebuilt/ios-simulator/lib/libdav1d.a +0 -0
  153. package/prebuilt/ios-simulator/lib/libvpx.a +0 -0
  154. package/react-native/android/CMakeLists.txt +14 -0
  155. package/react-native/android/build.gradle +110 -0
  156. package/react-native/android/src/main/AndroidManifest.xml +1 -0
  157. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModule.java +102 -0
  158. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoPackage.java +50 -0
  159. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManager.java +317 -0
  160. package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +10 -0
  161. package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +23 -0
  162. package/react-native/android/src/norangesource/java/dev/mediacache/alphavideo/AppDataSource.java +15 -0
  163. package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +30 -0
  164. package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +61 -0
  165. package/react-native/ios/RNAlphaVideoViewComponentView.mm +173 -0
  166. package/react-native/ios/RNAlphaVideoViewManager.mm +147 -0
  167. package/react-native/src/AlphaVideoView.tsx +329 -0
  168. package/react-native/src/AlphaVideoViewNativeComponent.ts +132 -0
  169. package/react-native/src/AlphaVideoViewNativeComponent.web.ts +5 -0
  170. package/react-native/src/NativeAlphaVideoModule.ts +22 -0
  171. package/react-native/src/NativeAlphaVideoModule.web.ts +2 -0
  172. package/react-native/src/WebPlayer.tsx +7 -0
  173. package/react-native/src/WebPlayer.web.tsx +60 -0
  174. package/react-native/src/index.tsx +144 -0
  175. package/react-native-alpha-video.podspec +99 -0
  176. package/react-native.config.js +12 -0
  177. package/scripts/build_codecs_android.sh +246 -0
  178. package/scripts/build_codecs_ios.sh +31 -0
  179. package/scripts/build_codecs_ios_device.sh +210 -0
  180. package/scripts/build_codecs_ios_simulator.sh +256 -0
  181. package/scripts/build_wasm.sh +230 -0
  182. package/scripts/build_xcframework.sh +49 -0
  183. package/scripts/fetch-prebuilt.sh +48 -0
  184. package/scripts/package_release.sh +107 -0
  185. package/scripts/publish-android.sh +85 -0
  186. package/scripts/vendor_range_source.sh +47 -0
  187. package/third_party/libwebm/AUTHORS.TXT +5 -0
  188. package/third_party/libwebm/LICENSE.TXT +30 -0
  189. package/third_party/libwebm/PATENTS.TXT +23 -0
  190. package/third_party/libwebm/README.md +7 -0
  191. package/third_party/libwebm/webm_parser/include/webm/buffer_reader.h +138 -0
  192. package/third_party/libwebm/webm_parser/include/webm/callback.h +363 -0
  193. package/third_party/libwebm/webm_parser/include/webm/dom_types.h +1781 -0
  194. package/third_party/libwebm/webm_parser/include/webm/element.h +208 -0
  195. package/third_party/libwebm/webm_parser/include/webm/file_reader.h +103 -0
  196. package/third_party/libwebm/webm_parser/include/webm/id.h +1085 -0
  197. package/third_party/libwebm/webm_parser/include/webm/istream_reader.h +99 -0
  198. package/third_party/libwebm/webm_parser/include/webm/reader.h +94 -0
  199. package/third_party/libwebm/webm_parser/include/webm/status.h +166 -0
  200. package/third_party/libwebm/webm_parser/include/webm/webm_parser.h +133 -0
  201. package/third_party/libwebm/webm_parser/src/ancestory.cc +334 -0
  202. package/third_party/libwebm/webm_parser/src/ancestory.h +83 -0
  203. package/third_party/libwebm/webm_parser/src/audio_parser.h +84 -0
  204. package/third_party/libwebm/webm_parser/src/bit_utils.cc +26 -0
  205. package/third_party/libwebm/webm_parser/src/bit_utils.h +24 -0
  206. package/third_party/libwebm/webm_parser/src/block_additions_parser.h +30 -0
  207. package/third_party/libwebm/webm_parser/src/block_group_parser.h +71 -0
  208. package/third_party/libwebm/webm_parser/src/block_header_parser.cc +76 -0
  209. package/third_party/libwebm/webm_parser/src/block_header_parser.h +65 -0
  210. package/third_party/libwebm/webm_parser/src/block_more_parser.h +32 -0
  211. package/third_party/libwebm/webm_parser/src/block_parser.cc +285 -0
  212. package/third_party/libwebm/webm_parser/src/block_parser.h +126 -0
  213. package/third_party/libwebm/webm_parser/src/bool_parser.h +95 -0
  214. package/third_party/libwebm/webm_parser/src/buffer_reader.cc +110 -0
  215. package/third_party/libwebm/webm_parser/src/byte_parser.h +144 -0
  216. package/third_party/libwebm/webm_parser/src/callback.cc +155 -0
  217. package/third_party/libwebm/webm_parser/src/chapter_atom_parser.h +42 -0
  218. package/third_party/libwebm/webm_parser/src/chapter_display_parser.h +34 -0
  219. package/third_party/libwebm/webm_parser/src/chapters_parser.h +28 -0
  220. package/third_party/libwebm/webm_parser/src/cluster_parser.h +48 -0
  221. package/third_party/libwebm/webm_parser/src/colour_parser.h +55 -0
  222. package/third_party/libwebm/webm_parser/src/content_enc_aes_settings_parser.h +32 -0
  223. package/third_party/libwebm/webm_parser/src/content_encoding_parser.h +38 -0
  224. package/third_party/libwebm/webm_parser/src/content_encodings_parser.h +30 -0
  225. package/third_party/libwebm/webm_parser/src/content_encryption_parser.h +37 -0
  226. package/third_party/libwebm/webm_parser/src/cue_point_parser.h +38 -0
  227. package/third_party/libwebm/webm_parser/src/cue_track_positions_parser.h +39 -0
  228. package/third_party/libwebm/webm_parser/src/cues_parser.h +27 -0
  229. package/third_party/libwebm/webm_parser/src/date_parser.cc +72 -0
  230. package/third_party/libwebm/webm_parser/src/date_parser.h +64 -0
  231. package/third_party/libwebm/webm_parser/src/ebml_parser.h +48 -0
  232. package/third_party/libwebm/webm_parser/src/edition_entry_parser.h +35 -0
  233. package/third_party/libwebm/webm_parser/src/element_parser.h +102 -0
  234. package/third_party/libwebm/webm_parser/src/file_reader.cc +142 -0
  235. package/third_party/libwebm/webm_parser/src/float_parser.cc +77 -0
  236. package/third_party/libwebm/webm_parser/src/float_parser.h +65 -0
  237. package/third_party/libwebm/webm_parser/src/id_element_parser.cc +49 -0
  238. package/third_party/libwebm/webm_parser/src/id_element_parser.h +56 -0
  239. package/third_party/libwebm/webm_parser/src/id_parser.cc +75 -0
  240. package/third_party/libwebm/webm_parser/src/id_parser.h +45 -0
  241. package/third_party/libwebm/webm_parser/src/info_parser.h +44 -0
  242. package/third_party/libwebm/webm_parser/src/int_parser.h +121 -0
  243. package/third_party/libwebm/webm_parser/src/istream_reader.cc +133 -0
  244. package/third_party/libwebm/webm_parser/src/master_parser.cc +298 -0
  245. package/third_party/libwebm/webm_parser/src/master_parser.h +230 -0
  246. package/third_party/libwebm/webm_parser/src/master_value_parser.h +533 -0
  247. package/third_party/libwebm/webm_parser/src/mastering_metadata_parser.h +57 -0
  248. package/third_party/libwebm/webm_parser/src/parser.h +34 -0
  249. package/third_party/libwebm/webm_parser/src/parser_utils.cc +34 -0
  250. package/third_party/libwebm/webm_parser/src/parser_utils.h +64 -0
  251. package/third_party/libwebm/webm_parser/src/projection_parser.h +40 -0
  252. package/third_party/libwebm/webm_parser/src/recursive_parser.h +93 -0
  253. package/third_party/libwebm/webm_parser/src/seek_head_parser.h +27 -0
  254. package/third_party/libwebm/webm_parser/src/seek_parser.h +37 -0
  255. package/third_party/libwebm/webm_parser/src/segment_parser.cc +86 -0
  256. package/third_party/libwebm/webm_parser/src/segment_parser.h +53 -0
  257. package/third_party/libwebm/webm_parser/src/simple_tag_parser.h +37 -0
  258. package/third_party/libwebm/webm_parser/src/size_parser.cc +56 -0
  259. package/third_party/libwebm/webm_parser/src/size_parser.h +43 -0
  260. package/third_party/libwebm/webm_parser/src/skip_callback.h +63 -0
  261. package/third_party/libwebm/webm_parser/src/skip_parser.cc +59 -0
  262. package/third_party/libwebm/webm_parser/src/skip_parser.h +35 -0
  263. package/third_party/libwebm/webm_parser/src/slices_parser.h +30 -0
  264. package/third_party/libwebm/webm_parser/src/tag_parser.h +37 -0
  265. package/third_party/libwebm/webm_parser/src/tags_parser.h +27 -0
  266. package/third_party/libwebm/webm_parser/src/targets_parser.h +35 -0
  267. package/third_party/libwebm/webm_parser/src/time_slice_parser.h +30 -0
  268. package/third_party/libwebm/webm_parser/src/track_entry_parser.h +65 -0
  269. package/third_party/libwebm/webm_parser/src/tracks_parser.h +27 -0
  270. package/third_party/libwebm/webm_parser/src/unknown_parser.cc +49 -0
  271. package/third_party/libwebm/webm_parser/src/unknown_parser.h +38 -0
  272. package/third_party/libwebm/webm_parser/src/var_int_parser.cc +71 -0
  273. package/third_party/libwebm/webm_parser/src/var_int_parser.h +56 -0
  274. package/third_party/libwebm/webm_parser/src/video_parser.h +122 -0
  275. package/third_party/libwebm/webm_parser/src/virtual_block_parser.cc +72 -0
  276. package/third_party/libwebm/webm_parser/src/virtual_block_parser.h +69 -0
  277. package/third_party/libwebm/webm_parser/src/void_parser.cc +49 -0
  278. package/third_party/libwebm/webm_parser/src/void_parser.h +41 -0
  279. package/third_party/libwebm/webm_parser/src/webm_parser.cc +282 -0
  280. package/third_party/range-source/LICENSE +21 -0
  281. package/third_party/range-source/README.md +8 -0
  282. package/third_party/range-source/include/range_source.h +395 -0
  283. package/third_party/range-source/include/range_source_download.h +309 -0
@@ -0,0 +1,984 @@
1
+ #include "AlphaDemuxer.h"
2
+
3
+ #include "Av1SequenceHeader.h"
4
+ #include "ByteSource.h"
5
+ #include "YuvToRgb.h"
6
+
7
+ #include <webm/callback.h>
8
+ #include <webm/reader.h>
9
+ #include <webm/status.h>
10
+ #include <webm/webm_parser.h>
11
+
12
+ #include <algorithm>
13
+ #include <cmath>
14
+ #include <cstring>
15
+ #include <deque>
16
+ #include <limits>
17
+ #include <map>
18
+ #include <numeric>
19
+ #include <string>
20
+
21
+ namespace webm {
22
+
23
+ namespace {
24
+
25
+ constexpr int64_t kNone = std::numeric_limits<int64_t>::min();
26
+ constexpr uint64_t kNoPosition = std::numeric_limits<uint64_t>::max();
27
+ constexpr size_t kReadBufferSize = 64 * 1024;
28
+ constexpr uint64_t kMaxFrameSize = 64 * 1024 * 1024;
29
+ // Without a DefaultDuration, the frame rate comes from this many colour timestamps
30
+ constexpr size_t kFrameRateSamples = 32;
31
+ // Opening stops looking for the first alpha packet this far past the first cluster (FFmpeg's probesize)
32
+ constexpr uint64_t kProbeBytes = 5000000;
33
+
34
+ // webm_parser's Reader over a ByteSource. Buffered: the parser reads element headers a few bytes
35
+ // at a time. A source's kPending is the parser's kWouldBlock, from which it resumes.
36
+ class ByteSourceReader : public Reader {
37
+ public:
38
+ explicit ByteSourceReader(ByteSource* source) : source_(source), buffer_(new uint8_t[kReadBufferSize]) {}
39
+
40
+ Status Read(std::size_t numToRead, std::uint8_t* buffer, std::uint64_t* numActuallyRead) override {
41
+ *numActuallyRead = 0;
42
+ if (numToRead == 0) return Status(Status::kOkCompleted);
43
+ if (pos_ < bufferPos_ || pos_ >= bufferPos_ + bufferSize_) {
44
+ if (numToRead >= kReadBufferSize) {
45
+ return advance(source_->read(pos_, buffer, numToRead), numToRead, numActuallyRead);
46
+ }
47
+ const int64_t n = source_->read(pos_, buffer_.get(), kReadBufferSize);
48
+ if (n <= 0) return advance(n, numToRead, numActuallyRead);
49
+ bufferPos_ = pos_;
50
+ bufferSize_ = static_cast<uint64_t>(n);
51
+ }
52
+ const uint64_t n = std::min<uint64_t>(numToRead, bufferPos_ + bufferSize_ - pos_);
53
+ std::memcpy(buffer, buffer_.get() + (pos_ - bufferPos_), n);
54
+ return advance(static_cast<int64_t>(n), numToRead, numActuallyRead);
55
+ }
56
+
57
+ // Moves without reading, but not past the end, so that the parser sees a truncated element
58
+ // as one.
59
+ Status Skip(std::uint64_t numToSkip, std::uint64_t* numActuallySkipped) override {
60
+ const int64_t size = source_->size();
61
+ uint64_t n = numToSkip;
62
+ if (size >= 0) n = std::min<uint64_t>(n, static_cast<uint64_t>(size) > pos_ ? size - pos_ : 0);
63
+ pos_ += n;
64
+ *numActuallySkipped = n;
65
+ if (n == numToSkip) return Status(Status::kOkCompleted);
66
+ return Status(n > 0 ? Status::kOkPartial : Status::kEndOfFile);
67
+ }
68
+
69
+ std::uint64_t Position() const override { return pos_; }
70
+
71
+ void seek(uint64_t pos) {
72
+ pos_ = pos;
73
+ failed_ = false;
74
+ pending_ = false;
75
+ }
76
+ bool failed() const { return failed_; }
77
+ // Whether the last read found the source without the bytes yet
78
+ bool pending() const { return pending_; }
79
+ void clearPending() { pending_ = false; }
80
+
81
+ private:
82
+ Status advance(int64_t n, std::size_t wanted, std::uint64_t* numActuallyRead) {
83
+ if (n == ByteSource::kPending) {
84
+ pending_ = true;
85
+ return Status(Status::kWouldBlock);
86
+ }
87
+ if (n < 0) failed_ = true;
88
+ if (n <= 0) return Status(Status::kEndOfFile);
89
+ pos_ += static_cast<uint64_t>(n);
90
+ *numActuallyRead = static_cast<uint64_t>(n);
91
+ return Status(static_cast<std::size_t>(n) == wanted ? Status::kOkCompleted : Status::kOkPartial);
92
+ }
93
+
94
+ ByteSource* source_;
95
+ std::unique_ptr<uint8_t[]> buffer_;
96
+ uint64_t bufferPos_ = 0;
97
+ uint64_t bufferSize_ = 0;
98
+ uint64_t pos_ = 0;
99
+ bool failed_ = false;
100
+ bool pending_ = false;
101
+ };
102
+
103
+ // a / d rounded to nearest, halves away from zero (FFmpeg's AV_ROUND_NEAR_INF), for d > 0.
104
+ int64_t divideRounded(int64_t a, int64_t d) {
105
+ return a >= 0 ? (a + d / 2) / d : -((-a + d / 2) / d);
106
+ }
107
+
108
+ // FFmpeg's av_reduce: the closest fraction to num/den whose terms are at most max. For positive
109
+ // num and den.
110
+ void reduceFraction(int64_t num, int64_t den, int64_t max, int64_t* outNum, int64_t* outDen) {
111
+ int64_t a0n = 0, a0d = 1, a1n = 1, a1d = 0;
112
+ const int64_t gcd = std::gcd(num, den);
113
+ if (gcd) {
114
+ num /= gcd;
115
+ den /= gcd;
116
+ }
117
+ if (num <= max && den <= max) {
118
+ a1n = num;
119
+ a1d = den;
120
+ den = 0;
121
+ }
122
+ while (den) {
123
+ int64_t x = num / den;
124
+ const int64_t nextDen = num - den * x;
125
+ const int64_t a2n = x * a1n + a0n;
126
+ const int64_t a2d = x * a1d + a0d;
127
+ if (a2n > max || a2d > max) {
128
+ if (a1n) x = (max - a0n) / a1n;
129
+ if (a1d) x = std::min(x, (max - a0d) / a1d);
130
+ if (den * (2 * x * a1d + a0d) > num * a1d) {
131
+ a1n = x * a1n + a0n;
132
+ a1d = x * a1d + a0d;
133
+ }
134
+ break;
135
+ }
136
+ a0n = a1n;
137
+ a0d = a1d;
138
+ a1n = a2n;
139
+ a1d = a2d;
140
+ num = den;
141
+ den = nextDen;
142
+ }
143
+ *outNum = a1n;
144
+ *outDen = a1d;
145
+ }
146
+
147
+ // From frame timestamps, snapped to a common rate within 1% of it.
148
+ double estimateFrameRate(const std::vector<int64_t>& ptsUs) {
149
+ if (ptsUs.size() < 2) return 0;
150
+ const auto [first, last] = std::minmax_element(ptsUs.begin(), ptsUs.end());
151
+ if (*last <= *first) return 0;
152
+ const double fps = (ptsUs.size() - 1) * 1e6 / static_cast<double>(*last - *first);
153
+ static const double kCommonRates[] = {
154
+ 24000.0 / 1001, 24, 25, 30000.0 / 1001, 30, 48, 50, 60000.0 / 1001, 60, 10, 12, 15, 120};
155
+ for (double rate : kCommonRates) {
156
+ if (std::fabs(fps - rate) < rate * 0.01) return rate;
157
+ }
158
+ return fps;
159
+ }
160
+
161
+ struct BitReader {
162
+ const uint8_t* data;
163
+ int bitPos = 0;
164
+ int bits(int n) {
165
+ int v = 0;
166
+ for (; n > 0; n--, bitPos++) v = (v << 1) | ((data[bitPos >> 3] >> (7 - (bitPos & 7))) & 1);
167
+ return v;
168
+ }
169
+ };
170
+
171
+ struct VpxHeader {
172
+ int profile = 0;
173
+ int bitDepth = 8;
174
+ int colorSpace = kMatrixUnspecified;
175
+ int colorRange = kRangeLimited;
176
+ int width = 0;
177
+ int height = 0;
178
+ };
179
+
180
+ // A VP9 keyframe's uncompressed header, as FFmpeg's vp9 decoder reads it (VP9 spec 6.2).
181
+ bool parseVp9KeyframeHeader(const uint8_t* data, size_t size, VpxHeader* out) {
182
+ if (!data || size < 10) return false; // The fields read below fit in 10 bytes
183
+ BitReader r{data};
184
+ if (r.bits(2) != 2) return false; // frame_marker
185
+ int profile = r.bits(1);
186
+ profile |= r.bits(1) << 1;
187
+ if (profile == 3) r.bits(1);
188
+ if (r.bits(1)) return false; // show_existing_frame
189
+ if (r.bits(1)) return false; // frame_type: not a keyframe
190
+ r.bits(2); // show_frame, error_resilient_mode
191
+ if (r.bits(24) != 0x498342) return false; // sync code
192
+
193
+ out->profile = profile;
194
+ out->bitDepth = profile >= 2 ? (r.bits(1) ? 12 : 10) : 8;
195
+ static const int kColorSpaces[8] = {kMatrixUnspecified, kMatrixBt470bg, kMatrixBt709, kMatrixSmpte170m,
196
+ kMatrixSmpte240m, kMatrixBt2020Ncl, kMatrixReserved, kMatrixRgb};
197
+ out->colorSpace = kColorSpaces[r.bits(3)];
198
+ if (out->colorSpace != kMatrixRgb) {
199
+ out->colorRange = r.bits(1) ? kRangeFull : kRangeLimited;
200
+ if (profile & 1) r.bits(3); // subsampling_x, subsampling_y, reserved_zero
201
+ } else {
202
+ out->colorRange = kRangeFull;
203
+ if (profile & 1) r.bits(1); // reserved_zero
204
+ }
205
+ out->width = r.bits(16) + 1;
206
+ out->height = r.bits(16) + 1;
207
+ return true;
208
+ }
209
+
210
+ // VP8's boolean decoder (RFC 6386 7.3), for the first bools of a frame's first partition
211
+ struct BoolDecoder {
212
+ const uint8_t* p;
213
+ const uint8_t* end;
214
+ uint32_t value = 0;
215
+ uint32_t range = 255;
216
+ int bitCount = 0;
217
+
218
+ BoolDecoder(const uint8_t* data, size_t size) : p(data + 2), end(data + size) {
219
+ value = (static_cast<uint32_t>(data[0]) << 8) | data[1];
220
+ }
221
+
222
+ int readBool(int probability) {
223
+ const uint32_t split = 1 + (((range - 1) * static_cast<uint32_t>(probability)) >> 8);
224
+ const uint32_t bigSplit = split << 8;
225
+ int bit = 0;
226
+ if (value >= bigSplit) {
227
+ bit = 1;
228
+ range -= split;
229
+ value -= bigSplit;
230
+ } else {
231
+ range = split;
232
+ }
233
+ while (range < 128) {
234
+ value <<= 1;
235
+ range <<= 1;
236
+ if (++bitCount == 8) {
237
+ bitCount = 0;
238
+ if (p < end) value |= *p++;
239
+ }
240
+ }
241
+ return bit;
242
+ }
243
+ };
244
+
245
+ // A VP8 keyframe's header, as FFmpeg's vp8 decoder reads it (RFC 6386 9.1-9.2). There is no
246
+ // matrix for VP8 but BT.601, which color_space 0 says, and clamping_type 1 says full range.
247
+ bool parseVp8KeyframeHeader(const uint8_t* data, size_t size, VpxHeader* out) {
248
+ if (!data || size < 12) return false; // The frame tag, start code, size and 2 bytes of bools
249
+ if (data[0] & 1) return false; // frame type: not a keyframe
250
+ if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a) return false; // start code
251
+ BoolDecoder bools(data + 10, size - 10);
252
+ const int colorSpace = bools.readBool(128);
253
+ const int clampingType = bools.readBool(128);
254
+
255
+ out->profile = (data[0] >> 1) & 7; // version
256
+ out->bitDepth = 8;
257
+ out->colorSpace = colorSpace == 0 ? kMatrixBt470bg : kMatrixUnspecified;
258
+ out->colorRange = clampingType ? kRangeFull : kRangeLimited;
259
+ out->width = (data[6] | (data[7] << 8)) & 0x3fff;
260
+ out->height = (data[8] | (data[9] << 8)) & 0x3fff;
261
+ return true;
262
+ }
263
+
264
+ // The sequence header from av1C's configOBUs, as FFmpeg's libdav1d reads it, or else from the
265
+ // first packet.
266
+ bool parseAv1TrackHeader(const std::vector<uint8_t>& av1C, const std::vector<uint8_t>& firstPacket,
267
+ Av1SequenceHeader* out) {
268
+ if (av1C.size() > 4 && (av1C[0] & 0x80) && parseAv1SequenceHeader(av1C.data() + 4, av1C.size() - 4, out)) {
269
+ return true;
270
+ }
271
+ return !firstPacket.empty() && parseAv1SequenceHeader(firstPacket.data(), firstPacket.size(), out);
272
+ }
273
+
274
+ // The track's Colour element, as FFmpeg's Matroska demuxer takes it.
275
+ void readContainerColour(const TrackEntry& entry, AlphaDemuxer::StreamInfo* s) {
276
+ if (!entry.video.is_present() || !entry.video.value().colour.is_present()) return;
277
+ const Colour& colour = entry.video.value().colour.value();
278
+ const int matrix = static_cast<int>(colour.matrix_coefficients.value());
279
+ const int primaries = static_cast<int>(colour.primaries.value());
280
+ const int trc = static_cast<int>(colour.transfer_characteristics.value());
281
+ const int range = static_cast<int>(colour.range.value());
282
+ if (matrix != kMatrixReserved) s->colorSpace = matrix;
283
+ if (primaries != 0 && primaries != 3) s->colorPrimaries = primaries;
284
+ if (trc != 0 && trc != 3) s->colorTrc = trc;
285
+ if (range == kRangeLimited || range == kRangeFull) s->colorRange = range;
286
+ }
287
+
288
+ } // namespace
289
+
290
+ bool isVp9Keyframe(const uint8_t* data, int size) {
291
+ if (!data || size < 1) return false;
292
+ const uint8_t b = data[0];
293
+ if ((b >> 6) != 2) return false; // frame_marker
294
+ const int profile = ((b >> 5) & 1) | (((b >> 4) & 1) << 1);
295
+ // profile 3 has a reserved bit before show_existing_frame
296
+ const int showExistingBit = profile == 3 ? 2 : 3;
297
+ if ((b >> showExistingBit) & 1) return false;
298
+ return ((b >> (showExistingBit - 1)) & 1) == 0; // frame_type 0 is a keyframe
299
+ }
300
+
301
+ bool isVp8Keyframe(const uint8_t* data, int size) {
302
+ return data && size >= 3 && (data[0] & 1) == 0; // frame type 0 is a keyframe
303
+ }
304
+
305
+ /**
306
+ * Reads the file through webm_parser, which calls back per element. The callbacks pause it at the
307
+ * end of each block it returns packets for, so packets are read one block at a time.
308
+ *
309
+ * Seeking, like FFmpeg's: to the colour keyframe at or before the time, from an index of the
310
+ * keyframes read so far (complete up to where the file has been read through from the start)
311
+ * and the Cues, read when first needed. Past the last keyframe known, the index grows by reading
312
+ * on. Then every block before that keyframe's time is skipped.
313
+ */
314
+ class AlphaDemuxer::Impl : public Callback {
315
+ public:
316
+ explicit Impl(std::unique_ptr<ByteSource> source)
317
+ : source_(std::move(source)), reader_(source_.get()) {}
318
+
319
+ AlphaDemuxer::Progress tryOpen();
320
+ const AlphaDemuxer::Info& info() const { return info_; }
321
+ bool readPacket(AlphaDemuxer::Packet* out);
322
+ bool failed() const { return reader_.failed(); }
323
+ bool pending() const { return pending_; }
324
+ void seekToStart();
325
+ void seekToTimeUs(int64_t timeUs);
326
+
327
+ Status OnElementBegin(const ElementMetadata& metadata, Action* action) override;
328
+ Status OnSegmentBegin(const ElementMetadata& metadata, Action* action) override;
329
+ Status OnSeek(const ElementMetadata& metadata, const Seek& seek) override;
330
+ Status OnInfo(const ElementMetadata& metadata, const webm::Info& info) override;
331
+ Status OnTrackEntry(const ElementMetadata& metadata, const TrackEntry& entry) override;
332
+ Status OnClusterBegin(const ElementMetadata& metadata, const Cluster& cluster, Action* action) override;
333
+ Status OnSimpleBlockBegin(const ElementMetadata& metadata, const SimpleBlock& block, Action* action) override;
334
+ Status OnSimpleBlockEnd(const ElementMetadata& metadata, const SimpleBlock& block) override;
335
+ Status OnBlockGroupBegin(const ElementMetadata& metadata, Action* action) override;
336
+ Status OnBlockBegin(const ElementMetadata& metadata, const Block& block, Action* action) override;
337
+ Status OnBlockGroupEnd(const ElementMetadata& metadata, const BlockGroup& group) override;
338
+ Status OnFrame(const FrameMetadata& metadata, Reader* reader, std::uint64_t* bytesRemaining) override;
339
+ Status OnCuePoint(const ElementMetadata& metadata, const CuePoint& cuePoint) override;
340
+
341
+ private:
342
+ // Reads only the Cues element, for its cue points.
343
+ class CuesCallback : public Callback {
344
+ public:
345
+ explicit CuesCallback(Impl* impl) : impl_(impl) {}
346
+ Status OnElementBegin(const ElementMetadata& metadata, Action* action) override;
347
+ Status OnCuePoint(const ElementMetadata&, const CuePoint& cuePoint) override {
348
+ impl_->addCuePoint(cuePoint);
349
+ return Status(Status::kOkCompleted);
350
+ }
351
+
352
+ private:
353
+ Impl* impl_;
354
+ uint64_t end_ = 0;
355
+ };
356
+
357
+ enum class Mode {
358
+ kProbe, // Opening: keeps the first packets, for the bitstream's info
359
+ kPackets, // Queues packets
360
+ kScan, // Indexes keyframes up to scanUntil_, without reading frames
361
+ };
362
+
363
+ struct Track {
364
+ uint64_t number = 0; // 0 when there's no such track
365
+ AlphaDemuxer::Codec codec = AlphaDemuxer::VP9;
366
+ TrackEntry entry;
367
+ };
368
+
369
+ struct OwnedPacket {
370
+ int stream;
371
+ std::vector<uint8_t> data;
372
+ int64_t ptsUs;
373
+ int frameIndex;
374
+ bool key;
375
+ };
376
+
377
+ enum class Fed {
378
+ kPaused, // A callback paused the parser
379
+ kPending, // The source doesn't have the next bytes yet
380
+ kEnded, // The end of the file, or an error
381
+ };
382
+
383
+ Fed feed();
384
+ void restartAt(int64_t position);
385
+ bool probeComplete() const;
386
+ void buildInfo();
387
+ AlphaDemuxer::StreamInfo streamInfoOf(const Track& track, const std::vector<uint8_t>& firstPacket,
388
+ VpxHeader* vpx) const;
389
+ bool findKeyframe(int64_t ticks, std::map<int64_t, uint64_t>::const_iterator* out);
390
+ std::map<int64_t, uint64_t>::const_iterator keyframeAtOrBefore(int64_t ticks) const;
391
+ bool scan(uint64_t from, int64_t untilTicks);
392
+ bool loadCues();
393
+ void addCuePoint(const CuePoint& cuePoint);
394
+
395
+ void beginBlock(uint64_t trackNumber, int16_t timecode);
396
+ Status noteColourBlock();
397
+ Status finishBlock(const std::vector<uint8_t>* blockAdditional);
398
+ Status keepProbePackets(const std::vector<uint8_t>* blockAdditional);
399
+
400
+ int64_t ticksToUs(int64_t ticks) const;
401
+ int64_t usToTicks(int64_t us) const;
402
+
403
+ std::unique_ptr<ByteSource> source_;
404
+ ByteSourceReader reader_;
405
+ WebmParser parser_;
406
+ bool ended_ = false;
407
+ bool opened_ = false;
408
+ bool pending_ = false; // The last call stopped at a kPending read
409
+ Mode mode_ = Mode::kProbe;
410
+ AlphaDemuxer::Info info_;
411
+
412
+ // From the headers
413
+ int64_t segmentDataStart_ = -1;
414
+ int64_t cuesPosition_ = -1;
415
+ int64_t firstClusterPosition_ = -1;
416
+ uint64_t timecodeScale_ = 1000000; // Nanoseconds per tick
417
+ double durationTicks_ = 0;
418
+ Track colour_;
419
+ Track alpha_;
420
+
421
+ // The block being read
422
+ uint64_t clusterPosition_ = 0;
423
+ int64_t clusterTicks_ = 0;
424
+ int stream_ = -1; // 0 colour, 1 alpha, -1 neither
425
+ int64_t blockTicks_ = 0;
426
+ bool key_ = false;
427
+ bool discard_ = false; // Not returned; its frames are skipped
428
+ bool inBlockGroup_ = false;
429
+ std::vector<std::vector<uint8_t>> frames_;
430
+ uint64_t framePosition_ = kNoPosition;
431
+ // A block end paused the parser; the parser calls it again when it resumes
432
+ bool pausedAtBlockEnd_ = false;
433
+
434
+ // Opening
435
+ bool haveColourPacket_ = false;
436
+ bool haveAlphaPacket_ = false;
437
+ std::vector<uint8_t> firstColourPacket_;
438
+ std::vector<uint8_t> firstAlphaPacket_;
439
+ bool firstColourHasBlockAdditional_ = false;
440
+ int64_t startTicks_ = 0;
441
+ std::vector<int64_t> colourPtsSamples_;
442
+
443
+ // Reading
444
+ std::deque<OwnedPacket> queue_;
445
+ OwnedPacket current_{};
446
+ int colourFrameIndex_ = 0;
447
+ int alphaFrameIndex_ = 0;
448
+ int64_t skipBefore_ = kNone; // After a seek, blocks before this tick are skipped
449
+ bool colourNeedsKey_ = false; // After a seek, colour blocks are skipped until a keyframe
450
+
451
+ // Seeking: colour keyframes' ticks -> their clusters' positions
452
+ std::map<int64_t, uint64_t> keyframes_;
453
+ bool readingFromStart_ = true; // Every block since the first has been seen
454
+ int64_t indexedUpTo_ = kNone; // keyframes_ has every keyframe up to this tick
455
+ bool cuesLoaded_ = false;
456
+ int64_t scanUntil_ = 0;
457
+ bool scanStopped_ = false;
458
+ };
459
+
460
+ // --- Reading -------------------------------------------------------------------------------
461
+
462
+ int64_t AlphaDemuxer::Impl::ticksToUs(int64_t ticks) const {
463
+ if (timecodeScale_ % 1000 == 0) return ticks * static_cast<int64_t>(timecodeScale_ / 1000);
464
+ return std::llround(static_cast<double>(ticks) * static_cast<double>(timecodeScale_) / 1000.0);
465
+ }
466
+
467
+ int64_t AlphaDemuxer::Impl::usToTicks(int64_t us) const {
468
+ if (timecodeScale_ % 1000 == 0) return divideRounded(us, static_cast<int64_t>(timecodeScale_ / 1000));
469
+ return std::llround(static_cast<double>(us) * 1000.0 / static_cast<double>(timecodeScale_));
470
+ }
471
+
472
+ // Feeds the parser until a callback pauses it, the source has no more bytes yet, or the file
473
+ // ends or can't be read.
474
+ AlphaDemuxer::Impl::Fed AlphaDemuxer::Impl::feed() {
475
+ if (ended_) return Fed::kEnded;
476
+ reader_.clearPending();
477
+ const Status status = parser_.Feed(this, &reader_);
478
+ if (status.code == Status::kWouldBlock) return reader_.pending() ? Fed::kPending : Fed::kPaused;
479
+ ended_ = true;
480
+ // Read through from the start: every keyframe is indexed
481
+ if (status.completed_ok() && readingFromStart_) indexedUpTo_ = std::numeric_limits<int64_t>::max();
482
+ return Fed::kEnded;
483
+ }
484
+
485
+ void AlphaDemuxer::Impl::restartAt(int64_t position) {
486
+ queue_.clear();
487
+ frames_.clear();
488
+ framePosition_ = kNoPosition;
489
+ pausedAtBlockEnd_ = false;
490
+ inBlockGroup_ = false;
491
+ readingFromStart_ = false;
492
+ skipBefore_ = kNone;
493
+ colourNeedsKey_ = false;
494
+ if (position < 0) {
495
+ ended_ = true;
496
+ return;
497
+ }
498
+ reader_.seek(static_cast<uint64_t>(position));
499
+ parser_.DidSeek();
500
+ ended_ = false;
501
+ }
502
+
503
+ bool AlphaDemuxer::Impl::readPacket(AlphaDemuxer::Packet* out) {
504
+ pending_ = false;
505
+ while (queue_.empty()) {
506
+ const Fed fed = feed();
507
+ if (fed == Fed::kPaused) continue;
508
+ pending_ = fed == Fed::kPending;
509
+ return false;
510
+ }
511
+ current_ = std::move(queue_.front());
512
+ queue_.pop_front();
513
+ out->stream = current_.stream;
514
+ out->data = current_.data.data();
515
+ out->size = static_cast<int>(current_.data.size());
516
+ out->ptsUs = current_.ptsUs;
517
+ out->frameIndex = current_.frameIndex;
518
+ out->key = current_.key;
519
+ return true;
520
+ }
521
+
522
+ // --- Opening -------------------------------------------------------------------------------
523
+
524
+ bool AlphaDemuxer::Impl::probeComplete() const {
525
+ if (!haveColourPacket_) return false;
526
+ if (reader_.Position() > static_cast<uint64_t>(firstClusterPosition_) + kProbeBytes) return true;
527
+ const bool needFrameRate = !colour_.entry.default_duration.is_present() ||
528
+ colour_.entry.default_duration.value() == 0;
529
+ return (!alpha_.number || haveAlphaPacket_) &&
530
+ (!needFrameRate || colourPtsSamples_.size() >= kFrameRateSamples);
531
+ }
532
+
533
+ AlphaDemuxer::Progress AlphaDemuxer::Impl::tryOpen() {
534
+ if (opened_) return AlphaDemuxer::Progress::kDone;
535
+ // The parser pauses once the first packets are kept, or at the first cluster if there's no track
536
+ while (!probeComplete()) {
537
+ const Fed fed = feed();
538
+ if (fed == Fed::kPending) return AlphaDemuxer::Progress::kPending;
539
+ if (fed == Fed::kEnded || !colour_.number) break;
540
+ }
541
+ if (!colour_.number) return AlphaDemuxer::Progress::kFailed;
542
+ buildInfo();
543
+ firstColourPacket_ = {};
544
+ firstAlphaPacket_ = {};
545
+ colourPtsSamples_ = {};
546
+ mode_ = Mode::kPackets;
547
+ seekToStart();
548
+ opened_ = true;
549
+ return AlphaDemuxer::Progress::kDone;
550
+ }
551
+
552
+ AlphaDemuxer::StreamInfo AlphaDemuxer::Impl::streamInfoOf(const Track& track, const std::vector<uint8_t>& firstPacket,
553
+ VpxHeader* vpx) const {
554
+ AlphaDemuxer::StreamInfo s;
555
+ s.codec = track.codec;
556
+ readContainerColour(track.entry, &s);
557
+ if (track.codec == AlphaDemuxer::VP9) {
558
+ // VP9's range and matrix are the bitstream's; primaries and transfer the container's
559
+ if (parseVp9KeyframeHeader(firstPacket.data(), firstPacket.size(), vpx)) {
560
+ s.profile = vpx->profile;
561
+ s.bitDepth = vpx->bitDepth;
562
+ s.colorSpace = vpx->colorSpace;
563
+ s.colorRange = vpx->colorRange;
564
+ }
565
+ return s;
566
+ }
567
+ if (track.codec == AlphaDemuxer::VP8) {
568
+ // VP8's range is the bitstream's, and its matrix unless color_space 1 leaves it open
569
+ if (parseVp8KeyframeHeader(firstPacket.data(), firstPacket.size(), vpx)) {
570
+ s.profile = vpx->profile;
571
+ s.bitDepth = vpx->bitDepth;
572
+ if (vpx->colorSpace != kMatrixUnspecified) s.colorSpace = vpx->colorSpace;
573
+ s.colorRange = vpx->colorRange;
574
+ }
575
+ return s;
576
+ }
577
+
578
+ s.codecPrivate = track.entry.codec_private.value();
579
+ Av1SequenceHeader seq;
580
+ if (parseAv1TrackHeader(s.codecPrivate, firstPacket, &seq)) {
581
+ s.profile = seq.profile;
582
+ s.bitDepth = seq.bitDepth;
583
+ s.colorRange = seq.fullRange ? kRangeFull : kRangeLimited;
584
+ s.colorSpace = seq.matrixCoefficients;
585
+ s.colorPrimaries = seq.colorPrimaries;
586
+ s.colorTrc = seq.transferCharacteristics;
587
+ } else if (s.codecPrivate.size() >= 3) {
588
+ const uint8_t flags = s.codecPrivate[2];
589
+ s.bitDepth = (flags & 0x40) ? ((flags & 0x20) ? 12 : 10) : 8;
590
+ }
591
+ return s;
592
+ }
593
+
594
+ void AlphaDemuxer::Impl::buildInfo() {
595
+ const TrackEntry& entry = colour_.entry;
596
+ if (entry.video.is_present()) {
597
+ info_.width = static_cast<int>(entry.video.value().pixel_width.value());
598
+ info_.height = static_cast<int>(entry.video.value().pixel_height.value());
599
+ }
600
+ VpxHeader vpx;
601
+ info_.color = streamInfoOf(colour_, firstColourPacket_, &vpx);
602
+ if (vpx.width > 0) {
603
+ info_.width = vpx.width;
604
+ info_.height = vpx.height;
605
+ }
606
+
607
+ if (entry.default_duration.is_present() && entry.default_duration.value() > 0) {
608
+ int64_t num = 0, den = 0;
609
+ reduceFraction(1000000000, static_cast<int64_t>(entry.default_duration.value()), 30000, &num, &den);
610
+ if (den > 0) info_.frameRate = static_cast<double>(num) / static_cast<double>(den);
611
+ } else {
612
+ std::vector<int64_t> ptsUs;
613
+ for (int64_t ticks : colourPtsSamples_) ptsUs.push_back(ticksToUs(ticks));
614
+ if (const double fps = estimateFrameRate(ptsUs); fps > 0) info_.frameRate = fps;
615
+ }
616
+
617
+ if (durationTicks_ > 0) {
618
+ info_.durationUs = static_cast<int64_t>(durationTicks_ * static_cast<double>(timecodeScale_) / 1000.0);
619
+ info_.totalFrames = static_cast<int>(std::lround(static_cast<double>(info_.durationUs) / 1e6 * info_.frameRate));
620
+ } else {
621
+ info_.totalFrames = 100;
622
+ }
623
+ info_.startUs = haveColourPacket_ ? ticksToUs(startTicks_) : 0;
624
+
625
+ if (alpha_.number) {
626
+ VpxHeader alphaVpx;
627
+ info_.alpha = streamInfoOf(alpha_, firstAlphaPacket_, &alphaVpx);
628
+ } else if ((colour_.codec == AlphaDemuxer::VP9 || colour_.codec == AlphaDemuxer::VP8) &&
629
+ firstColourHasBlockAdditional_) {
630
+ info_.alphaInBlockAdditional = true;
631
+ info_.alpha = info_.color;
632
+ // libvpx's alpha stream says limited range but holds alpha as-is, 0-255
633
+ info_.alpha.colorRange = kRangeFull;
634
+ }
635
+ info_.hasAlpha = alpha_.number != 0 || info_.alphaInBlockAdditional;
636
+ }
637
+
638
+ // --- Seeking -------------------------------------------------------------------------------
639
+
640
+ void AlphaDemuxer::Impl::seekToStart() {
641
+ restartAt(firstClusterPosition_);
642
+ readingFromStart_ = true;
643
+ colourFrameIndex_ = 0;
644
+ alphaFrameIndex_ = 0;
645
+ }
646
+
647
+ void AlphaDemuxer::Impl::seekToTimeUs(int64_t timeUs) {
648
+ pending_ = false;
649
+ std::map<int64_t, uint64_t>::const_iterator keyframe;
650
+ const bool found = findKeyframe(usToTicks(timeUs), &keyframe);
651
+ if (pending_) return;
652
+ if (!found) {
653
+ restartAt(firstClusterPosition_);
654
+ readingFromStart_ = true;
655
+ return;
656
+ }
657
+ const int64_t keyTicks = keyframe->first;
658
+ restartAt(static_cast<int64_t>(keyframe->second));
659
+ skipBefore_ = keyTicks;
660
+ colourNeedsKey_ = true;
661
+ }
662
+
663
+ std::map<int64_t, uint64_t>::const_iterator AlphaDemuxer::Impl::keyframeAtOrBefore(int64_t ticks) const {
664
+ // Clamped to the first, as FFmpeg does
665
+ auto it = keyframes_.upper_bound(std::max(ticks, keyframes_.begin()->first));
666
+ return --it;
667
+ }
668
+
669
+ // False if no keyframe is known, or if reading stopped at a kPending read (pending_).
670
+ bool AlphaDemuxer::Impl::findKeyframe(int64_t ticks, std::map<int64_t, uint64_t>::const_iterator* out) {
671
+ const bool pastIndexed = indexedUpTo_ == kNone || ticks > indexedUpTo_;
672
+ if (pastIndexed && !cuesLoaded_ && !loadCues()) return false;
673
+ // Past the last keyframe known, there may be later ones that aren't
674
+ if (pastIndexed &&
675
+ (keyframes_.empty() || std::next(keyframeAtOrBefore(ticks)) == keyframes_.end())) {
676
+ const uint64_t from = keyframes_.empty() ? static_cast<uint64_t>(firstClusterPosition_)
677
+ : keyframeAtOrBefore(ticks)->second;
678
+ if (!scan(from, ticks)) return false;
679
+ }
680
+ if (keyframes_.empty()) return false;
681
+ *out = keyframeAtOrBefore(ticks);
682
+ return true;
683
+ }
684
+
685
+ // Reads on from the cluster at `from` until a colour block after `untilTicks`, indexing keyframes.
686
+ // False if it stopped at a kPending read; the keyframes found up to there are kept.
687
+ bool AlphaDemuxer::Impl::scan(uint64_t from, int64_t untilTicks) {
688
+ if (firstClusterPosition_ < 0) return true;
689
+ restartAt(static_cast<int64_t>(from));
690
+ mode_ = Mode::kScan;
691
+ scanUntil_ = untilTicks;
692
+ scanStopped_ = false;
693
+ Fed fed = Fed::kPaused;
694
+ while (!scanStopped_ && (fed = feed()) == Fed::kPaused) {}
695
+ mode_ = Mode::kPackets;
696
+ pending_ = fed == Fed::kPending;
697
+ return !pending_;
698
+ }
699
+
700
+ // False if it stopped at a kPending read; the Cues are then read again next time.
701
+ bool AlphaDemuxer::Impl::loadCues() {
702
+ cuesLoaded_ = true;
703
+ if (cuesPosition_ < 0) return true;
704
+ ByteSourceReader reader(source_.get());
705
+ reader.seek(static_cast<uint64_t>(cuesPosition_));
706
+ WebmParser parser;
707
+ parser.DidSeek();
708
+ CuesCallback callback(this);
709
+ parser.Feed(&callback, &reader);
710
+ if (!reader.pending()) return true;
711
+ cuesLoaded_ = false;
712
+ pending_ = true;
713
+ return false;
714
+ }
715
+
716
+ Status AlphaDemuxer::Impl::CuesCallback::OnElementBegin(const ElementMetadata& metadata, Action* action) {
717
+ if (end_ == 0) {
718
+ if (metadata.id != Id::kCues) return Status(Status::kWouldBlock);
719
+ end_ = metadata.size == kUnknownElementSize ? kNoPosition
720
+ : metadata.position + metadata.header_size + metadata.size;
721
+ } else if (metadata.position >= end_) {
722
+ return Status(Status::kWouldBlock);
723
+ }
724
+ *action = Action::kRead;
725
+ return Status(Status::kOkCompleted);
726
+ }
727
+
728
+ void AlphaDemuxer::Impl::addCuePoint(const CuePoint& cuePoint) {
729
+ if (!cuePoint.time.is_present() || segmentDataStart_ < 0) return;
730
+ for (const Element<CueTrackPositions>& positions : cuePoint.cue_track_positions) {
731
+ const CueTrackPositions& p = positions.value();
732
+ if (p.track.value() != colour_.number || !p.cluster_position.is_present()) continue;
733
+ keyframes_.emplace(static_cast<int64_t>(cuePoint.time.value()),
734
+ static_cast<uint64_t>(segmentDataStart_) + p.cluster_position.value());
735
+ }
736
+ }
737
+
738
+ // --- Parser callbacks ----------------------------------------------------------------------
739
+
740
+ Status AlphaDemuxer::Impl::OnElementBegin(const ElementMetadata& metadata, Action* action) {
741
+ if (metadata.id == Id::kChapters || metadata.id == Id::kTags) {
742
+ *action = Action::kSkip;
743
+ } else {
744
+ if (metadata.id == Id::kCues) cuesLoaded_ = true;
745
+ *action = Action::kRead;
746
+ }
747
+ return Status(Status::kOkCompleted);
748
+ }
749
+
750
+ Status AlphaDemuxer::Impl::OnSegmentBegin(const ElementMetadata& metadata, Action* action) {
751
+ if (segmentDataStart_ < 0) segmentDataStart_ = static_cast<int64_t>(metadata.position + metadata.header_size);
752
+ *action = Action::kRead;
753
+ return Status(Status::kOkCompleted);
754
+ }
755
+
756
+ Status AlphaDemuxer::Impl::OnSeek(const ElementMetadata&, const Seek& seek) {
757
+ if (seek.id.value() == Id::kCues && seek.position.is_present() && cuesPosition_ < 0 && segmentDataStart_ >= 0) {
758
+ cuesPosition_ = segmentDataStart_ + static_cast<int64_t>(seek.position.value());
759
+ }
760
+ return Status(Status::kOkCompleted);
761
+ }
762
+
763
+ Status AlphaDemuxer::Impl::OnInfo(const ElementMetadata&, const webm::Info& info) {
764
+ if (info.timecode_scale.value() > 0) timecodeScale_ = info.timecode_scale.value();
765
+ if (info.duration.is_present()) durationTicks_ = info.duration.value();
766
+ return Status(Status::kOkCompleted);
767
+ }
768
+
769
+ Status AlphaDemuxer::Impl::OnTrackEntry(const ElementMetadata&, const TrackEntry& entry) {
770
+ if (entry.track_type.value() != TrackType::kVideo) return Status(Status::kOkCompleted);
771
+ const std::string& codecId = entry.codec_id.value();
772
+ if (codecId != "V_VP9" && codecId != "V_AV1" && codecId != "V_VP8") return Status(Status::kOkCompleted);
773
+ Track* track = !colour_.number ? &colour_ : !alpha_.number ? &alpha_ : nullptr;
774
+ if (track && entry.track_number.value() > 0) {
775
+ track->number = entry.track_number.value();
776
+ track->codec = codecId == "V_AV1" ? AlphaDemuxer::AV1
777
+ : codecId == "V_VP8" ? AlphaDemuxer::VP8
778
+ : AlphaDemuxer::VP9;
779
+ track->entry = entry;
780
+ }
781
+ return Status(Status::kOkCompleted);
782
+ }
783
+
784
+ Status AlphaDemuxer::Impl::OnClusterBegin(const ElementMetadata& metadata, const Cluster& cluster, Action* action) {
785
+ if (firstClusterPosition_ < 0) {
786
+ firstClusterPosition_ = static_cast<int64_t>(metadata.position);
787
+ // No track to read: opening stops here
788
+ if (!colour_.number) return Status(Status::kWouldBlock);
789
+ }
790
+ clusterPosition_ = metadata.position;
791
+ clusterTicks_ = static_cast<int64_t>(cluster.timecode.value());
792
+ *action = Action::kRead;
793
+ return Status(Status::kOkCompleted);
794
+ }
795
+
796
+ void AlphaDemuxer::Impl::beginBlock(uint64_t trackNumber, int16_t timecode) {
797
+ stream_ = trackNumber == colour_.number ? 0 : (alpha_.number && trackNumber == alpha_.number) ? 1 : -1;
798
+ blockTicks_ = clusterTicks_ + timecode;
799
+ frames_.clear();
800
+ framePosition_ = kNoPosition;
801
+ discard_ = mode_ == Mode::kScan || stream_ < 0;
802
+ // Like FFmpeg, blocks of any track end the skip after a seek
803
+ if (mode_ != Mode::kScan && skipBefore_ != kNone) {
804
+ if (blockTicks_ < skipBefore_) {
805
+ discard_ = true;
806
+ } else {
807
+ skipBefore_ = kNone;
808
+ }
809
+ }
810
+ }
811
+
812
+ Status AlphaDemuxer::Impl::OnSimpleBlockBegin(const ElementMetadata&, const SimpleBlock& block, Action* action) {
813
+ beginBlock(block.track_number, block.timecode);
814
+ key_ = block.is_key_frame;
815
+ *action = discard_ ? Action::kSkip : Action::kRead;
816
+ return stream_ == 0 ? noteColourBlock() : Status(Status::kOkCompleted);
817
+ }
818
+
819
+ Status AlphaDemuxer::Impl::OnSimpleBlockEnd(const ElementMetadata&, const SimpleBlock&) {
820
+ if (pausedAtBlockEnd_) {
821
+ pausedAtBlockEnd_ = false;
822
+ return Status(Status::kOkCompleted);
823
+ }
824
+ return finishBlock(nullptr);
825
+ }
826
+
827
+ Status AlphaDemuxer::Impl::OnBlockGroupBegin(const ElementMetadata&, Action* action) {
828
+ inBlockGroup_ = false;
829
+ *action = Action::kRead;
830
+ return Status(Status::kOkCompleted);
831
+ }
832
+
833
+ Status AlphaDemuxer::Impl::OnBlockBegin(const ElementMetadata&, const Block& block, Action* action) {
834
+ beginBlock(block.track_number, block.timecode);
835
+ inBlockGroup_ = true;
836
+ *action = discard_ ? Action::kSkip : Action::kRead;
837
+ return Status(Status::kOkCompleted);
838
+ }
839
+
840
+ Status AlphaDemuxer::Impl::OnBlockGroupEnd(const ElementMetadata&, const BlockGroup& group) {
841
+ if (pausedAtBlockEnd_) {
842
+ pausedAtBlockEnd_ = false;
843
+ return Status(Status::kOkCompleted);
844
+ }
845
+ if (!inBlockGroup_) return Status(Status::kOkCompleted);
846
+ inBlockGroup_ = false;
847
+ key_ = group.references.empty();
848
+ if (stream_ == 0) {
849
+ const Status status = noteColourBlock();
850
+ if (!status.completed_ok()) return status;
851
+ }
852
+ const std::vector<uint8_t>* blockAdditional = nullptr;
853
+ if (group.additions.is_present()) {
854
+ for (const Element<BlockMore>& more : group.additions.value().block_mores) {
855
+ if (more.value().id.value() == 1) {
856
+ blockAdditional = &more.value().data.value();
857
+ break;
858
+ }
859
+ }
860
+ }
861
+ return finishBlock(blockAdditional);
862
+ }
863
+
864
+ Status AlphaDemuxer::Impl::OnFrame(const FrameMetadata& metadata, Reader* reader, std::uint64_t* bytesRemaining) {
865
+ if (discard_) return Skip(reader, bytesRemaining);
866
+ if (metadata.position != framePosition_) {
867
+ if (metadata.size > kMaxFrameSize) return Status(Status::kNotEnoughMemory);
868
+ framePosition_ = metadata.position;
869
+ frames_.emplace_back(static_cast<size_t>(metadata.size));
870
+ }
871
+ std::vector<uint8_t>& frame = frames_.back();
872
+ while (*bytesRemaining > 0) {
873
+ std::uint64_t n = 0;
874
+ const Status status = reader->Read(static_cast<size_t>(*bytesRemaining),
875
+ frame.data() + (frame.size() - *bytesRemaining), &n);
876
+ *bytesRemaining -= n;
877
+ if (!status.ok()) return status;
878
+ }
879
+ return Status(Status::kOkCompleted);
880
+ }
881
+
882
+ Status AlphaDemuxer::Impl::OnCuePoint(const ElementMetadata&, const CuePoint& cuePoint) {
883
+ addCuePoint(cuePoint);
884
+ return Status(Status::kOkCompleted);
885
+ }
886
+
887
+ // Indexes a colour block once its keyframe flag is known; stops a scan past its target.
888
+ Status AlphaDemuxer::Impl::noteColourBlock() {
889
+ if (key_) keyframes_.emplace(blockTicks_, clusterPosition_);
890
+ if (readingFromStart_ && (indexedUpTo_ == kNone || blockTicks_ > indexedUpTo_)) indexedUpTo_ = blockTicks_;
891
+ if (mode_ == Mode::kScan && blockTicks_ > scanUntil_) {
892
+ scanStopped_ = true;
893
+ return Status(Status::kWouldBlock);
894
+ }
895
+ return Status(Status::kOkCompleted);
896
+ }
897
+
898
+ // Turns the block just read into packets and pauses the parser, so that readPacket returns them.
899
+ Status AlphaDemuxer::Impl::finishBlock(const std::vector<uint8_t>* blockAdditional) {
900
+ if (discard_ || frames_.empty()) return Status(Status::kOkCompleted);
901
+ if (stream_ == 0 && colourNeedsKey_) {
902
+ if (!key_) return Status(Status::kOkCompleted);
903
+ colourNeedsKey_ = false;
904
+ }
905
+ if (mode_ == Mode::kProbe) return keepProbePackets(blockAdditional);
906
+
907
+ const int64_t ptsUs = ticksToUs(blockTicks_);
908
+ for (std::vector<uint8_t>& frame : frames_) {
909
+ if (stream_ == 1) {
910
+ queue_.push_back({1, std::move(frame), ptsUs, alphaFrameIndex_++, key_});
911
+ continue;
912
+ }
913
+ queue_.push_back({0, std::move(frame), ptsUs, colourFrameIndex_++, key_});
914
+ if (info_.alphaInBlockAdditional) {
915
+ // Empty when the block has no alpha
916
+ std::vector<uint8_t> alpha = blockAdditional ? *blockAdditional : std::vector<uint8_t>();
917
+ const int size = static_cast<int>(alpha.size());
918
+ const bool key = info_.color.codec == AlphaDemuxer::VP8 ? isVp8Keyframe(alpha.data(), size)
919
+ : isVp9Keyframe(alpha.data(), size);
920
+ queue_.push_back({1, std::move(alpha), ptsUs, alphaFrameIndex_++, key});
921
+ }
922
+ }
923
+ frames_.clear();
924
+ pausedAtBlockEnd_ = true;
925
+ return Status(Status::kWouldBlock);
926
+ }
927
+
928
+ Status AlphaDemuxer::Impl::keepProbePackets(const std::vector<uint8_t>* blockAdditional) {
929
+ if (stream_ == 0) {
930
+ if (!haveColourPacket_) {
931
+ haveColourPacket_ = true;
932
+ firstColourPacket_ = frames_.front();
933
+ firstColourHasBlockAdditional_ = blockAdditional && !blockAdditional->empty();
934
+ startTicks_ = blockTicks_;
935
+ }
936
+ if (colourPtsSamples_.size() < kFrameRateSamples) colourPtsSamples_.push_back(blockTicks_);
937
+ } else if (!haveAlphaPacket_) {
938
+ haveAlphaPacket_ = true;
939
+ firstAlphaPacket_ = frames_.front();
940
+ }
941
+ frames_.clear();
942
+ if (!probeComplete()) return Status(Status::kOkCompleted);
943
+ pausedAtBlockEnd_ = true;
944
+ return Status(Status::kWouldBlock);
945
+ }
946
+
947
+ // --- AlphaDemuxer --------------------------------------------------------------------------
948
+
949
+ AlphaDemuxer::AlphaDemuxer(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {}
950
+ AlphaDemuxer::~AlphaDemuxer() = default;
951
+
952
+ std::unique_ptr<AlphaDemuxer> AlphaDemuxer::open(std::unique_ptr<ByteSource> source) {
953
+ std::unique_ptr<AlphaDemuxer> demuxer = create(std::move(source));
954
+ if (!demuxer || demuxer->tryOpen() != Progress::kDone) return nullptr;
955
+ return demuxer;
956
+ }
957
+
958
+ std::unique_ptr<AlphaDemuxer> AlphaDemuxer::create(std::unique_ptr<ByteSource> source) {
959
+ if (!source) return nullptr;
960
+ return std::unique_ptr<AlphaDemuxer>(new AlphaDemuxer(std::make_unique<Impl>(std::move(source))));
961
+ }
962
+
963
+ std::vector<int> AlphaDemuxer::colorAlphaTracks(std::unique_ptr<ByteSource> source) {
964
+ std::vector<int> tracks;
965
+ const std::unique_ptr<AlphaDemuxer> demuxer = open(std::move(source));
966
+ if (!demuxer) return tracks;
967
+ const Info& info = demuxer->info();
968
+ tracks = {info.color.codec, info.color.profile};
969
+ if (info.hasAlpha) {
970
+ tracks.push_back(info.alpha.codec);
971
+ tracks.push_back(info.alpha.profile);
972
+ }
973
+ return tracks;
974
+ }
975
+
976
+ AlphaDemuxer::Progress AlphaDemuxer::tryOpen() { return impl_->tryOpen(); }
977
+ const AlphaDemuxer::Info& AlphaDemuxer::info() const { return impl_->info(); }
978
+ bool AlphaDemuxer::readPacket(Packet* out) { return impl_->readPacket(out); }
979
+ bool AlphaDemuxer::failed() const { return impl_->failed(); }
980
+ bool AlphaDemuxer::pending() const { return impl_->pending(); }
981
+ void AlphaDemuxer::seekToStart() { impl_->seekToStart(); }
982
+ void AlphaDemuxer::seekToTimeUs(int64_t timeUs) { impl_->seekToTimeUs(timeUs); }
983
+
984
+ } // namespace webm