/* <!-- +++ package com.almalence.opencam_plus.ui; +++ --> */ // <!-- -+- package com.almalence.opencam.ui; //-+- --> import java.io.IOException; import java.nio.ByteBuffer; import android.annotation.SuppressLint; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaRecorder; import android.util.Log; @SuppressLint("NewApi") public class AudioRecorder { public static final String TAG = "AudioRecorder"; public static final String MIME_TYPE_AUDIO = "audio/mp4a-latm"; public static final int SAMPLE_RATE = 44100; public static final int CHANNEL_COUNT = 1; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; public static final int BIT_RATE_AUDIO = 128000; public static final int SAMPLES_PER_FRAME = 1024; // AAC public static final int FRAMES_PER_BUFFER = 24; public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; public static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; public static final int TIMEOUT_USEC = 10000; private final MediaMuxer muxer; private final EncodingThread encodingThread; private final Object sync = new Object(); private boolean started = false; private boolean running = false; private volatile boolean record = false; private volatile long time = 0; private volatile long timeOrigin = 0; public AudioRecorder(final MediaMuxer muxer) { this.muxer = muxer; this.encodingThread = new EncodingThread(); } public void updateTime(final long time) { this.time = time; this.timeOrigin = System.nanoTime(); } public void record(final boolean record) { this.record = record; } public void start() { synchronized (this.sync) { if (this.started) { throw new IllegalStateException("Already started"); } this.started = true; this.running = true; synchronized (this.encodingThread.sync) { this.encodingThread.start(); try { this.encodingThread.sync.wait(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } } public void stop() { synchronized (this.sync) { if (!this.started) { throw new IllegalStateException("Not started"); } else if (!this.running) { throw new IllegalStateException("Already finished"); } this.running = false; this.encodingThread.finish(); } } private class EncodingThread extends Thread { private final Object sync = new Object(); private final MediaCodec encoder; private volatile boolean running = true; private int iBufferSize; private long timeLast = 0; public EncodingThread() { final int iMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); this.iBufferSize = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER; if (this.iBufferSize < iMinBufferSize) { this.iBufferSize = ((iMinBufferSize / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2; } final MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, CHANNEL_COUNT); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE_AUDIO); MediaCodec newEncoder = null; try { newEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.encoder = newEncoder; this.encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); this.encoder.start(); } @Override public void run() { final byte[] recordedBytes = new byte[SAMPLES_PER_FRAME]; final AudioRecord audioRecorder = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, this.iBufferSize); audioRecorder.startRecording(); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int track = 0; ByteBuffer[] buffersInput = null; ByteBuffer[] buffersOutput = this.encoder.getOutputBuffers(); ByteBuffer encodedData; while (this.running) { final int resultRead = audioRecorder.read(recordedBytes, 0, SAMPLES_PER_FRAME); if (resultRead == AudioRecord.ERROR_BAD_VALUE || resultRead == AudioRecord.ERROR_INVALID_OPERATION) { break; } if (AudioRecorder.this.record) { try { if (buffersInput == null) buffersInput = this.encoder.getInputBuffers(); int inputBufferIndex = this.encoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { final ByteBuffer inputBuffer = buffersInput[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(recordedBytes); this.timeLast = Math .max(this.timeLast + 1, (AudioRecorder.this.time + (System.nanoTime() - AudioRecorder.this.timeOrigin)) / 1000); this.encoder.queueInputBuffer(inputBufferIndex, 0, recordedBytes.length, this.timeLast, 0); } } catch (final Throwable t) { Log.e(TAG, "sendFrameToAudioEncoder exception"); t.printStackTrace(); break; } } final int encoderStatus = this.encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // not expected for an encoder buffersOutput = this.encoder.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { final MediaFormat newFormat = this.encoder.getOutputFormat(); track = AudioRecorder.this.muxer.addTrack(newFormat); synchronized (this.sync) { sync.notify(); } } else if (encoderStatus < 0) { Log.w(TAG, "Unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); } else { encodedData = buffersOutput[encoderStatus]; if (encodedData == null) { throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); } if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // The codec config data was pulled out and fed // to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore // it. bufferInfo.size = 0; } if (bufferInfo.size != 0) { // adjust the ByteBuffer values to match // BufferInfo (not needed?) encodedData.position(bufferInfo.offset); encodedData.limit(bufferInfo.offset + bufferInfo.size); if (AudioRecorder.this.record) { synchronized (AudioRecorder.this.muxer) { AudioRecorder.this.muxer.writeSampleData(track, encodedData, bufferInfo); } } } this.encoder.releaseOutputBuffer(encoderStatus, false); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { Log.w(TAG, "AudioRecorder: EOS reached."); break; } } } audioRecorder.stop(); audioRecorder.release(); this.encoder.stop(); this.encoder.release(); } public void finish() { this.running = false; } } }