找回密码
 立即注册
首页 业界区 安全 MediaCodec使用之MP4解码(三)

MediaCodec使用之MP4解码(三)

嫂潍 2025-8-17 16:44:44
MediaCodec使用之视频解码(三)

准备三个视频,一个是普通的h264视频,一个是8k h265的,一个是电影的mkv
使用fflay播放并查看详细信息。
普通视频
  1. Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '.\video1.mp4':  0B
  2.   Metadata:
  3.     major_brand     : isom
  4.     minor_version   : 512
  5.     compatible_brands: isomiso2avc1mp41
  6.     encoder         : Lavf58.29.100
  7.   Duration: 00:02:29.94, start: 0.000000, bitrate: 641 kb/s
  8.   Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 662x1280 [SAR 1:1 DAR 331:640], 499 kb/s, 59.96 fps, 60 tbr, 15360 tbn (default)
  9.       Metadata:
  10.         handler_name    : VideoHandler
  11.         vendor_id       : [0][0][0][0]
  12.   Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
  13.       Metadata:
  14.         handler_name    : SoundHandler
  15.         vendor_id       : [0][0][0][0]
  16.    5.30 A-V: -0.031 fd=  24 aq=   16KB vq=   55KB sq=    0B
复制代码
h265的
  1. Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '.\video2.mp4':  0B
  2.   Metadata:
  3.     major_brand     : mp42
  4.     minor_version   : 0
  5.     compatible_brands: isommp42
  6.     creation_time   : 2025-08-17T03:21:22.000000Z
  7.     location        : +40.0770+116.4052/
  8.     location-eng    : +40.0770+116.4052/
  9.     com.android.version: 15
  10.   Duration: 00:00:15.90, start: 0.000000, bitrate: 81199 kb/s
  11.   Stream #0:0[0x1](eng): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, bt709), 7680x4320, 80753 kb/s, 30 fps, 30 tbr, 90k tbn (default)
  12.       Metadata:
  13.         creation_time   : 2025-08-17T03:21:22.000000Z
  14.         handler_name    : VideoHandle
  15.         vendor_id       : [0][0][0][0]
  16.   Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 256 kb/s (default)
  17.       Metadata:
  18.         creation_time   : 2025-08-17T03:21:22.000000Z
  19.         handler_name    : SoundHandle
  20.         vendor_id       : [0][0][0][0]
复制代码
电影
  1. ffplay -x 1280 -y 720 -ss 00:50:00  video3.mkv
  2. Input #0, matroska,webm, from '.\video3.mkv':
  3.   Metadata:
  4.     title           : Black.Widow.2021.2160p.BluRay.REMUX.HEVC.DTS-HD.MA.7.1.TrueHD.7.1.Atmos.DTS-HD.MA.7.1.zh-老K
  5.     encoder         : libebml v1.4.2 + libmatroska v1.6.4
  6.     creation_time   : 2024-04-06T01:57:58.000000Z
  7.   Duration: 02:13:46.79, start: 0.000000, bitrate: 57664 kb/s
  8.   Chapters:
  9.     Chapter #0:0: start 0.000000, end 295.169867
  10.       Metadata:
  11.         title           : 第 01 章
  12.     Chapter #0:1: start 295.169867, end 790.080956
  13.       Metadata:
  14.         title           : 第 02 章
  15.         ...
  16. Stream #0:0: Video: hevc (Main 10), yuv420p10le(tv, bt2020nc/bt2020/smpte2084), 3840x2160 [SAR 1:1 DAR 16:9], 23.98 fps, 23.98 tbr, 1k tbn (default)
  17.       Metadata:
  18.         BPS             : 46197111
  19.         DURATION        : 02:13:46.769000000
  20.         NUMBER_OF_FRAMES: 192450
  21.         NUMBER_OF_BYTES : 46351692620
  22.         SOURCE_ID       : 001011
  23.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  24.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  25.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
  26.   Stream #0:1(eng): Audio: truehd (Dolby TrueHD + Dolby Atmos), 48000 Hz, 7.1, s32 (24 bit) (default)
  27.       Metadata:
  28.         title           : TrueHD Atmos 7.1
  29.         BPS             : 4284386
  30.         DURATION        : 02:13:46.769000000
  31.         NUMBER_OF_FRAMES: 9632122
  32.         NUMBER_OF_BYTES : 4298722162
  33.         SOURCE_ID       : 001100
  34.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  35.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  36.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
  37.   Stream #0:2(eng): Audio: dts (dca) (DTS-HD MA), 48000 Hz, 7.1, s32p (24 bit)
  38.       Metadata:
  39.         title           : DTS-HD MA 7.1
  40.         BPS             : 4411433
  41.         DURATION        : 02:13:46.794000000
  42.         NUMBER_OF_FRAMES: 752481
  43.         NUMBER_OF_BYTES : 4426208460
  44.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  45.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  46.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
  47.   Stream #0:3(chi): Audio: dts (dca) (DTS-HD MA), 48000 Hz, 7.1, s16p
  48.       Metadata:
  49.         title           : 国语 DTS-HD MA 7.1
  50.         BPS             : 2647732
  51.         DURATION        : 02:13:46.773000000
  52.         NUMBER_OF_FRAMES: 752510
  53.         NUMBER_OF_BYTES : 2656593104
  54.         SOURCE_ID       : 001105
  55.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  56.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  57.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
  58.   Stream #0:4(chi): Subtitle: ass (ssa) (default)
  59.       Metadata:
  60.         title           : 简英
  61.         BPS             : 199
  62.         DURATION        : 02:12:27.160000000
  63.         NUMBER_OF_FRAMES: 1376
  64.         NUMBER_OF_BYTES : 198336
  65.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  66.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  67.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
  68.   Stream #0:5(chi): Subtitle: hdmv_pgs_subtitle (pgssub)
  69.       Metadata:
  70.         title           : 国语 简体
  71.         BPS             : 93951
  72.         DURATION        : 02:13:00.556000000
  73.         NUMBER_OF_FRAMES: 6703
  74.         NUMBER_OF_BYTES : 93723446
  75.         SOURCE_ID       : 0012a4
  76.         _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
  77.         _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
  78.         _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
  79.   Stream #0:6: Video: mjpeg (Progressive), yuvj420p(pc, bt470bg/unknown/unknown), 254x254 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn (attached pic)
  80.       Metadata:
  81.         filename        : 老K  QQ 195383233.jpg
  82.         mimetype        : image/jpeg
复制代码
解码普通视频
  1. internal suspend fun videoToYuvPcm(context: Context, videoUri: Uri, yuvUri: Uri, pcmUri: Uri): Unit {
  2.         val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
  3.         // 视频解码器材
  4.         val h264Decoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
  5.                 .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_AVC in it.supportedTypes }
  6.         if (h264Decoders.isEmpty()){
  7.                 Log.i(TAG, "videoToYuvPcm -> 不支持h264解码")
  8.                 return
  9.         }
  10.         // 拿解码器
  11.         val h264Decoder: MediaCodecInfo = h264Decoders.firstOrNull {
  12.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  13.                         it.isHardwareAccelerated
  14.                 } else {
  15.                         true
  16.                 }
  17.         } ?: h264Decoders.first()
  18.         // MediaCodec.createByCodecName(h264Decoder.name)
  19.         h264Decoders.forEach { mediaCodecInfo ->
  20.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  21.                         Log.i(TAG, "h264Decoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  22.                 } else {
  23.                         Log.i(TAG, "h264Decoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  24.                 }
  25.         }
  26.         // 音频解码器
  27.         val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
  28.                 it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
  29.         }
  30.         if (aacDecoders.isEmpty()){
  31.                 Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
  32.                 return
  33.         }
  34.         val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
  35.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  36.                         it.isHardwareAccelerated
  37.                 } else {
  38.                         true
  39.                 }
  40.         } ?: aacDecoders.first()
  41.         aacDecoders.forEach { mediaCodecInfo ->
  42.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  43.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  44.                 } else {
  45.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  46.                 }
  47.         }
  48.         val mediaExtractor = MediaExtractor()
  49.         mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())
  50.         for (i in 0 until mediaExtractor.trackCount){
  51.                 Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
  52.         }
  53.         if (mediaExtractor.trackCount < 2){
  54.                 mediaExtractor.release()
  55.                 return
  56.         }
  57.         Log.i(TAG, "videoToYuvPcm -> decode before...")
  58.         //
  59.         decode(context, 0, yuvUri, mediaExtractor, h264Decoder.name)
  60.         // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
  61.         decode(context, 1, pcmUri, mediaExtractor, aacDecoder.name)
  62.         Log.i(TAG, "videoToYuvPcm -> decode after...")
  63.         mediaExtractor.release()
  64.         Log.i(TAG, "videoToYuvPcm -> end...")
  65. }
  66. private suspend fun decode(
  67.         context: Context,
  68.         index: Int,
  69.         output: Uri,
  70.         mediaExtractor: MediaExtractor,
  71.         decodeName: String
  72. ): Unit = suspendCancellableCoroutine { continuation ->
  73.         val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
  74.         mediaExtractor.selectTrack(index)
  75.         val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
  76.         mediaCodec.configure(mediaFormat, null, null, 0)
  77.         val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
  78.         val bytes = ByteArray(1024 * 1024 * 2)
  79.         mediaCodec.setCallback(object : MediaCodec.Callback() {
  80.                 override fun onError(
  81.                         codec: MediaCodec,
  82.                         e: MediaCodec.CodecException
  83.                 ) {
  84.                         Log.e(
  85.                                 TAG,
  86.                                 "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
  87.                                 e
  88.                         )
  89.                 }
  90.                 override fun onInputBufferAvailable(
  91.                         codec: MediaCodec,
  92.                         index: Int
  93.                 ) {
  94.                         val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
  95.                         val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
  96.                         Log.i(
  97.                                 TAG,
  98.                                 "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
  99.                         )
  100.                         if (size > 0) {
  101.                                 codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
  102.                                 mediaExtractor.advance()
  103.                         } else {
  104.                                 codec.queueInputBuffer(
  105.                                         index,
  106.                                         0,
  107.                                         0,
  108.                                         0,
  109.                                         MediaCodec.BUFFER_FLAG_END_OF_STREAM
  110.                                 )
  111.                         }
  112.                 }
  113.                 override fun onOutputBufferAvailable(
  114.                         codec: MediaCodec,
  115.                         index: Int,
  116.                         info: MediaCodec.BufferInfo
  117.                 ) {
  118.                         Log.i(
  119.                                 TAG,
  120.                                 "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
  121.                         )
  122.                         val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return
  123.                         outputBuffer.get(bytes, 0, info.size)
  124.                         aacOutputStream.write(bytes, 0, info.size)
  125.                         codec.releaseOutputBuffer(index, false)
  126.                         if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
  127.                                 Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
  128.                                 aacOutputStream.close()
  129.                                 if (continuation.isActive) {
  130.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
  131.                                         continuation.resume(Unit)
  132.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
  133.                                 }
  134.                         }
  135.                 }
  136.                 override fun onOutputFormatChanged(
  137.                         codec: MediaCodec,
  138.                         format: MediaFormat
  139.                 ) {
  140.                         Log.i(
  141.                                 TAG,
  142.                                 "onOutputFormatChanged -> name: ${codec.name}, format: $format"
  143.                         )
  144.                 }
  145.         })
  146.         Log.i(TAG, "pcmToAac -> before start...")
  147.         mediaCodec.start()
  148.         Log.i(TAG, "pcmToAac -> after start...")
  149. }
复制代码
播放生成的音视频
  1. ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
  2. ffplay -f rawvideo -pixel_format yuv420p -video_size 672x1280 -framerate 60 output.yuv
复制代码
8k h265

记得视频搞短一点,有些解码器会吞掉EOS标识,不清楚为什么
  1. private suspend fun decode(
  2.         context: Context,
  3.         index: Int,
  4.         output: Uri,
  5.         mediaExtractor: MediaExtractor,
  6.         decodeName: String
  7. ): Unit = suspendCancellableCoroutine { continuation ->
  8.         val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
  9.         mediaExtractor.selectTrack(index)
  10.         val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
  11.         mediaCodec.configure(mediaFormat, null, null, 0)
  12.         val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
  13.         val bytes = ByteArray(1024 * 1024 * 100)
  14.         mediaCodec.setCallback(object : MediaCodec.Callback() {
  15.                 override fun onError(
  16.                         codec: MediaCodec,
  17.                         e: MediaCodec.CodecException
  18.                 ) {
  19.                         Log.e(
  20.                                 TAG,
  21.                                 "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
  22.                                 e
  23.                         )
  24.                 }
  25.                 override fun onInputBufferAvailable(
  26.                         codec: MediaCodec,
  27.                         index: Int
  28.                 ) {
  29.                         val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
  30.                         val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
  31.                         Log.i(
  32.                                 TAG,
  33.                                 "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
  34.                         )
  35.                         if (size > 0) {
  36.                                 codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
  37.                                 mediaExtractor.advance()
  38.                         } else {
  39.                                 codec.queueInputBuffer(
  40.                                         index,
  41.                                         0,
  42.                                         0,
  43.                                         0,
  44.                                         MediaCodec.BUFFER_FLAG_END_OF_STREAM
  45.                                 )
  46.                                 Log.i(TAG, "onInputBufferAvailable -> BUFFER_FLAG_END_OF_STREAM")
  47.                         }
  48.                 }
  49.                 override fun onOutputBufferAvailable(
  50.                         codec: MediaCodec,
  51.                         index: Int,
  52.                         info: MediaCodec.BufferInfo
  53.                 ) {
  54.                         Log.i(
  55.                                 TAG,
  56.                                 "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
  57.                         )
  58.                         val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return
  59.                         outputBuffer.get(bytes, 0, info.size)
  60.                         aacOutputStream.write(bytes, 0, info.size)
  61.                         codec.releaseOutputBuffer(index, false)
  62.                         if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
  63.                                 Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
  64.                                 aacOutputStream.close()
  65.                                 if (continuation.isActive) {
  66.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
  67.                                         continuation.resume(Unit)
  68.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
  69.                                 }
  70.                         }
  71.                 }
  72.                 override fun onOutputFormatChanged(
  73.                         codec: MediaCodec,
  74.                         format: MediaFormat
  75.                 ) {
  76.                         Log.i(
  77.                                 TAG,
  78.                                 "onOutputFormatChanged -> name: ${codec.name}, format: $format"
  79.                         )
  80.                 }
  81.         })
  82.         Log.i(TAG, "pcmToAac -> before start...")
  83.         mediaCodec.start()
  84.         Log.i(TAG, "pcmToAac -> after start...")
  85. }
  86. internal suspend fun h265ToYuvPcm(context: Context, videoUri: Uri, yuvUri: Uri, pcmUri: Uri){
  87.         val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
  88.         // 视频解码器材
  89.         val mkvDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
  90.                 .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }
  91.         if (mkvDecoders.isEmpty()){
  92.                 Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
  93.                 return
  94.         }
  95.         // 拿解码器
  96.         val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
  97.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  98.                         it.isHardwareAccelerated
  99.                 } else {
  100.                         true
  101.                 }
  102.         } ?: mkvDecoders.first()
  103.         // 重试解码器 android 8 不支持  hevc (Main 10)
  104.         // val mkvDecoder: MediaCodecInfo = mkvDecoders[1]
  105.         // MediaCodec.createByCodecName(h264Decoder.name)
  106.         Log.i(TAG, "videoToYuvPcm1 -> mkvDecoderName: ${mkvDecoder.name}")
  107.         mkvDecoders.forEach { mediaCodecInfo ->
  108.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  109.                         Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  110.                 } else {
  111.                         Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  112.                 }
  113.                 mediaCodecInfo.supportedTypes.forEach { mimeType: String ->
  114.                         if (mimeType.lowercase() == MediaFormat.MIMETYPE_VIDEO_HEVC){
  115.                                 val mediaCodecInfoCodecCapabilities: MediaCodecInfo.CodecCapabilities = mediaCodecInfo.getCapabilitiesForType(mimeType)
  116.                                 mediaCodecInfoCodecCapabilities.profileLevels.forEach { codecProfileLevel ->
  117.                                         if (codecProfileLevel.profile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain){
  118.                                                 if (codecProfileLevel.level >= MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62){
  119.                                                         Log.i(TAG, "h265ToYuvPcm -> 支持 8k h265 name: ${mediaCodecInfo.name}")
  120.                                                 } else {
  121.                                                         Log.i(TAG, "h265ToYuvPcm -> 不支持 8k h265 name: ${mediaCodecInfo.name}")
  122.                                                 }
  123.                                         }
  124.                                 }
  125.                         }
  126.                 }
  127.         }
  128.         // 音频解码器
  129.         val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
  130.                 it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
  131.         }
  132.         if (aacDecoders.isEmpty()){
  133.                 Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
  134.                 return
  135.         }
  136.         val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
  137.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  138.                         it.isHardwareAccelerated
  139.                 } else {
  140.                         true
  141.                 }
  142.         } ?: aacDecoders.first()
  143.         aacDecoders.forEach { mediaCodecInfo ->
  144.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  145.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  146.                 } else {
  147.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  148.                 }
  149.         }
  150.         val mediaExtractor = MediaExtractor()
  151.         mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())
  152.         for (i in 0 until mediaExtractor.trackCount){
  153.                 Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
  154.         }
  155.         if (mediaExtractor.trackCount < 1){
  156.                 mediaExtractor.release()
  157.                 return
  158.         }
  159.         Log.i(TAG, "videoToYuvPcm -> decode before...")
  160.         //
  161.         decode(context, 0, yuvUri, mediaExtractor, mkvDecoder.name)
  162.         decode(context, 1, pcmUri, mediaExtractor, aacDecoder.name)
  163.         // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
  164.         Log.i(TAG, "videoToYuvPcm -> decode after...")
  165.         mediaExtractor.release()
  166.         Log.i(TAG, "videoToYuvPcm -> end...")
  167. }
复制代码
使用下面的命令
  1. # 8K 265
  2. ffplay -x 1280 -y 720 -f rawvideo -pixel_format nv12 -video_size 7680x4320 -framerate 30 output2.yuv
  3. # 4k 256
  4. ffplay -x 1280 -y 720 -f rawvideo -pixel_format yuv420p -video_size 3840x2176 -framerate 60 output1.yuv
  5. ffplay -f s16le -ar 48000 -ch_layout stereo -i output.pcm
复制代码
否则会有下面的错误
  1. 2025-08-17 16:34:05.005 10483-10483 AndroidRuntime          edu.tyut.helloktorfit                E  FATAL EXCEPTION: main
  2.                                                                                                     Process: edu.tyut.helloktorfit, PID: 10483
  3.                                                                                                     java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
  4.                                                                                                             at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
  5.                                                                                                             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957)
  6.                                                                                                     Caused by: java.lang.reflect.InvocationTargetException
  7.                                                                                                             at java.lang.reflect.Method.invoke(Native Method)
  8.                                                                                                             at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
  9.                                                                                                             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957) 
  10.                                                                                                     Caused by: java.io.IOException: write failed: ENOSPC (No space left on device)
  11.                                                                                                             at libcore.io.IoBridge.write(IoBridge.java:651)
  12.                                                                                                             at java.io.FileOutputStream.write(FileOutputStream.java:432)
  13.                                                                                                             at edu.tyut.helloktorfit.manager.AudioExtractManager$decode$2$1.onOutputBufferAvailable(AudioExtractManager.kt:1051)
  14.                                                                                                             at android.media.MediaCodec$EventHandler.handleCallback(MediaCodec.java:2011)
  15.                                                                                                             at android.media.MediaCodec$EventHandler.handleMessage(MediaCodec.java:1887)
  16.                                                                                                             at android.os.Handler.dispatchMessage(Handler.java:109)
  17.                                                                                                             at android.os.Looper.loopOnce(Looper.java:250)
  18.                                                                                                             at android.os.Looper.loop(Looper.java:340)
  19.                                                                                                             at android.app.ActivityThread.main(ActivityThread.java:9865)
  20.                                                                                                             at java.lang.reflect.Method.invoke(Native Method) 
  21.                                                                                                             at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621) 
  22.                                                                                                             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957) 
  23.                                                                                                     Caused by: android.system.ErrnoException: write failed: ENOSPC (No space left on device)
  24.                                                                                                             at libcore.io.Linux.writeBytes(Native Method)
  25.                                                                                                             at libcore.io.Linux.write(Linux.java:296)
  26.                                                                                                             at libcore.io.ForwardingOs.write(ForwardingOs.java:943)
  27.                                                                                                             at libcore.io.BlockGuardOs.write(BlockGuardOs.java:448)
  28.                                                                                                             at libcore.io.ForwardingOs.write(ForwardingOs.java:943)
复制代码
mkv电影

很多机型均无法解析4k mkv电影
  1. private suspend fun decode(
  2.         context: Context,
  3.         index: Int,
  4.         output: Uri,
  5.         mediaExtractor: MediaExtractor,
  6.         decodeName: String
  7. ): Unit = suspendCancellableCoroutine { continuation ->
  8.         val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
  9.         mediaExtractor.selectTrack(index)
  10.         val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
  11.         mediaCodec.configure(mediaFormat, null, null, 0)
  12.         val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
  13.         val bytes = ByteArray(1024 * 1024 * 100)
  14.         mediaCodec.setCallback(object : MediaCodec.Callback() {
  15.                 override fun onError(
  16.                         codec: MediaCodec,
  17.                         e: MediaCodec.CodecException
  18.                 ) {
  19.                         Log.e(
  20.                                 TAG,
  21.                                 "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
  22.                                 e
  23.                         )
  24.                 }
  25.                 override fun onInputBufferAvailable(
  26.                         codec: MediaCodec,
  27.                         index: Int
  28.                 ) {
  29.                         val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
  30.                         val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
  31.                         Log.i(
  32.                                 TAG,
  33.                                 "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
  34.                         )
  35.                         if (size > 0) {
  36.                                 codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
  37.                                 mediaExtractor.advance()
  38.                         } else {
  39.                                 codec.queueInputBuffer(
  40.                                         index,
  41.                                         0,
  42.                                         0,
  43.                                         0,
  44.                                         MediaCodec.BUFFER_FLAG_END_OF_STREAM
  45.                                 )
  46.                         }
  47.                 }
  48.                 override fun onOutputBufferAvailable(
  49.                         codec: MediaCodec,
  50.                         index: Int,
  51.                         info: MediaCodec.BufferInfo
  52.                 ) {
  53.                         Log.i(
  54.                                 TAG,
  55.                                 "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
  56.                         )
  57.                         val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return
  58.                         outputBuffer.get(bytes, 0, info.size)
  59.                         aacOutputStream.write(bytes, 0, info.size)
  60.                         codec.releaseOutputBuffer(index, false)
  61.                         if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
  62.                                 Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
  63.                                 aacOutputStream.close()
  64.                                 if (continuation.isActive) {
  65.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
  66.                                         continuation.resume(Unit)
  67.                                         Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
  68.                                 }
  69.                         }
  70.                 }
  71.                 override fun onOutputFormatChanged(
  72.                         codec: MediaCodec,
  73.                         format: MediaFormat
  74.                 ) {
  75.                         Log.i(
  76.                                 TAG,
  77.                                 "onOutputFormatChanged -> name: ${codec.name}, format: $format"
  78.                         )
  79.                 }
  80.         })
  81.         Log.i(TAG, "pcmToAac -> before start...")
  82.         mediaCodec.start()
  83.         Log.i(TAG, "pcmToAac -> after start...")
  84. }
  85. internal suspend fun videoToYuvPcm1(context: Context, videoUri: Uri, yuvUri: Uri): Unit {
  86.         val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
  87.         // 视频解码器材
  88.         val mkvDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
  89.                 .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }
  90.         if (mkvDecoders.isEmpty()){
  91.                 Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
  92.                 return
  93.         }
  94.         // 拿解码器
  95.         val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
  96.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  97.                         it.isHardwareAccelerated
  98.                 } else {
  99.                         true
  100.                 }
  101.         } ?: mkvDecoders.first()
  102.         // 重试解码器 android 8 不支持  hevc (Main 10)
  103.         // val mkvDecoder: MediaCodecInfo = mkvDecoders[2]
  104.         // MediaCodec.createByCodecName(h264Decoder.name)
  105.         Log.i(TAG, "videoToYuvPcm1 -> mkvDecoderName: ${mkvDecoder.name}")
  106.         mkvDecoders.forEach { mediaCodecInfo ->
  107.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  108.                         Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  109.                 } else {
  110.                         Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  111.                 }
  112.         }
  113.         // 音频解码器
  114.         val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
  115.                 it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
  116.         }
  117.         if (aacDecoders.isEmpty()){
  118.                 Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
  119.                 return
  120.         }
  121.         val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
  122.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  123.                         it.isHardwareAccelerated
  124.                 } else {
  125.                         true
  126.                 }
  127.         } ?: aacDecoders.first()
  128.         aacDecoders.forEach { mediaCodecInfo ->
  129.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  130.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  131.                 } else {
  132.                         Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
  133.                 }
  134.         }
  135.         val mediaExtractor = MediaExtractor()
  136.         mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())
  137.         for (i in 0 until mediaExtractor.trackCount){
  138.                 Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
  139.         }
  140.         if (mediaExtractor.trackCount < 1){
  141.                 mediaExtractor.release()
  142.                 return
  143.         }
  144.         Log.i(TAG, "videoToYuvPcm -> decode before...")
  145.         //
  146.         decode(context, 0, yuvUri, mediaExtractor, mkvDecoder.name)
  147.         // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
  148.         Log.i(TAG, "videoToYuvPcm -> decode after...")
  149.         mediaExtractor.release()
  150.         Log.i(TAG, "videoToYuvPcm -> end...")
  151. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册