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[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)
      Metadata:
        handler_name    : VideoHandler
        vendor_id       : [0][0][0][0]
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
      Metadata:
        handler_name    : SoundHandler
        vendor_id       : [0][0][0][0]
   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[0x1](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       : [0][0][0][0]
  Stream #0:1[0x2](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       : [0][0][0][0]

电影

 ffplay -x 1280 -y 720 -ss 00:50:00  video3.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 [SAR 1:1 DAR 16:9], 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 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn (attached pic)
      Metadata:
        filename        : 老K  QQ 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.yuv

8k 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[1]
	// 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                E  FATAL 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[2]
	// 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...")
}
posted @ 2025-08-17 17:36  爱情丶眨眼而去  阅读(10)  评论(0)    收藏  举报