/** * Initializes the basic audio track to be able to playback. * * @param sampleRate The sample rate of the track * @param numChannels The number of channels available in the track. */ private void initDevice(int sampleRate, int numChannels) throws IOException { L.d(TAG, "initDevice called in state:" + state); lock.lock(); try { final int format = findFormatFromChannels(numChannels); final int minSize = AudioTrack.getMinBufferSize(sampleRate, format, AudioFormat.ENCODING_PCM_16BIT); if (minSize == AudioTrack.ERROR || minSize == AudioTrack.ERROR_BAD_VALUE) { L.e(TAG, "minSize=" + minSize); throw new IOException("getMinBufferSize returned " + minSize); } track = new AudioTrack( AudioManager.STREAM_MUSIC, sampleRate, format, AudioFormat.ENCODING_PCM_16BIT, minSize * 4, AudioTrack.MODE_STREAM); sonic = new Sonic(sampleRate, numChannels); } finally { lock.unlock(); } }
@Override public void setDataSource(String source) { L.d(TAG, "setDataSource: " + source); switch (state) { case IDLE: this.path = source; state = State.INITIALIZED; L.d(TAG, "State changed to: " + state); break; default: error("setDataSource"); break; } }
@Override public void prepare() throws IOException { L.v(TAG, "prepare called in state: " + state); switch (state) { case INITIALIZED: case STOPPED: initStream(); state = State.PREPARED; L.d(TAG, "State changed to: " + state); break; default: error("prepare"); } }
private void initStream() throws IOException, IllegalArgumentException { L.v(TAG, "initStream called in state=" + state); lock.lock(); try { extractor = new MediaExtractor(); if (path != null) { extractor.setDataSource(path); } else { error("initStream"); throw new IOException(); } int trackNum = 0; final MediaFormat oFormat = extractor.getTrackFormat(trackNum); if (!oFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE)) { error("initStream"); throw new IOException("No KEY_SAMPLE_RATE"); } int sampleRate = oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); if (!oFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) { error("initStream"); throw new IOException("No KEY_CHANNEL_COUNT"); } int channelCount = oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); if (!oFormat.containsKey(MediaFormat.KEY_MIME)) { error("initStream"); throw new IOException("No KEY_MIME"); } final String mime = oFormat.getString(MediaFormat.KEY_MIME); if (!oFormat.containsKey(MediaFormat.KEY_DURATION)) { error("initStream"); throw new IOException("No KEY_DURATION"); } duration = oFormat.getLong(MediaFormat.KEY_DURATION); L.v(TAG, "Sample rate: " + sampleRate); L.v(TAG, "Mime type: " + mime); initDevice(sampleRate, channelCount); extractor.selectTrack(trackNum); codec = MediaCodec.createDecoderByType(mime); codec.configure(oFormat, null, null, 0); } finally { lock.unlock(); } }
@Override public void pause() { L.v(TAG, "pause called"); switch (state) { case PLAYBACK_COMPLETED: state = State.PAUSED; L.d(TAG, "State changed to: " + state); stayAwake(false); break; case STARTED: case PAUSED: track.pause(); state = State.PAUSED; L.d(TAG, "State changed to: " + state); stayAwake(false); break; default: error("pause"); } }
@Override public void reset() { L.v(TAG, "reset called in state: " + state); stayAwake(false); lock.lock(); try { continuing = false; try { if (state != State.PLAYBACK_COMPLETED) { while (isDecoding) { synchronized (decoderLock) { decoderLock.notify(); decoderLock.wait(); } } } } catch (InterruptedException e) { L.e(TAG, "Interrupted in reset while waiting for decoder thread to stop.", e); } if (codec != null) { codec.release(); L.d(TAG, "releasing codec"); codec = null; } if (extractor != null) { extractor.release(); extractor = null; } if (track != null) { track.release(); track = null; } state = State.IDLE; L.d(TAG, "State changed to: " + state); } finally { lock.unlock(); } }
@Override public void start() { L.v(TAG, "start called in state:" + state); switch (state) { case PLAYBACK_COMPLETED: try { initStream(); } catch (IOException e) { e.printStackTrace(); error("start"); break; } case PREPARED: state = State.STARTED; L.d(TAG, "State changed to: " + state); continuing = true; track.play(); decode(); stayAwake(true); break; case STARTED: break; case PAUSED: state = State.STARTED; L.d(TAG, "State changed to: " + state + " with path=" + path); synchronized (decoderLock) { decoderLock.notify(); } track.play(); stayAwake(true); break; default: error("start"); break; } }
@Override public void run() { isDecoding = true; codec.start(); @SuppressWarnings("deprecation") ByteBuffer[] inputBuffers = codec.getInputBuffers(); @SuppressWarnings("deprecation") ByteBuffer[] outputBuffers = codec.getOutputBuffers(); boolean sawInputEOS = false; boolean sawOutputEOS = false; while (!sawInputEOS && !sawOutputEOS && continuing) { if (state == State.PAUSED) { try { synchronized (decoderLock) { decoderLock.wait(); } } catch (InterruptedException e) { // Purposely not doing anything here } continue; } if (sonic != null) { sonic.setSpeed(speed); sonic.setPitch(1); } int inputBufIndex = codec.dequeueInputBuffer(200); if (inputBufIndex >= 0) { ByteBuffer dstBuf = inputBuffers[inputBufIndex]; int sampleSize = extractor.readSampleData(dstBuf, 0); long presentationTimeUs = 0; if (sampleSize < 0) { sawInputEOS = true; sampleSize = 0; } else { presentationTimeUs = extractor.getSampleTime(); } codec.queueInputBuffer( inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); if (flushCodec) { codec.flush(); flushCodec = false; } if (!sawInputEOS) { extractor.advance(); } } final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); byte[] modifiedSamples = new byte[info.size]; int res; //noinspection deprecation do { res = codec.dequeueOutputBuffer(info, 200); if (res >= 0) { final byte[] chunk = new byte[info.size]; outputBuffers[res].get(chunk); outputBuffers[res].clear(); if (chunk.length > 0) { sonic.writeBytesToStream(chunk, chunk.length); } else { sonic.flushStream(); } int available = sonic.availableBytes(); if (available > 0) { if (modifiedSamples.length < available) { modifiedSamples = new byte[available]; } sonic.readBytesFromStream(modifiedSamples, available); track.write(modifiedSamples, 0, available); } codec.releaseOutputBuffer(res, false); if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { sawOutputEOS = true; } } else //noinspection deprecation if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { //noinspection deprecation outputBuffers = codec.getOutputBuffers(); } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { track.stop(); lock.lock(); try { track.release(); final MediaFormat oFormat = codec.getOutputFormat(); initDevice( oFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), oFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); //noinspection deprecation outputBuffers = codec.getOutputBuffers(); track.play(); } catch (IOException e) { e.printStackTrace(); } finally { lock.unlock(); } } } while (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); } codec.stop(); track.stop(); isDecoding = false; if (continuing && (sawInputEOS || sawOutputEOS)) { state = State.PLAYBACK_COMPLETED; L.d(TAG, "State changed to: " + state); Thread t = new Thread( new Runnable() { @Override public void run() { if (onCompletionListener != null) { onCompletionListener.onCompletion(); } stayAwake(false); } }); t.setDaemon(true); t.start(); } synchronized (decoderLock) { decoderLock.notifyAll(); } }
private void decode() { L.d(TAG, "decode called ins state=" + state); executor.execute(decoderRunnable); }
private void error(String methodName) { L.e(TAG, "Error in " + methodName + " at state=" + state); state = State.ERROR; stayAwake(false); }