/* * Copyright (C) 2016 The Android Open Source Project * * 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.google.android.exoplayer2.ext.flac; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.FlacStreamInfo; import java.io.IOException; import java.nio.ByteBuffer; /** * JNI wrapper for the libflac Flac decoder. */ /* package */ final class FlacDecoderJni { private static final int TEMP_BUFFER_SIZE = 8192; // The same buffer size which libflac has private final long nativeDecoderContext; private ByteBuffer byteBufferData; private ExtractorInput extractorInput; private boolean endOfExtractorInput; private byte[] tempBuffer; public FlacDecoderJni() throws FlacDecoderException { if (!FlacLibrary.isAvailable()) { throw new FlacDecoderException("Failed to load decoder native libraries."); } nativeDecoderContext = flacInit(); if (nativeDecoderContext == 0) { throw new FlacDecoderException("Failed to initialize decoder"); } } /** * Sets data to be parsed by libflac. * @param byteBufferData Source {@link ByteBuffer} */ public void setData(ByteBuffer byteBufferData) { this.byteBufferData = byteBufferData; this.extractorInput = null; this.tempBuffer = null; } /** * Sets data to be parsed by libflac. * @param extractorInput Source {@link ExtractorInput} */ public void setData(ExtractorInput extractorInput) { this.byteBufferData = null; this.extractorInput = extractorInput; if (tempBuffer == null) { this.tempBuffer = new byte[TEMP_BUFFER_SIZE]; } endOfExtractorInput = false; } public boolean isEndOfData() { if (byteBufferData != null) { return byteBufferData.remaining() == 0; } else if (extractorInput != null) { return endOfExtractorInput; } return true; } /** * Reads up to {@code length} bytes from the data source. * <p> * This method blocks until at least one byte of data can be read, the end of the input is * detected or an exception is thrown. * <p> * This method is called from the native code. * * @param target A target {@link ByteBuffer} into which data should be written. * @return Returns the number of bytes read, or -1 on failure. It's not an error if this returns * zero; it just means all the data read from the source. */ public int read(ByteBuffer target) throws IOException, InterruptedException { int byteCount = target.remaining(); if (byteBufferData != null) { byteCount = Math.min(byteCount, byteBufferData.remaining()); int originalLimit = byteBufferData.limit(); byteBufferData.limit(byteBufferData.position() + byteCount); target.put(byteBufferData); byteBufferData.limit(originalLimit); } else if (extractorInput != null) { byteCount = Math.min(byteCount, TEMP_BUFFER_SIZE); int read = readFromExtractorInput(0, byteCount); if (read < 4) { // Reading less than 4 bytes, most of the time, happens because of getting the bytes left in // the buffer of the input. Do another read to reduce the number of calls to this method // from the native code. read += readFromExtractorInput(read, byteCount - read); } byteCount = read; target.put(tempBuffer, 0, byteCount); } else { return -1; } return byteCount; } public FlacStreamInfo decodeMetadata() throws IOException, InterruptedException { return flacDecodeMetadata(nativeDecoderContext); } public int decodeSample(ByteBuffer output) throws IOException, InterruptedException { return output.isDirect() ? flacDecodeToBuffer(nativeDecoderContext, output) : flacDecodeToArray(nativeDecoderContext, output.array()); } /** * Returns the position of the next data to be decoded, or -1 in case of error. */ public long getDecodePosition() { return flacGetDecodePosition(nativeDecoderContext); } public long getLastSampleTimestamp() { return flacGetLastTimestamp(nativeDecoderContext); } /** * Maps a seek position in microseconds to a corresponding position (byte offset) in the flac * stream. * * @param timeUs A seek position in microseconds. * @return The corresponding position (byte offset) in the flac stream or -1 if the stream doesn't * have a seek table. */ public long getSeekPosition(long timeUs) { return flacGetSeekPosition(nativeDecoderContext, timeUs); } public String getStateString() { return flacGetStateString(nativeDecoderContext); } public void flush() { flacFlush(nativeDecoderContext); } /** * Resets internal state of the decoder and sets the stream position. * * @param newPosition Stream's new position. */ public void reset(long newPosition) { flacReset(nativeDecoderContext, newPosition); } public void release() { flacRelease(nativeDecoderContext); } private int readFromExtractorInput(int offset, int length) throws IOException, InterruptedException { int read = extractorInput.read(tempBuffer, offset, length); if (read == C.RESULT_END_OF_INPUT) { endOfExtractorInput = true; read = 0; } return read; } private native long flacInit(); private native FlacStreamInfo flacDecodeMetadata(long context) throws IOException, InterruptedException; private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer) throws IOException, InterruptedException; private native int flacDecodeToArray(long context, byte[] outputArray) throws IOException, InterruptedException; private native long flacGetDecodePosition(long context); private native long flacGetLastTimestamp(long context); private native long flacGetSeekPosition(long context, long timeUs); private native String flacGetStateString(long context); private native void flacFlush(long context); private native void flacReset(long context, long newPosition); private native void flacRelease(long context); }