/* * Copyright (C) 2021 pedroSG94. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pedro.encoder.input.decoder; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.util.Log; import com.pedro.encoder.Frame; import com.pedro.encoder.input.audio.GetMicrophoneData; import com.pedro.encoder.utils.CodecUtil; import com.pedro.encoder.utils.PCMUtil; import java.nio.ByteBuffer; /** * Created by pedro on 20/06/17. */ public class AudioDecoder extends BaseDecoder { private AudioDecoderInterface audioDecoderInterface; private GetMicrophoneData getMicrophoneData; private int sampleRate; private boolean isStereo; private int channels = 1; private int size = 2048; private byte[] pcmBuffer = new byte[size]; private byte[] pcmBufferMuted = new byte[11]; private boolean muted = false; public AudioDecoder(GetMicrophoneData getMicrophoneData, AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) { super(loopFileInterface); TAG = "AudioDecoder"; this.getMicrophoneData = getMicrophoneData; this.audioDecoderInterface = audioDecoderInterface; } @Override protected boolean extract(MediaExtractor audioExtractor) { size = 2048; running = false; for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) { mediaFormat = audioExtractor.getTrackFormat(i); mime = mediaFormat.getString(MediaFormat.KEY_MIME); if (mime.startsWith("audio/")) { audioExtractor.selectTrack(i); } else { mediaFormat = null; } } if (mediaFormat != null) { channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); isStereo = channels >= 2; sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); duration = mediaFormat.getLong(MediaFormat.KEY_DURATION); fixBuffer(); return true; //audio decoder not supported } else { mime = ""; mediaFormat = null; return false; } } private void fixBuffer() { if (channels >= 2) { size *= channels; } pcmBuffer = new byte[size]; } public boolean prepareAudio() { return prepare(null); } public void reset() { resetCodec(null); } @Override protected void decode() { ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); startMs = System.currentTimeMillis(); while (running) { int inIndex = codec.dequeueInputBuffer(10000); if (inIndex >= 0) { ByteBuffer buffer = inputBuffers[inIndex]; int sampleSize = extractor.readSampleData(buffer, 0); if (sampleSize < 0) { codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } else { codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0); extractor.advance(); } int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000); switch (outIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = codec.getOutputBuffers(); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: case MediaCodec.INFO_TRY_AGAIN_LATER: break; default: long extractorTs = extractor.getSampleTime() / 1000; long currentTs = System.currentTimeMillis() - startMs + seekTime; if (extractorTs > currentTs) { try { long sleepTime = extractorTs - currentTs; Thread.sleep(sleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } ByteBuffer outBuffer = outputBuffers[outIndex]; //This buffer is PCM data if (muted) { outBuffer.get(pcmBufferMuted, 0, Math.min(outBuffer.remaining(), pcmBufferMuted.length)); getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length)); } else { if (pcmBuffer.length < outBuffer.remaining()) { pcmBuffer = new byte[outBuffer.remaining()]; } outBuffer.get(pcmBuffer, 0, Math.min(outBuffer.remaining(), pcmBuffer.length)); if (channels > 2) { //downgrade to stereo byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels); getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length)); } else { getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length)); } } codec.releaseOutputBuffer(outIndex, false); break; } } // All decoded frames have been rendered, we can stop playing now if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { seekTime = 0; Log.i(TAG, "end of file out"); running = false; if (loopMode) { loopFileInterface.onReset(false); } else { audioDecoderInterface.onAudioDecoderFinished(); } } } } /** * This method should be called after prepare. * Get max output size to set max input size in encoder. */ public int getOutsize() { if (!(mime.equals(CodecUtil.AAC_MIME) || mime.equals(CodecUtil.OPUS_MIME) || mime.equals( CodecUtil.VORBIS_MIME))) { Log.i(TAG, "fixing input size"); try { if (running) { return codec.getOutputBuffers()[0].remaining(); } else { if (codec != null) { codec.start(); int outSize = codec.getOutputBuffers()[0].remaining(); stopDecoder(); if (prepare(null)) return outSize; } return 0; } } catch (Exception e) { return 0; } } else { Log.i(TAG, "default input size"); return 0; } } public void mute() { muted = true; } public void unMute() { muted = false; } public boolean isMuted() { return muted; } public int getSampleRate() { return sampleRate; } public boolean isStereo() { return isStereo; } }