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]