嫂潍 发表于 2025-8-17 16:44:44

MediaCodec使用之MP4解码(三)

MediaCodec使用之视频解码(三)

准备三个视频,一个是普通的h264视频,一个是8k h265的,一个是电影的mkv
使用fflay播放并查看详细信息。
普通视频
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '.\video1.mp4':0B
Metadata:
    major_brand   : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
Duration: 00:02:29.94, start: 0.000000, bitrate: 641 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 662x1280 , 499 kb/s, 59.96 fps, 60 tbr, 15360 tbn (default)
      Metadata:
      handler_name    : VideoHandler
      vendor_id       :
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
      Metadata:
      handler_name    : SoundHandler
      vendor_id       :
   5.30 A-V: -0.031 fd=24 aq=   16KB vq=   55KB sq=    0B h265的
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '.\video2.mp4':0B
Metadata:
    major_brand   : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2025-08-17T03:21:22.000000Z
    location      : +40.0770+116.4052/
    location-eng    : +40.0770+116.4052/
    com.android.version: 15
Duration: 00:00:15.90, start: 0.000000, bitrate: 81199 kb/s
Stream #0:0(eng): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, bt709), 7680x4320, 80753 kb/s, 30 fps, 30 tbr, 90k tbn (default)
      Metadata:
      creation_time   : 2025-08-17T03:21:22.000000Z
      handler_name    : VideoHandle
      vendor_id       :
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 256 kb/s (default)
      Metadata:
      creation_time   : 2025-08-17T03:21:22.000000Z
      handler_name    : SoundHandle
      vendor_id       : 电影
ffplay -x 1280 -y 720 -ss 00:50:00video3.mkv

Input #0, matroska,webm, from '.\video3.mkv':
Metadata:
    title         : Black.Widow.2021.2160p.BluRay.REMUX.HEVC.DTS-HD.MA.7.1.TrueHD.7.1.Atmos.DTS-HD.MA.7.1.zh-老K
    encoder         : libebml v1.4.2 + libmatroska v1.6.4
    creation_time   : 2024-04-06T01:57:58.000000Z
Duration: 02:13:46.79, start: 0.000000, bitrate: 57664 kb/s
Chapters:
    Chapter #0:0: start 0.000000, end 295.169867
      Metadata:
      title         : 第 01 章
    Chapter #0:1: start 295.169867, end 790.080956
      Metadata:
      title         : 第 02 章
        ...
Stream #0:0: Video: hevc (Main 10), yuv420p10le(tv, bt2020nc/bt2020/smpte2084), 3840x2160 , 23.98 fps, 23.98 tbr, 1k tbn (default)
      Metadata:
      BPS             : 46197111
      DURATION      : 02:13:46.769000000
      NUMBER_OF_FRAMES: 192450
      NUMBER_OF_BYTES : 46351692620
      SOURCE_ID       : 001011
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
Stream #0:1(eng): Audio: truehd (Dolby TrueHD + Dolby Atmos), 48000 Hz, 7.1, s32 (24 bit) (default)
      Metadata:
      title         : TrueHD Atmos 7.1
      BPS             : 4284386
      DURATION      : 02:13:46.769000000
      NUMBER_OF_FRAMES: 9632122
      NUMBER_OF_BYTES : 4298722162
      SOURCE_ID       : 001100
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
Stream #0:2(eng): Audio: dts (dca) (DTS-HD MA), 48000 Hz, 7.1, s32p (24 bit)
      Metadata:
      title         : DTS-HD MA 7.1
      BPS             : 4411433
      DURATION      : 02:13:46.794000000
      NUMBER_OF_FRAMES: 752481
      NUMBER_OF_BYTES : 4426208460
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:3(chi): Audio: dts (dca) (DTS-HD MA), 48000 Hz, 7.1, s16p
      Metadata:
      title         : 国语 DTS-HD MA 7.1
      BPS             : 2647732
      DURATION      : 02:13:46.773000000
      NUMBER_OF_FRAMES: 752510
      NUMBER_OF_BYTES : 2656593104
      SOURCE_ID       : 001105
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
Stream #0:4(chi): Subtitle: ass (ssa) (default)
      Metadata:
      title         : 简英
      BPS             : 199
      DURATION      : 02:12:27.160000000
      NUMBER_OF_FRAMES: 1376
      NUMBER_OF_BYTES : 198336
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES
Stream #0:5(chi): Subtitle: hdmv_pgs_subtitle (pgssub)
      Metadata:
      title         : 国语 简体
      BPS             : 93951
      DURATION      : 02:13:00.556000000
      NUMBER_OF_FRAMES: 6703
      NUMBER_OF_BYTES : 93723446
      SOURCE_ID       : 0012a4
      _STATISTICS_WRITING_APP: mkvmerge v68.0.0 ('The Curtain') 64-bit
      _STATISTICS_WRITING_DATE_UTC: 2024-04-06 01:57:58
      _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
Stream #0:6: Video: mjpeg (Progressive), yuvj420p(pc, bt470bg/unknown/unknown), 254x254 , 90k tbr, 90k tbn (attached pic)
      Metadata:
      filename      : 老KQQ 195383233.jpg
      mimetype      : image/jpeg解码普通视频

internal suspend fun videoToYuvPcm(context: Context, videoUri: Uri, yuvUri: Uri, pcmUri: Uri): Unit {

        val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)

        // 视频解码器材
        val h264Decoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
                .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_AVC in it.supportedTypes }

        if (h264Decoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持h264解码")
                return
        }

        // 拿解码器
        val h264Decoder: MediaCodecInfo = h264Decoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: h264Decoders.first()

        // MediaCodec.createByCodecName(h264Decoder.name)

        h264Decoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "h264Decoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
        }

        // 音频解码器
        val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
                it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
        }

        if (aacDecoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
                return
        }

        val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: aacDecoders.first()

        aacDecoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
        }

        val mediaExtractor = MediaExtractor()
        mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())

        for (i in 0 until mediaExtractor.trackCount){
                Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
        }

        if (mediaExtractor.trackCount < 2){
                mediaExtractor.release()
                return
        }

        Log.i(TAG, "videoToYuvPcm -> decode before...")
        //
        decode(context, 0, yuvUri, mediaExtractor, h264Decoder.name)
        // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
        decode(context, 1, pcmUri, mediaExtractor, aacDecoder.name)
        Log.i(TAG, "videoToYuvPcm -> decode after...")

        mediaExtractor.release()

        Log.i(TAG, "videoToYuvPcm -> end...")
}

private suspend fun decode(
        context: Context,
        index: Int,
        output: Uri,
        mediaExtractor: MediaExtractor,
        decodeName: String
): Unit = suspendCancellableCoroutine { continuation ->

        val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
        mediaExtractor.selectTrack(index)
        val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
        mediaCodec.configure(mediaFormat, null, null, 0)

        val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
        val bytes = ByteArray(1024 * 1024 * 2)

        mediaCodec.setCallback(object : MediaCodec.Callback() {
                override fun onError(
                        codec: MediaCodec,
                        e: MediaCodec.CodecException
                ) {
                        Log.e(
                                TAG,
                                "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
                                e
                        )
                }

                override fun onInputBufferAvailable(
                        codec: MediaCodec,
                        index: Int
                ) {
                        val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
                        val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
                        Log.i(
                                TAG,
                                "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
                        )
                        if (size > 0) {
                                codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
                                mediaExtractor.advance()
                        } else {
                                codec.queueInputBuffer(
                                        index,
                                        0,
                                        0,
                                        0,
                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                                )
                        }
                }

                override fun onOutputBufferAvailable(
                        codec: MediaCodec,
                        index: Int,
                        info: MediaCodec.BufferInfo
                ) {
                        Log.i(
                                TAG,
                                "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
                        )

                        val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return

                        outputBuffer.get(bytes, 0, info.size)

                        aacOutputStream.write(bytes, 0, info.size)

                        codec.releaseOutputBuffer(index, false)

                        if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                                Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
                                aacOutputStream.close()
                                if (continuation.isActive) {
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
                                        continuation.resume(Unit)
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
                                }
                        }
                }

                override fun onOutputFormatChanged(
                        codec: MediaCodec,
                        format: MediaFormat
                ) {
                        Log.i(
                                TAG,
                                "onOutputFormatChanged -> name: ${codec.name}, format: $format"
                        )
                }
        })
        Log.i(TAG, "pcmToAac -> before start...")
        mediaCodec.start()
        Log.i(TAG, "pcmToAac -> after start...")
}播放生成的音视频
ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
ffplay -f rawvideo -pixel_format yuv420p -video_size 672x1280 -framerate 60 output.yuv8k h265

记得视频搞短一点,有些解码器会吞掉EOS标识,不清楚为什么
private suspend fun decode(
        context: Context,
        index: Int,
        output: Uri,
        mediaExtractor: MediaExtractor,
        decodeName: String
): Unit = suspendCancellableCoroutine { continuation ->

        val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
        mediaExtractor.selectTrack(index)
        val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
        mediaCodec.configure(mediaFormat, null, null, 0)

        val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
        val bytes = ByteArray(1024 * 1024 * 100)

        mediaCodec.setCallback(object : MediaCodec.Callback() {
                override fun onError(
                        codec: MediaCodec,
                        e: MediaCodec.CodecException
                ) {
                        Log.e(
                                TAG,
                                "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
                                e
                        )
                }

                override fun onInputBufferAvailable(
                        codec: MediaCodec,
                        index: Int
                ) {
                        val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
                        val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
                        Log.i(
                                TAG,
                                "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
                        )
                        if (size > 0) {
                                codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
                                mediaExtractor.advance()
                        } else {
                                codec.queueInputBuffer(
                                        index,
                                        0,
                                        0,
                                        0,
                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                                )
                                Log.i(TAG, "onInputBufferAvailable -> BUFFER_FLAG_END_OF_STREAM")
                        }
                }

                override fun onOutputBufferAvailable(
                        codec: MediaCodec,
                        index: Int,
                        info: MediaCodec.BufferInfo
                ) {
                        Log.i(
                                TAG,
                                "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
                        )

                        val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return

                        outputBuffer.get(bytes, 0, info.size)

                        aacOutputStream.write(bytes, 0, info.size)

                        codec.releaseOutputBuffer(index, false)

                        if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                                Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
                                aacOutputStream.close()
                                if (continuation.isActive) {
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
                                        continuation.resume(Unit)
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
                                }
                        }
                }

                override fun onOutputFormatChanged(
                        codec: MediaCodec,
                        format: MediaFormat
                ) {
                        Log.i(
                                TAG,
                                "onOutputFormatChanged -> name: ${codec.name}, format: $format"
                        )
                }
        })
        Log.i(TAG, "pcmToAac -> before start...")
        mediaCodec.start()
        Log.i(TAG, "pcmToAac -> after start...")
}
internal suspend fun h265ToYuvPcm(context: Context, videoUri: Uri, yuvUri: Uri, pcmUri: Uri){

        val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)

        // 视频解码器材
        val mkvDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
                .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }

        if (mkvDecoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
                return
        }

        // 拿解码器
        val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: mkvDecoders.first()

        // 重试解码器 android 8 不支持hevc (Main 10)
        // val mkvDecoder: MediaCodecInfo = mkvDecoders
        // MediaCodec.createByCodecName(h264Decoder.name)
        Log.i(TAG, "videoToYuvPcm1 -> mkvDecoderName: ${mkvDecoder.name}")

        mkvDecoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
                mediaCodecInfo.supportedTypes.forEach { mimeType: String ->
                        if (mimeType.lowercase() == MediaFormat.MIMETYPE_VIDEO_HEVC){
                                val mediaCodecInfoCodecCapabilities: MediaCodecInfo.CodecCapabilities = mediaCodecInfo.getCapabilitiesForType(mimeType)
                                mediaCodecInfoCodecCapabilities.profileLevels.forEach { codecProfileLevel ->
                                        if (codecProfileLevel.profile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain){
                                                if (codecProfileLevel.level >= MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62){
                                                        Log.i(TAG, "h265ToYuvPcm -> 支持 8k h265 name: ${mediaCodecInfo.name}")
                                                } else {
                                                        Log.i(TAG, "h265ToYuvPcm -> 不支持 8k h265 name: ${mediaCodecInfo.name}")
                                                }
                                        }
                                }
                        }
                }
        }

        // 音频解码器
        val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
                it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
        }

        if (aacDecoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
                return
        }

        val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: aacDecoders.first()

        aacDecoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
        }

        val mediaExtractor = MediaExtractor()
        mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())

        for (i in 0 until mediaExtractor.trackCount){
                Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
        }

        if (mediaExtractor.trackCount < 1){
                mediaExtractor.release()
                return
        }

        Log.i(TAG, "videoToYuvPcm -> decode before...")
        //
        decode(context, 0, yuvUri, mediaExtractor, mkvDecoder.name)
        decode(context, 1, pcmUri, mediaExtractor, aacDecoder.name)
        // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
        Log.i(TAG, "videoToYuvPcm -> decode after...")

        mediaExtractor.release()

        Log.i(TAG, "videoToYuvPcm -> end...")
}使用下面的命令
# 8K 265
ffplay -x 1280 -y 720 -f rawvideo -pixel_format nv12 -video_size 7680x4320 -framerate 30 output2.yuv
# 4k 256
ffplay -x 1280 -y 720 -f rawvideo -pixel_format yuv420p -video_size 3840x2176 -framerate 60 output1.yuv
ffplay -f s16le -ar 48000 -ch_layout stereo -i output.pcm否则会有下面的错误
2025-08-17 16:34:05.005 10483-10483 AndroidRuntime          edu.tyut.helloktorfit                EFATAL EXCEPTION: main
                                                                                                    Process: edu.tyut.helloktorfit, PID: 10483
                                                                                                    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
                                                                                                            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
                                                                                                            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957)
                                                                                                    Caused by: java.lang.reflect.InvocationTargetException
                                                                                                            at java.lang.reflect.Method.invoke(Native Method)
                                                                                                            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
                                                                                                            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957) 
                                                                                                    Caused by: java.io.IOException: write failed: ENOSPC (No space left on device)
                                                                                                            at libcore.io.IoBridge.write(IoBridge.java:651)
                                                                                                            at java.io.FileOutputStream.write(FileOutputStream.java:432)
                                                                                                            at edu.tyut.helloktorfit.manager.AudioExtractManager$decode$2$1.onOutputBufferAvailable(AudioExtractManager.kt:1051)
                                                                                                            at android.media.MediaCodec$EventHandler.handleCallback(MediaCodec.java:2011)
                                                                                                            at android.media.MediaCodec$EventHandler.handleMessage(MediaCodec.java:1887)
                                                                                                            at android.os.Handler.dispatchMessage(Handler.java:109)
                                                                                                            at android.os.Looper.loopOnce(Looper.java:250)
                                                                                                            at android.os.Looper.loop(Looper.java:340)
                                                                                                            at android.app.ActivityThread.main(ActivityThread.java:9865)
                                                                                                            at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621) 
                                                                                                            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957) 
                                                                                                    Caused by: android.system.ErrnoException: write failed: ENOSPC (No space left on device)
                                                                                                            at libcore.io.Linux.writeBytes(Native Method)
                                                                                                            at libcore.io.Linux.write(Linux.java:296)
                                                                                                            at libcore.io.ForwardingOs.write(ForwardingOs.java:943)
                                                                                                            at libcore.io.BlockGuardOs.write(BlockGuardOs.java:448)
                                                                                                            at libcore.io.ForwardingOs.write(ForwardingOs.java:943)mkv电影

很多机型均无法解析4k mkv电影
private suspend fun decode(
        context: Context,
        index: Int,
        output: Uri,
        mediaExtractor: MediaExtractor,
        decodeName: String
): Unit = suspendCancellableCoroutine { continuation ->

        val mediaFormat: MediaFormat = mediaExtractor.getTrackFormat(index)
        mediaExtractor.selectTrack(index)
        val mediaCodec: MediaCodec = MediaCodec.createByCodecName(decodeName)
        mediaCodec.configure(mediaFormat, null, null, 0)

        val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(output)!!
        val bytes = ByteArray(1024 * 1024 * 100)

        mediaCodec.setCallback(object : MediaCodec.Callback() {
                override fun onError(
                        codec: MediaCodec,
                        e: MediaCodec.CodecException
                ) {
                        Log.e(
                                TAG,
                                "onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
                                e
                        )
                }

                override fun onInputBufferAvailable(
                        codec: MediaCodec,
                        index: Int
                ) {
                        val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
                        val size: Int = mediaExtractor.readSampleData(inputBuffer, 0)
                        Log.i(
                                TAG,
                                "onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}, size: $size"
                        )
                        if (size > 0) {
                                codec.queueInputBuffer(index, 0, size, mediaExtractor.sampleTime, mediaExtractor.sampleFlags)
                                mediaExtractor.advance()
                        } else {
                                codec.queueInputBuffer(
                                        index,
                                        0,
                                        0,
                                        0,
                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                                )
                        }
                }

                override fun onOutputBufferAvailable(
                        codec: MediaCodec,
                        index: Int,
                        info: MediaCodec.BufferInfo
                ) {
                        Log.i(
                                TAG,
                                "onOutputBufferAvailable -> name: ${codec.name}, index: $index, infoSize: ${info.size}, thread: ${Thread.currentThread()}"
                        )

                        val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return

                        outputBuffer.get(bytes, 0, info.size)

                        aacOutputStream.write(bytes, 0, info.size)

                        codec.releaseOutputBuffer(index, false)

                        if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                                Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
                                aacOutputStream.close()
                                if (continuation.isActive) {
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
                                        continuation.resume(Unit)
                                        Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
                                }
                        }
                }

                override fun onOutputFormatChanged(
                        codec: MediaCodec,
                        format: MediaFormat
                ) {
                        Log.i(
                                TAG,
                                "onOutputFormatChanged -> name: ${codec.name}, format: $format"
                        )
                }
        })
        Log.i(TAG, "pcmToAac -> before start...")
        mediaCodec.start()
        Log.i(TAG, "pcmToAac -> after start...")
}

internal suspend fun videoToYuvPcm1(context: Context, videoUri: Uri, yuvUri: Uri): Unit {

        val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)

        // 视频解码器材
        val mkvDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
                .filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }

        if (mkvDecoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
                return
        }

        // 拿解码器
        val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: mkvDecoders.first()

        // 重试解码器 android 8 不支持hevc (Main 10)
        // val mkvDecoder: MediaCodecInfo = mkvDecoders
        // MediaCodec.createByCodecName(h264Decoder.name)
        Log.i(TAG, "videoToYuvPcm1 -> mkvDecoderName: ${mkvDecoder.name}")

        mkvDecoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
        }

        // 音频解码器
        val aacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {
                it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_AAC)
        }

        if (aacDecoders.isEmpty()){
                Log.i(TAG, "videoToYuvPcm -> 不支持aac解码")
                return
        }

        val aacDecoder: MediaCodecInfo = aacDecoders.firstOrNull {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        it.isHardwareAccelerated
                } else {
                        true
                }
        } ?: aacDecoders.first()

        aacDecoders.forEach { mediaCodecInfo ->
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        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()}")
                } else {
                        Log.i(TAG, "aacDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
                }
        }

        val mediaExtractor = MediaExtractor()
        mediaExtractor.setDataSource(context, videoUri, mapOf<String, String>())

        for (i in 0 until mediaExtractor.trackCount){
                Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")
        }

        if (mediaExtractor.trackCount < 1){
                mediaExtractor.release()
                return
        }

        Log.i(TAG, "videoToYuvPcm -> decode before...")
        //
        decode(context, 0, yuvUri, mediaExtractor, mkvDecoder.name)
        // ffplay -f s16le -ar 44100 -ch_layout stereo -i output.pcm
        Log.i(TAG, "videoToYuvPcm -> decode after...")

        mediaExtractor.release()

        Log.i(TAG, "videoToYuvPcm -> end...")
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: MediaCodec使用之MP4解码(三)