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,802 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.graphics.SurfaceTexture;
4
+ import android.media.MediaCodec;
5
+ import android.media.MediaCodecInfo;
6
+ import android.media.MediaCodecList;
7
+ import android.media.MediaFormat;
8
+ import android.opengl.GLES11Ext;
9
+ import android.opengl.GLES20;
10
+ import android.os.Build;
11
+ import android.os.Handler;
12
+ import android.os.HandlerThread;
13
+ import android.util.Log;
14
+ import android.view.Surface;
15
+
16
+ import java.io.IOException;
17
+ import java.nio.ByteBuffer;
18
+ import java.util.concurrent.ConcurrentLinkedQueue;
19
+ import java.util.concurrent.CountDownLatch;
20
+ import java.util.concurrent.TimeUnit;
21
+ import java.util.concurrent.atomic.AtomicBoolean;
22
+ import java.util.concurrent.atomic.AtomicInteger;
23
+ import java.util.concurrent.atomic.AtomicLong;
24
+
25
+ /**
26
+ * Hardware-accelerated video decoder (VP8/VP9/AV1) that outputs directly to a SurfaceTexture.
27
+ *
28
+ * This keeps decoded frames on the GPU - no CPU readback required!
29
+ * The SurfaceTexture provides an OES texture that can be used directly in OpenGL.
30
+ *
31
+ * Usage:
32
+ * 1. Create decoder on GL thread (so OES texture is created in right context)
33
+ * 2. Call configure() with video dimensions
34
+ * 3. Queue compressed packets with queueInputPacket()
35
+ * 4. When frame is ready, onFrameAvailable callback is invoked
36
+ * 5. Call updateTexImage() on GL thread to make frame available as OES texture
37
+ * 6. Use getOesTextureId() in your shader
38
+ */
39
+ public class SurfaceTextureVideoDecoder implements SurfaceTexture.OnFrameAvailableListener {
40
+ private static final String TAG = "SurfaceVideoDec";
41
+ public static final String MIME_VP9 = "video/x-vnd.on2.vp9";
42
+ public static final String MIME_AV1 = "video/av01";
43
+ public static final String MIME_VP8 = "video/x-vnd.on2.vp8";
44
+
45
+ // Timeout for codec operations (microseconds)
46
+ private static final long DEQUEUE_TIMEOUT_US = 0; // Non-blocking for async
47
+ private static final long INPUT_TIMEOUT_US = 10000; // 10ms for input
48
+ private static final long FLUSH_TIMEOUT_MS = 500;
49
+
50
+ private final String label;
51
+ private final String mimeType;
52
+ private final String decoderName;
53
+ private MediaCodec codec;
54
+ private MediaFormat format;
55
+ private Surface outputSurface;
56
+ private SurfaceTexture surfaceTexture;
57
+ private int oesTextureId = -1;
58
+
59
+ private int width;
60
+ private int height;
61
+
62
+ // State
63
+ private final AtomicBoolean isConfigured = new AtomicBoolean(false);
64
+ private final AtomicBoolean isStarted = new AtomicBoolean(false);
65
+ private final AtomicBoolean isReleased = new AtomicBoolean(false);
66
+ private final AtomicBoolean hasNewFrame = new AtomicBoolean(false);
67
+
68
+ // Frame tracking - use counter instead of FIFO queue for reliable sync
69
+ private final AtomicInteger nextOutputFrameIndex = new AtomicInteger(0);
70
+ private final AtomicInteger lastRenderedFrameIndex = new AtomicInteger(-1);
71
+ private final AtomicLong lastFrameTimestampNs = new AtomicLong(0);
72
+
73
+ // PTS-gated release
74
+ private final AtomicLong targetPresentationTimeUs = new AtomicLong(0);
75
+ private final AtomicBoolean frameReleasedSinceConsume = new AtomicBoolean(false);
76
+ private volatile long releasedPtsUs = -1;
77
+ // Outputs before this are discarded unshown: after a seek, the frames from the keyframe up to the target
78
+ private final AtomicLong dropBeforeUs = new AtomicLong(Long.MIN_VALUE);
79
+
80
+ private final ConcurrentLinkedQueue<FrameInfo> pendingFrames = new ConcurrentLinkedQueue<>();
81
+
82
+ private FrameAvailableCallback frameCallback;
83
+
84
+ private HandlerThread codecThread;
85
+ private Handler codecHandler;
86
+
87
+ private volatile boolean verbose = false;
88
+
89
+ private static final AtomicInteger activeDecoderCount = new AtomicInteger(0);
90
+
91
+ private final Object pendingFramesLock = new Object();
92
+ // Guarded by pendingFramesLock
93
+ private boolean endOfStreamQueued = false;
94
+
95
+ private static class FrameInfo {
96
+ final int frameIndex;
97
+ final long presentationTimeUs;
98
+
99
+ FrameInfo(int frameIndex, long pts) {
100
+ this.frameIndex = frameIndex;
101
+ this.presentationTimeUs = pts;
102
+ }
103
+ }
104
+
105
+ public interface FrameAvailableCallback {
106
+ void onFrameAvailable(SurfaceTextureVideoDecoder decoder, int frameIndex, long timestampNs);
107
+ void onDecoderError(SurfaceTextureVideoDecoder decoder, String error);
108
+ }
109
+
110
+ /** @param decoderName the MediaCodec decoder to open, e.g. "c2.qti.vp9.decoder" */
111
+ public SurfaceTextureVideoDecoder(String label, String mimeType, String decoderName) {
112
+ this.label = label;
113
+ this.mimeType = mimeType;
114
+ this.decoderName = decoderName;
115
+ }
116
+
117
+ // -------------------------------------------------------------------------
118
+ // Static utility methods
119
+ // -------------------------------------------------------------------------
120
+
121
+ public static int getMaxInstances(String mime) {
122
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
123
+ for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
124
+ if (codecInfo.isEncoder()) continue;
125
+ for (String type : codecInfo.getSupportedTypes()) {
126
+ if (type.equalsIgnoreCase(mime)) {
127
+ try {
128
+ MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
129
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
130
+ return caps.getMaxSupportedInstances();
131
+ }
132
+ } catch (Exception e) {
133
+ Log.e(TAG, "Error getting max instances for " + mime, e);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return -1;
139
+ }
140
+
141
+ /** @deprecated Use getMaxInstances(MIME_VP9) */
142
+ public static int getMaxVp9Instances() {
143
+ return getMaxInstances(MIME_VP9);
144
+ }
145
+
146
+ public static int getActiveDecoderCount() {
147
+ return activeDecoderCount.get();
148
+ }
149
+
150
+ public static boolean isHardwareDecoderAvailable(String mime) {
151
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
152
+ for (MediaCodecInfo info : codecList.getCodecInfos()) {
153
+ if (info.isEncoder()) continue;
154
+
155
+ for (String type : info.getSupportedTypes()) {
156
+ if (mime.equalsIgnoreCase(type)) {
157
+ String name = info.getName();
158
+ String lowerName = name.toLowerCase();
159
+
160
+ if (lowerName.contains(".secure") || lowerName.contains("_secure")) {
161
+ continue;
162
+ }
163
+
164
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
165
+ if (info.isHardwareAccelerated()) {
166
+ return true;
167
+ }
168
+ } else {
169
+ if (!lowerName.contains("sw") && !lowerName.contains("google")) {
170
+ return true;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ /** @deprecated Use isHardwareDecoderAvailable(MIME_VP9) */
180
+ public static boolean isVp9HardwareDecoderAvailable() {
181
+ return isHardwareDecoderAvailable(MIME_VP9);
182
+ }
183
+
184
+ public static boolean isAv1HardwareDecoderAvailable() {
185
+ return isHardwareDecoderAvailable(MIME_AV1);
186
+ }
187
+
188
+ public static String getDecoderInfo() {
189
+ StringBuilder info = new StringBuilder();
190
+ info.append("Video Decoder Information\n");
191
+ info.append("========================\n");
192
+
193
+ String[] mimes = { MIME_VP8, MIME_VP9, MIME_AV1 };
194
+ String[] labels = { "VP8", "VP9", "AV1" };
195
+
196
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
197
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
198
+
199
+ for (int m = 0; m < mimes.length; m++) {
200
+ String mime = mimes[m];
201
+ String codecLabel = labels[m];
202
+ int decoderCount = 0;
203
+
204
+ info.append("\n--- ").append(codecLabel).append(" (").append(mime).append(") ---\n");
205
+
206
+ for (MediaCodecInfo codecInfo : codecInfos) {
207
+ if (codecInfo.isEncoder()) continue;
208
+
209
+ for (String type : codecInfo.getSupportedTypes()) {
210
+ if (type.equalsIgnoreCase(mime)) {
211
+ decoderCount++;
212
+ info.append("\nDecoder: ").append(codecInfo.getName()).append("\n");
213
+
214
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
215
+ info.append(" Hardware accelerated: ").append(codecInfo.isHardwareAccelerated()).append("\n");
216
+ info.append(" Software only: ").append(codecInfo.isSoftwareOnly()).append("\n");
217
+ info.append(" Vendor: ").append(codecInfo.isVendor()).append("\n");
218
+ }
219
+
220
+ try {
221
+ MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
222
+
223
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
224
+ int maxInstances = caps.getMaxSupportedInstances();
225
+ info.append(" Max concurrent instances: ").append(maxInstances).append("\n");
226
+ info.append(" --> Max dual-stream players: ").append(maxInstances / 2).append("\n");
227
+ }
228
+
229
+ int[] colorFormats = caps.colorFormats;
230
+ info.append(" Color formats: ");
231
+ for (int i = 0; i < Math.min(colorFormats.length, 5); i++) {
232
+ info.append(colorFormats[i]);
233
+ if (i < colorFormats.length - 1 && i < 4) info.append(", ");
234
+ }
235
+ if (colorFormats.length > 5) info.append("...");
236
+ info.append("\n");
237
+
238
+ MediaCodecInfo.VideoCapabilities videoCaps = caps.getVideoCapabilities();
239
+ if (videoCaps != null) {
240
+ info.append(" Width range: ").append(videoCaps.getSupportedWidths()).append("\n");
241
+ info.append(" Height range: ").append(videoCaps.getSupportedHeights()).append("\n");
242
+ }
243
+ } catch (Exception e) {
244
+ info.append(" Error getting capabilities: ").append(e.getMessage()).append("\n");
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ info.append("\nTotal ").append(codecLabel).append(" decoders found: ").append(decoderCount).append("\n");
251
+ }
252
+
253
+ info.append("\nCurrently active decoders: ").append(activeDecoderCount.get()).append("\n");
254
+
255
+ return info.toString();
256
+ }
257
+
258
+ /** @deprecated Use getDecoderInfo() */
259
+ public static String getVp9DecoderInfo() {
260
+ return getDecoderInfo();
261
+ }
262
+
263
+ // -------------------------------------------------------------------------
264
+ // Instance methods
265
+ // -------------------------------------------------------------------------
266
+
267
+ /** @param avColorRange the stream's colour range (numbered as FFmpeg's AVColorRange); with avColorSpace, sets MediaCodec's colour keys */
268
+ public boolean configure(int width, int height, int avColorRange, int avColorSpace) {
269
+ if (isConfigured.get()) {
270
+ Log.w(TAG, "[" + label + "] Already configured");
271
+ return true;
272
+ }
273
+
274
+ if (isReleased.get()) {
275
+ Log.e(TAG, "[" + label + "] Cannot configure - decoder released");
276
+ return false;
277
+ }
278
+
279
+ this.width = width;
280
+ this.height = height;
281
+
282
+ try {
283
+ int[] textures = new int[1];
284
+ GLES20.glGenTextures(1, textures, 0);
285
+ oesTextureId = textures[0];
286
+
287
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);
288
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
289
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
290
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
291
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
292
+
293
+ logV("[" + label + "] Created OES texture: " + oesTextureId);
294
+
295
+ surfaceTexture = new SurfaceTexture(oesTextureId);
296
+ surfaceTexture.setDefaultBufferSize(width, height);
297
+ surfaceTexture.setOnFrameAvailableListener(this);
298
+
299
+ outputSurface = new Surface(surfaceTexture);
300
+
301
+ logV("[" + label + "] Using decoder: " + decoderName + " for " + width + "x" + height + " (" + mimeType + ")");
302
+ codec = MediaCodec.createByCodecName(decoderName);
303
+ activeDecoderCount.incrementAndGet();
304
+
305
+ format = MediaFormat.createVideoFormat(mimeType, width, height);
306
+ MediaCodecColor.apply(format, avColorRange, avColorSpace, width, height);
307
+ // Software decoders otherwise output 10-bit clips as P010, which draws blank on Adreno
308
+ // (c2.android.av1-dav1d on the A52s). Asked for flexible YUV420, AOSP's software
309
+ // decoders convert to 8-bit; 8-bit clips are unaffected.
310
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codec.getCodecInfo().isSoftwareOnly()) {
311
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
312
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
313
+ }
314
+ codec.configure(format, outputSurface, null, 0);
315
+
316
+ codecThread = new HandlerThread("Codec-" + label);
317
+ codecThread.start();
318
+ codecHandler = new Handler(codecThread.getLooper());
319
+
320
+ isConfigured.set(true);
321
+ logV("[" + label + "] Configured with Surface output (GPU-accelerated)");
322
+ return true;
323
+
324
+ } catch (IOException e) {
325
+ Log.e(TAG, "[" + label + "] Failed to create codec: " + e.getMessage());
326
+ cleanup();
327
+ return false;
328
+ } catch (Exception e) {
329
+ Log.e(TAG, "[" + label + "] Configuration error: " + e.getMessage());
330
+ cleanup();
331
+ return false;
332
+ }
333
+ }
334
+
335
+ public boolean start() {
336
+ if (!isConfigured.get()) {
337
+ Log.e(TAG, "[" + label + "] Cannot start - not configured");
338
+ return false;
339
+ }
340
+
341
+ if (isStarted.get()) {
342
+ Log.w(TAG, "[" + label + "] Already started");
343
+ return true;
344
+ }
345
+
346
+ try {
347
+ codec.start();
348
+ isStarted.set(true);
349
+ codecHandler.post(this::pollOutput);
350
+ logV("[" + label + "] Started");
351
+ return true;
352
+ } catch (Exception e) {
353
+ Log.e(TAG, "[" + label + "] Failed to start: " + e.getMessage());
354
+ return false;
355
+ }
356
+ }
357
+
358
+ public boolean queueInputPacket(byte[] data, int offset, int size, long presentationTimeUs, int frameIndex) {
359
+ synchronized (pendingFramesLock) {
360
+ if (!isStarted.get() || isReleased.get()) {
361
+ return false;
362
+ }
363
+
364
+ try {
365
+ int inputIndex = codec.dequeueInputBuffer(INPUT_TIMEOUT_US);
366
+ if (inputIndex < 0) {
367
+ return false;
368
+ }
369
+
370
+ ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
371
+ if (inputBuffer == null) {
372
+ return false;
373
+ }
374
+
375
+ inputBuffer.clear();
376
+ inputBuffer.put(data, offset, size);
377
+
378
+ pendingFrames.offer(new FrameInfo(frameIndex, presentationTimeUs));
379
+ codec.queueInputBuffer(inputIndex, 0, size, presentationTimeUs, 0);
380
+ return true;
381
+
382
+ } catch (Exception e) {
383
+ Log.e(TAG, "[" + label + "] Queue error: " + e.getMessage());
384
+ return false;
385
+ }
386
+ }
387
+ }
388
+
389
+ public boolean queueInputPacket(ByteBuffer data, long presentationTimeUs, int frameIndex) {
390
+ synchronized (pendingFramesLock) {
391
+ if (!isStarted.get() || isReleased.get()) {
392
+ return false;
393
+ }
394
+
395
+ try {
396
+ int inputIndex = codec.dequeueInputBuffer(INPUT_TIMEOUT_US);
397
+ if (inputIndex < 0) {
398
+ return false;
399
+ }
400
+
401
+ ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
402
+ if (inputBuffer == null) {
403
+ return false;
404
+ }
405
+
406
+ inputBuffer.clear();
407
+ data.rewind();
408
+ int size = data.remaining();
409
+ inputBuffer.put(data);
410
+ data.rewind();
411
+
412
+ logV("[" + label + "] Queued frame " + frameIndex + ", size=" + size + ", pts=" + presentationTimeUs);
413
+
414
+ pendingFrames.offer(new FrameInfo(frameIndex, presentationTimeUs));
415
+ codec.queueInputBuffer(inputIndex, 0, size, presentationTimeUs, 0);
416
+ return true;
417
+
418
+ } catch (Exception e) {
419
+ Log.e(TAG, "[" + label + "] Queue error: " + e.getMessage());
420
+ return false;
421
+ }
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Tells the decoder no more input follows, so it outputs the frames it still holds back (AV1
427
+ * decoders keep some until then). Until the next flush, no packets are accepted.
428
+ *
429
+ * @return false if the decoder had no free input buffer; try again
430
+ */
431
+ public boolean queueEndOfStream() {
432
+ synchronized (pendingFramesLock) {
433
+ if (!isStarted.get() || isReleased.get() || endOfStreamQueued) return true;
434
+ try {
435
+ int inputIndex = codec.dequeueInputBuffer(INPUT_TIMEOUT_US);
436
+ if (inputIndex < 0) return false;
437
+ codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
438
+ endOfStreamQueued = true;
439
+ logV("[" + label + "] Queued end of stream");
440
+ } catch (Exception e) {
441
+ Log.e(TAG, "[" + label + "] End-of-stream error: " + e.getMessage());
442
+ }
443
+ return true;
444
+ }
445
+ }
446
+
447
+ private void pollOutput() {
448
+ if (isReleased.get() || !isStarted.get()) {
449
+ return;
450
+ }
451
+
452
+ try {
453
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
454
+
455
+ while (!isReleased.get() && isStarted.get()) {
456
+ if (frameReleasedSinceConsume.get()) {
457
+ break;
458
+ }
459
+
460
+ int outputIndex = codec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US);
461
+
462
+ if (outputIndex >= 0) {
463
+ long pts = bufferInfo.presentationTimeUs;
464
+ long target = targetPresentationTimeUs.get();
465
+
466
+ boolean endOfStream = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
467
+ if (endOfStream && bufferInfo.size == 0) {
468
+ codec.releaseOutputBuffer(outputIndex, false);
469
+ continue;
470
+ }
471
+
472
+ if (pts < dropBeforeUs.get()) {
473
+ codec.releaseOutputBuffer(outputIndex, false);
474
+ pendingFrames.poll();
475
+ continue;
476
+ }
477
+ if (pts <= target) {
478
+ releasedPtsUs = pts;
479
+ codec.releaseOutputBuffer(outputIndex, true);
480
+ frameReleasedSinceConsume.set(true);
481
+
482
+ logV("[" + label + "] Released frame, pts=" + pts +
483
+ ", target=" + target + ", flags=" + bufferInfo.flags);
484
+ break;
485
+ } else {
486
+ heldOutputIndex = outputIndex;
487
+ heldOutputPts = pts;
488
+ heldOutputFlags = bufferInfo.flags;
489
+ break;
490
+ }
491
+
492
+ } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
493
+ MediaFormat newFormat = codec.getOutputFormat();
494
+ logV("[" + label + "] Output format changed: " + newFormat);
495
+
496
+ } else if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
497
+ break;
498
+ }
499
+ }
500
+ } catch (IllegalStateException e) {
501
+ if (!isReleased.get()) {
502
+ Log.w(TAG, "[" + label + "] Poll exception: " + e.getMessage());
503
+ }
504
+ } catch (Exception e) {
505
+ Log.e(TAG, "[" + label + "] Poll error: " + e.getMessage());
506
+ if (frameCallback != null) {
507
+ frameCallback.onDecoderError(this, e.getMessage());
508
+ }
509
+ }
510
+
511
+ if (!isReleased.get() && isStarted.get()) {
512
+ if (heldOutputIndex >= 0) {
513
+ codecHandler.postDelayed(this::checkHeldBuffer, 1);
514
+ } else {
515
+ codecHandler.postDelayed(this::pollOutput, 1);
516
+ }
517
+ }
518
+ }
519
+
520
+ private int heldOutputIndex = -1;
521
+ private long heldOutputPts = 0;
522
+ private int heldOutputFlags = 0;
523
+
524
+ private void checkHeldBuffer() {
525
+ if (isReleased.get() || !isStarted.get()) {
526
+ if (heldOutputIndex >= 0) {
527
+ try { codec.releaseOutputBuffer(heldOutputIndex, false); } catch (Exception ignored) {}
528
+ heldOutputIndex = -1;
529
+ }
530
+ return;
531
+ }
532
+
533
+ if (heldOutputIndex >= 0) {
534
+ if (frameReleasedSinceConsume.get()) {
535
+ if (!isReleased.get() && isStarted.get()) {
536
+ codecHandler.postDelayed(this::checkHeldBuffer, 1);
537
+ }
538
+ return;
539
+ }
540
+
541
+ long target = targetPresentationTimeUs.get();
542
+ if (heldOutputPts <= target) {
543
+ try {
544
+ releasedPtsUs = heldOutputPts;
545
+ codec.releaseOutputBuffer(heldOutputIndex, true);
546
+ frameReleasedSinceConsume.set(true);
547
+
548
+ logV("[" + label + "] Released held frame, pts=" + heldOutputPts +
549
+ ", target=" + target);
550
+ } catch (Exception e) {
551
+ Log.w(TAG, "[" + label + "] Error releasing held buffer: " + e.getMessage());
552
+ }
553
+ heldOutputIndex = -1;
554
+
555
+ if (!isReleased.get() && isStarted.get()) {
556
+ codecHandler.postDelayed(this::pollOutput, 1);
557
+ }
558
+ } else {
559
+ if (!isReleased.get() && isStarted.get()) {
560
+ codecHandler.postDelayed(this::checkHeldBuffer, 1);
561
+ }
562
+ }
563
+ } else {
564
+ if (!isReleased.get() && isStarted.get()) {
565
+ codecHandler.post(this::pollOutput);
566
+ }
567
+ }
568
+ }
569
+
570
+ @Override
571
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
572
+ if (isReleased.get()) return;
573
+
574
+ hasNewFrame.set(true);
575
+
576
+ FrameInfo frameInfo = pendingFrames.poll();
577
+ int frameIndex;
578
+ if (frameInfo != null) {
579
+ frameIndex = frameInfo.frameIndex;
580
+ } else {
581
+ frameIndex = nextOutputFrameIndex.getAndIncrement();
582
+ Log.w(TAG, "[" + label + "] pendingFrames empty! Using counter fallback: " + frameIndex);
583
+ }
584
+
585
+ long timestampNs = surfaceTexture.getTimestamp();
586
+
587
+ logV("[" + label + "] onFrameAvailable: frameIndex=" + frameIndex + ", ts=" + timestampNs);
588
+
589
+ lastFrameTimestampNs.set(timestampNs);
590
+ lastRenderedFrameIndex.set(frameIndex);
591
+
592
+ if (frameCallback != null) {
593
+ frameCallback.onFrameAvailable(this, frameIndex, timestampNs);
594
+ }
595
+ }
596
+
597
+ public void updateTexImage() {
598
+ if (surfaceTexture != null) {
599
+ boolean wasNew = hasNewFrame.getAndSet(false);
600
+ try {
601
+ surfaceTexture.updateTexImage();
602
+ if (!wasNew) {
603
+ Log.w(TAG, "[" + label + "] updateTexImage called with hasNewFrame=false (timing issue?)");
604
+ }
605
+ } catch (Exception e) {
606
+ Log.w(TAG, "[" + label + "] updateTexImage error: " + e.getMessage());
607
+ }
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Latches the newest frame on the SurfaceTexture; GL thread.
613
+ *
614
+ * @return whether that is the frame last released, which may not have arrived yet
615
+ */
616
+ public boolean latchReleasedFrame() {
617
+ if (surfaceTexture == null) return false;
618
+ hasNewFrame.set(false);
619
+ try {
620
+ surfaceTexture.updateTexImage();
621
+ } catch (Exception e) {
622
+ Log.w(TAG, "[" + label + "] updateTexImage error: " + e.getMessage());
623
+ return false;
624
+ }
625
+ return surfaceTexture.getTimestamp() == releasedPtsUs * 1000;
626
+ }
627
+
628
+ public boolean hasNewFrame() {
629
+ return hasNewFrame.get();
630
+ }
631
+
632
+ public int getOesTextureId() {
633
+ return oesTextureId;
634
+ }
635
+
636
+ public void getTransformMatrix(float[] matrix) {
637
+ if (surfaceTexture != null) {
638
+ surfaceTexture.getTransformMatrix(matrix);
639
+ }
640
+ }
641
+
642
+ public long getLastFrameTimestampNs() {
643
+ return lastFrameTimestampNs.get();
644
+ }
645
+
646
+ /** The presentation time of the frame the last updateTexImage() latched; GL thread. */
647
+ public long getLatchedTimestampNs() {
648
+ return surfaceTexture != null ? surfaceTexture.getTimestamp() : -1;
649
+ }
650
+
651
+ public int getLastRenderedFrameIndex() {
652
+ return lastRenderedFrameIndex.get();
653
+ }
654
+
655
+ public void setFrameCallback(FrameAvailableCallback callback) {
656
+ this.frameCallback = callback;
657
+ }
658
+
659
+ public void setVerbose(boolean verbose) {
660
+ this.verbose = verbose;
661
+ }
662
+
663
+ /** Lifecycle and per-frame chatter, only with verbose. Warnings and errors always log. */
664
+ private void logV(String message) {
665
+ if (verbose) Log.i(TAG, message);
666
+ }
667
+
668
+ public void setTargetPresentationTimeUs(long ptsUs) {
669
+ targetPresentationTimeUs.set(ptsUs);
670
+ }
671
+
672
+ /** Discards decoded frames earlier than ptsUs without showing them, until the next flush. */
673
+ public void setDropBeforeUs(long ptsUs) {
674
+ dropBeforeUs.set(ptsUs);
675
+ }
676
+
677
+ public boolean wasFrameReleased() {
678
+ return frameReleasedSinceConsume.get();
679
+ }
680
+
681
+ public void clearFrameReleased() {
682
+ frameReleasedSinceConsume.set(false);
683
+ }
684
+
685
+ /**
686
+ * Drops every queued and decoded frame. Runs on the codec thread, between output polls: a poll
687
+ * holding an output buffer across the flush would afterwards wait on a buffer the codec has
688
+ * taken back, and show nothing until the playhead reached that buffer's time again.
689
+ */
690
+ public void flush() {
691
+ Handler handler = codecHandler;
692
+ if (isReleased.get() || handler == null) {
693
+ Log.w(TAG, "[" + label + "] flush() called but already released");
694
+ return;
695
+ }
696
+
697
+ logV("[" + label + "] Flushing decoder...");
698
+ CountDownLatch flushed = new CountDownLatch(1);
699
+ handler.post(() -> {
700
+ try {
701
+ flushOnCodecThread(handler);
702
+ } finally {
703
+ flushed.countDown();
704
+ }
705
+ });
706
+ try {
707
+ if (!flushed.await(FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
708
+ Log.w(TAG, "[" + label + "] Flush still running after " + FLUSH_TIMEOUT_MS + " ms");
709
+ }
710
+ } catch (InterruptedException e) {
711
+ Thread.currentThread().interrupt();
712
+ }
713
+ }
714
+
715
+ private void flushOnCodecThread(Handler handler) {
716
+ if (isReleased.get()) return;
717
+ // The poll restarts below, after the flush
718
+ handler.removeCallbacksAndMessages(null);
719
+ synchronized (pendingFramesLock) {
720
+ pendingFrames.clear();
721
+ nextOutputFrameIndex.set(0);
722
+ lastRenderedFrameIndex.set(-1);
723
+ hasNewFrame.set(false);
724
+ lastFrameTimestampNs.set(0);
725
+ targetPresentationTimeUs.set(0);
726
+ dropBeforeUs.set(Long.MIN_VALUE);
727
+ frameReleasedSinceConsume.set(false);
728
+ heldOutputIndex = -1;
729
+ endOfStreamQueued = false;
730
+ try {
731
+ codec.flush();
732
+ logV("[" + label + "] Flushed and restarted successfully");
733
+ } catch (Exception e) {
734
+ Log.e(TAG, "[" + label + "] Flush error: " + e.getMessage());
735
+ }
736
+ }
737
+ handler.post(this::pollOutput);
738
+ }
739
+
740
+ public void release() {
741
+ if (isReleased.getAndSet(true)) {
742
+ return;
743
+ }
744
+
745
+ logV("[" + label + "] Releasing");
746
+
747
+ isStarted.set(false);
748
+ isConfigured.set(false);
749
+
750
+ cleanup();
751
+
752
+ logV("[" + label + "] Released");
753
+ }
754
+
755
+ private void cleanup() {
756
+ if (codecThread != null) {
757
+ codecThread.quitSafely();
758
+ codecThread = null;
759
+ codecHandler = null;
760
+ }
761
+
762
+ if (codec != null) {
763
+ try {
764
+ codec.stop();
765
+ codec.release();
766
+ activeDecoderCount.decrementAndGet();
767
+ } catch (Exception e) {
768
+ Log.w(TAG, "[" + label + "] Codec cleanup error: " + e.getMessage());
769
+ }
770
+ codec = null;
771
+ }
772
+
773
+ if (outputSurface != null) {
774
+ outputSurface.release();
775
+ outputSurface = null;
776
+ }
777
+
778
+ if (surfaceTexture != null) {
779
+ surfaceTexture.setOnFrameAvailableListener(null);
780
+ surfaceTexture.release();
781
+ surfaceTexture = null;
782
+ }
783
+
784
+ if (oesTextureId > 0) {
785
+ int[] textures = {oesTextureId};
786
+ GLES20.glDeleteTextures(1, textures, 0);
787
+ oesTextureId = -1;
788
+ }
789
+
790
+ pendingFrames.clear();
791
+ frameCallback = null;
792
+ }
793
+
794
+ public int getWidth() { return width; }
795
+ public int getHeight() { return height; }
796
+ public String getLabel() { return label; }
797
+ public String getMimeType() { return mimeType; }
798
+ public boolean isConfigured() { return isConfigured.get(); }
799
+ public boolean isStarted() { return isStarted.get(); }
800
+ public boolean isReleased() { return isReleased.get(); }
801
+ public String getDecoderName() { return decoderName; }
802
+ }