当前位置: 首页 > news >正文

MediaCodec的使用(音频解码二)

MediaCodec的使用(音频解码二)

本节我们使用MediaCodec进行音频解码,音频解码与音频

准备工作

准备一个ogg文件,然后使用ffplay播放

ffplay audio1.ogg
PS D:\SoftWare\Android\AndroidProjects\JetpacksProjects\HelloKtorfit> ffplay .\audio1.ogg
ffplay version 7.1.1-essentials_build-www.gyan.dev Copyright (c) 2003-2025 the FFmpeg developersbuilt with gcc 14.2.0 (Rev1, Built by MSYS2 project)configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --ena
ble-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11
va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberbandlibavutil      59. 39.100 / 59. 39.100libavcodec     61. 19.101 / 61. 19.101libavformat    61.  7.100 / 61.  7.100libavdevice    61.  3.100 / 61.  3.100libavfilter    10.  4.100 / 10.  4.100libswscale      8.  3.100 /  8.  3.100libswresample   5.  3.100 /  5.  3.100libpostproc    58.  3.100 / 58.  3.100
Input #0, ogg, from '.\audio1.ogg':0KB vq=    0KB sq=    0B Duration: 00:06:18.10, start: 0.000000, bitrate: 347 kb/sStream #0:0: Audio: vorbis, 44100 Hz, stereo, fltp, 320 kb/sMetadata:TITLE           : 春庭雪ARTIST          : 赵允哲/DJMagend             : 16419143endserial       : 1067571064endgran         : 1667416613.96 M-A:  0.000 fd=   0 aq=   39KB vq=    0KB sq=    0B 

可以看到视频详细信息。

解码

/*** 解码 ogg 为 pcm*/
internal suspend fun oggToPcm(context: Context, oggUri: Uri, pcmUri: Uri): Unit = suspendCancellableCoroutine { continuation ->val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)// 解码器val oggDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_VORBIS)}oggDecoders.forEach { mediaCodecInfo ->if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {Log.i(TAG, "oggToPcm -> 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, "oggToPcm -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")}}Log.i(TAG, "pcmToAac -> oggDecoders: ${oggDecoders.joinToString { it.name }}")if (oggDecoders.isEmpty()){// 不支持硬件解码Log.i(TAG, "oggToPcm -> 不支持硬件解码...") // 使用软解码if (continuation.isActive){continuation.resume(Unit)}return@suspendCancellableCoroutine}// 可惜 aac 仅支持软解码Log.i(TAG, "oggToPcm -> 支持硬件解码")val mediaFormat1: MediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_RAW, 16000, 1)val mediaExtractor: MediaExtractor = MediaExtractor()mediaExtractor.setDataSource(context, oggUri, mapOf<String, String>())val mediaFormat2: MediaFormat =  mediaExtractor.getTrackFormat(0) // 或者提前判断trackCount > 0Log.i(TAG, "oggToPcm -> mediaFormat1: $mediaFormat1, mediaFormat2: $mediaFormat2")mediaExtractor.selectTrack(0)val mediaCodec: MediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_VORBIS)mediaCodec.configure(mediaFormat2, null, null, 0) // 解码val pcmInputStream: InputStream = context.contentResolver.openInputStream(oggUri)!!val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(pcmUri)!!val bytes = ByteArray(1024 * 8)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) ?: returnval 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) ?: returnoutputBuffer.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 -> == 编码结束...") // todopcmInputStream.close()aacOutputStream.close()mediaExtractor.release()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...")
}

细节1

看个特殊的例子

internal suspend fun oggToPcm1(context: Context, oggUri: Uri, pcmUri: Uri): Unit = suspendCancellableCoroutine { continuation ->continuation.resume(Unit)continuation.resume(Unit)
}

可以看到如下异常

2025-08-16 22:55:53.179 10991-10991 AndroidRuntime          pid-10991                            E  FATAL EXCEPTION: main @coroutine#39Process: edu.tyut.helloktorfit, PID: 10991java.lang.IllegalStateException: Already resumed, but proposed with update kotlin.Unitat kotlinx.coroutines.CancellableContinuationImpl.alreadyResumedError(CancellableContinuationImpl.kt:556)at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core(CancellableContinuationImpl.kt:521)at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core$default(CancellableContinuationImpl.kt:493)at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:359)

在回调众多的方法当中,无法确定resume是否被多次执行的可能,可以修改为如下

internal suspend fun oggToPcm1(context: Context, oggUri: Uri, pcmUri: Uri): Unit = suspendCancellableCoroutine { continuation ->if (continuation.isActive) {continuation.resume(Unit)}if (continuation.isActive) {continuation.resume(Unit)}
}

细节2

如果使用的是Java语言没有协程咋办?
可以使用下面的方法进行

internal fun oggToPcm1(context: Context, oggUri: Uri, pcmUri: Uri) : Unit  {Log.i(TAG, "oggToPcm1 -> launch before: ${Thread.currentThread()}")var done = falsethread {Thread.sleep(1000)synchronized(this@AudioExtractManager){Log.i(TAG, "oggToPcm1 -> notify before: ${Thread.currentThread()}")done = truethis@AudioExtractManager.notify()Log.i(TAG, "oggToPcm1 -> notify after: ${Thread.currentThread()}")}}Log.i(TAG, "oggToPcm1 -> launch after: ${Thread.currentThread()}")Thread.sleep(2000)synchronized(this@AudioExtractManager){Log.i(TAG, "oggToPcm1 -> wait before: ${Thread.currentThread()}")while (!done) {this@AudioExtractManager.wait()}Log.i(TAG, "oggToPcm1 -> wait after: ${Thread.currentThread()}")}Log.i(TAG, "oggToPcm1 -> finish...")
}

可以看到Java的写法显得非常low,还容易导致死锁,一定会导致线程上下文切换,消耗资源。

AAC 解码为 PCM 目前使用的软解

/*** 解码 ogg 为 pcm*/
internal suspend fun flacToPcm(context: Context, flacUri: Uri, pcmUri: Uri): Unit = suspendCancellableCoroutine { continuation ->val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)// 解码器val flacDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos.filter { !it.isEncoder }.filter {it.supportedTypes.contains(element = MediaFormat.MIMETYPE_AUDIO_FLAC)}flacDecoders.forEach { mediaCodecInfo ->if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {Log.i(TAG, "oggToPcm -> 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, "oggToPcm -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")}}Log.i(TAG, "pcmToAac -> oggDecoders: ${flacDecoders.joinToString { it.name }}")if (flacDecoders.isEmpty()){// 不支持硬件解码Log.i(TAG, "oggToPcm -> 不支持硬件解码...") // 使用软解码if (continuation.isActive){continuation.resume(Unit)}return@suspendCancellableCoroutine}// 可惜 aac 仅支持软解码Log.i(TAG, "oggToPcm -> 支持硬件解码")val mediaExtractor = MediaExtractor()mediaExtractor.setDataSource(context, flacUri, mapOf<String, String>())val mediaFormat2: MediaFormat =  mediaExtractor.getTrackFormat(0) // 或者提前判断trackCount > 0for (i in 0 until mediaExtractor.trackCount){Log.i(TAG, "flacToPcm -> format: ${mediaExtractor.getTrackFormat(i)}")}Log.i(TAG, "oggToPcm -> mediaFormat2: $mediaFormat2, trackCount: ${mediaExtractor.trackCount}")mediaExtractor.selectTrack(0)val mediaCodec: MediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_RAW)mediaCodec.configure(mediaFormat2, null, null, 0) // 解码val aacOutputStream: OutputStream = context.contentResolver.openOutputStream(pcmUri)!!val bytes = ByteArray(1024 * 1024)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) ?: returnval 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) ?: returnoutputBuffer.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 -> == 编码结束...") // todoaacOutputStream.close()mediaExtractor.release()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...")
}
http://www.sczhlp.com/news/13511/

相关文章:

  • 微信永封解封教程
  • 题解:Physical Education Lessons
  • ODT 珂朵莉树详解
  • ISBN
  • 告别盲目招聘!Moka自定义报表一键诊断招聘瓶颈
  • 软考系统分析师每日学习卡 | [日期:2025-08-16] | [今日主题:规范化理论-范式判断]
  • Linux系统基础
  • 在本地部署Qwen大语言模型全过程总结
  • 快捷键
  • 8.16随笔
  • 2025年8月16日
  • 大一总结
  • unioffice校验
  • AtCoder Beginner Contest 419 ABCDEF 题目解析
  • 全屋智能系统通过各种传感器实现对家庭环境、设备和资源的自动化监控与控制。传感器在全屋智能中起到至关重要的作用,能提升家居的舒适性、安全性和能源效率。以下是常见的传感器分类:
  • 【攻防世界】fakezip
  • 有用的github
  • Markdown 基础学习
  • 在K8S中,负载均衡器有何作用?
  • 哈夫曼编码,编码过程:
  • 2️⃣Linux系统安装
  • 在AI技术快速落地的时代,挖掘低层设计学习的新需求成为关键——某知名编程教育平台需求洞察
  • 在K8S中,各模块如何与APlServer通信?
  • C++小白修仙记_LeetCode刷题_1323 6 和 9 组成的最大数字
  • markdown-it-mathjax3-pro —— 新一代 Markdown 数学公式渲染插件
  • 第一个博客
  • 修改win11右键默认显示更多选项
  • 报表相关知识点
  • 25-暑期-来追梦noip-卷3 总结
  • 题目等候区