From 60611bcda606873379eb9d4b4765500f34376cb5 Mon Sep 17 00:00:00 2001 From: xufuji456 Date: Mon, 21 Mar 2022 21:36:43 +0800 Subject: [PATCH] Feature: add AudioTrackController --- .../controller/AudioTrackController.kt | 146 ++++++++++++++++++ .../controller/MediaDecodeController.kt | 5 +- .../controller/MediaMuxController.kt | 5 +- 3 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 AndroidMedia/src/main/java/com/frank/androidmedia/controller/AudioTrackController.kt diff --git a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/AudioTrackController.kt b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/AudioTrackController.kt new file mode 100644 index 0000000..616122b --- /dev/null +++ b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/AudioTrackController.kt @@ -0,0 +1,146 @@ +package com.frank.androidmedia.controller + +import android.media.* +import android.os.SystemClock +import android.util.Log +import java.lang.Exception + +/** + * 1. Using MediaExtractor to demux audio. + * 2. Using MediaCodec to decode audio. + * 3. Using AudioTrack to play audio. + * See also AAduio, oboe, openSL ES. + * + * @author frank + * @date 2022/3/21 + */ +open class AudioTrackController { + + companion object { + val TAG: String = AudioTrackController::class.java.simpleName + + private const val DEQUEUE_TIME = (10 * 1000).toLong() + private const val SLEEP_TIME: Long = 20 + } + + private var audioTrack: AudioTrack? = null + private var mediaCodec: MediaCodec? = null + private var mediaExtractor: MediaExtractor? = null + + private fun parseAudioFormat(path: String): MediaFormat? { + mediaExtractor = MediaExtractor() + try { + mediaExtractor?.setDataSource(path) + for (i in 0 until mediaExtractor!!.trackCount) { + val mediaFormat = mediaExtractor!!.getTrackFormat(i) + val mimeType = mediaFormat.getString(MediaFormat.KEY_MIME) + if (mimeType != null && mimeType.startsWith("audio")) { + mediaExtractor!!.selectTrack(i) + return mediaFormat + } + } + } catch (e: Exception) { + Log.e(TAG, "parseAudioFormat err=$e") + } + return null + } + + private fun initMediaCodec(mediaFormat: MediaFormat): Boolean { + val mimeType = mediaFormat.getString(MediaFormat.KEY_MIME) + mediaCodec = MediaCodec.createDecoderByType(mimeType) + return try { + mediaCodec!!.configure(mediaFormat, null, null, 0) + mediaCodec!!.start() + true + } catch (e: Exception) { + Log.e(TAG, "initMediaCodec err=$e") + false + } + } + + private fun initAudioTrack(mediaFormat: MediaFormat): Boolean { + val sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) + val channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + val channelConfig = if (channelCount == 1) { + AudioFormat.CHANNEL_OUT_MONO + } else { + AudioFormat.CHANNEL_OUT_STEREO + } + val audioFormat = AudioFormat.ENCODING_PCM_16BIT + val bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + Log.e(TAG, "sampleRate=$sampleRate, channelCount=$channelCount, bufferSize=$bufferSize") + + try { + audioTrack = AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM) + audioTrack!!.play() +// audioTrack!!.attachAuxEffect() + } catch (e: Exception) { + Log.e(TAG, "initAudioTrack err=$e") + return false + } + return true + } + + private fun release() { + if (mediaExtractor != null) { + mediaExtractor!!.release() + mediaExtractor = null + } + if (mediaCodec != null) { + mediaCodec!!.release() + mediaCodec = null + } + if (audioTrack != null) { + audioTrack!!.release() + audioTrack = null + } + } + + fun playAudio(path: String) { + val data = ByteArray(10 * 1024) + var finished = false + val bufferInfo = MediaCodec.BufferInfo() + val mediaFormat = parseAudioFormat(path) ?: return release() + var result = initMediaCodec(mediaFormat) + if (!result) { + return release() + } + result = initAudioTrack(mediaFormat) + if (!result) { + return release() + } + + while (!finished) { + val inputIndex = mediaCodec!!.dequeueInputBuffer(DEQUEUE_TIME) + if (inputIndex >= 0) { + val inputBuffer = mediaCodec!!.getInputBuffer(inputIndex) + // demux + val sampleSize = mediaExtractor!!.readSampleData(inputBuffer!!, 0) + Log.e(TAG, "sampleSize=$sampleSize") + // decode + if (sampleSize < 0) { + mediaCodec!!.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) + finished = true + } else { + mediaCodec!!.queueInputBuffer(inputIndex, 0, sampleSize, mediaExtractor!!.sampleTime, mediaExtractor!!.sampleFlags) + mediaExtractor!!.advance() + } + } + + val outputIndex = mediaCodec!!.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME) + // play + if (outputIndex >= 0) { + val outputBuffer = mediaCodec!!.getOutputBuffer(outputIndex) + val size = outputBuffer!!.limit() + Log.e(TAG, "outputIndex=$outputIndex, position=" + outputBuffer.position() + " ,limit=" + size) + outputBuffer.get(data, outputBuffer.position(), size) + audioTrack!!.write(data, 0, size) + mediaCodec!!.releaseOutputBuffer(outputIndex, false) + SystemClock.sleep(SLEEP_TIME) + } + } + + release() + } + +} \ No newline at end of file diff --git a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaDecodeController.kt b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaDecodeController.kt index ca94e59..4e1362a 100644 --- a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaDecodeController.kt +++ b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaDecodeController.kt @@ -209,7 +209,6 @@ open class MediaDecodeController(val mSurface: Surface, val mFilePath: String, v mediaCodec!!.configure(mediaFormat, mSurface, null, 0) mediaCodec!!.start() val bufferInfo = MediaCodec.BufferInfo() - val inputBuffers = mediaCodec!!.inputBuffers while (!isInterrupted) { if (!isPreviewing) { @@ -219,8 +218,8 @@ open class MediaDecodeController(val mSurface: Surface, val mFilePath: String, v //dequeue from input buffer val inputIndex = mediaCodec!!.dequeueInputBuffer(DEQUEUE_TIME) if (inputIndex >= 0) { - val inputBuffer = inputBuffers[inputIndex] - val sampleSize = mediaExtractor!!.readSampleData(inputBuffer, 0) + val inputBuffer = mediaCodec!!.getInputBuffer(inputIndex) + val sampleSize = mediaExtractor!!.readSampleData(inputBuffer!!, 0) //enqueue to input buffer if (sampleSize < 0) { mediaCodec!!.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) diff --git a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaMuxController.kt b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaMuxController.kt index 5f879b5..840399d 100644 --- a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaMuxController.kt +++ b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaMuxController.kt @@ -57,7 +57,6 @@ open class MediaMuxController { while (!finished) { val sampleSize = mediaExtractor.readSampleData(inputBuffer, 0) - Log.i("MediaMuxerController", "sampleIndex=" + mediaExtractor.sampleTrackIndex + ",sampleSize=" + sampleSize) if (sampleSize > 0) { bufferInfo.size = sampleSize bufferInfo.flags = mediaExtractor.sampleFlags @@ -75,14 +74,14 @@ open class MediaMuxController { } } catch (e: Exception) { - Log.e("MediaMuxerController", "mux error=$e") + Log.e("MediaMuxController", "mux error=$e") happenError = true } finally { try { mediaMuxer.release() mediaExtractor.release() } catch (e1: Exception) { - Log.e("MediaMuxerController", "release error=$e1") + Log.e("MediaMuxController", "release error=$e1") } return !happenError }