1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.ext.flac; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.C; 20 import com.google.android.exoplayer2.ParserException; 21 import com.google.android.exoplayer2.extractor.ExtractorInput; 22 import com.google.android.exoplayer2.extractor.FlacStreamMetadata; 23 import com.google.android.exoplayer2.extractor.SeekMap; 24 import com.google.android.exoplayer2.extractor.SeekPoint; 25 import com.google.android.exoplayer2.util.Util; 26 import java.io.IOException; 27 import java.nio.ByteBuffer; 28 29 /** 30 * JNI wrapper for the libflac Flac decoder. 31 */ 32 /* package */ final class FlacDecoderJni { 33 34 /** Exception to be thrown if {@link #decodeSample(ByteBuffer)} fails to decode a frame. */ 35 public static final class FlacFrameDecodeException extends Exception { 36 37 public final int errorCode; 38 FlacFrameDecodeException(String message, int errorCode)39 public FlacFrameDecodeException(String message, int errorCode) { 40 super(message); 41 this.errorCode = errorCode; 42 } 43 } 44 45 private static final int TEMP_BUFFER_SIZE = 8192; // The same buffer size as libflac. 46 47 private final long nativeDecoderContext; 48 49 @Nullable private ByteBuffer byteBufferData; 50 @Nullable private ExtractorInput extractorInput; 51 @Nullable private byte[] tempBuffer; 52 private boolean endOfExtractorInput; 53 FlacDecoderJni()54 public FlacDecoderJni() throws FlacDecoderException { 55 if (!FlacLibrary.isAvailable()) { 56 throw new FlacDecoderException("Failed to load decoder native libraries."); 57 } 58 nativeDecoderContext = flacInit(); 59 if (nativeDecoderContext == 0) { 60 throw new FlacDecoderException("Failed to initialize decoder"); 61 } 62 } 63 64 /** 65 * Sets the data to be parsed. 66 * 67 * @param byteBufferData Source {@link ByteBuffer}. 68 */ setData(ByteBuffer byteBufferData)69 public void setData(ByteBuffer byteBufferData) { 70 this.byteBufferData = byteBufferData; 71 this.extractorInput = null; 72 } 73 74 /** 75 * Sets the data to be parsed. 76 * 77 * @param extractorInput Source {@link ExtractorInput}. 78 */ setData(ExtractorInput extractorInput)79 public void setData(ExtractorInput extractorInput) { 80 this.byteBufferData = null; 81 this.extractorInput = extractorInput; 82 endOfExtractorInput = false; 83 if (tempBuffer == null) { 84 tempBuffer = new byte[TEMP_BUFFER_SIZE]; 85 } 86 } 87 88 /** 89 * Returns whether the end of the data to be parsed has been reached, or true if no data was set. 90 */ isEndOfData()91 public boolean isEndOfData() { 92 if (byteBufferData != null) { 93 return byteBufferData.remaining() == 0; 94 } else if (extractorInput != null) { 95 return endOfExtractorInput; 96 } else { 97 return true; 98 } 99 } 100 101 /** Clears the data to be parsed. */ clearData()102 public void clearData() { 103 byteBufferData = null; 104 extractorInput = null; 105 } 106 107 /** 108 * Reads up to {@code length} bytes from the data source. 109 * 110 * <p>This method blocks until at least one byte of data can be read, the end of the input is 111 * detected or an exception is thrown. 112 * 113 * @param target A target {@link ByteBuffer} into which data should be written. 114 * @return Returns the number of bytes read, or -1 on failure. If all of the data has already been 115 * read from the source, then 0 is returned. 116 */ 117 @SuppressWarnings("unused") // Called from native code. read(ByteBuffer target)118 public int read(ByteBuffer target) throws IOException { 119 int byteCount = target.remaining(); 120 if (byteBufferData != null) { 121 byteCount = Math.min(byteCount, byteBufferData.remaining()); 122 int originalLimit = byteBufferData.limit(); 123 byteBufferData.limit(byteBufferData.position() + byteCount); 124 target.put(byteBufferData); 125 byteBufferData.limit(originalLimit); 126 } else if (extractorInput != null) { 127 ExtractorInput extractorInput = this.extractorInput; 128 byte[] tempBuffer = Util.castNonNull(this.tempBuffer); 129 byteCount = Math.min(byteCount, TEMP_BUFFER_SIZE); 130 int read = readFromExtractorInput(extractorInput, tempBuffer, /* offset= */ 0, byteCount); 131 if (read < 4) { 132 // Reading less than 4 bytes, most of the time, happens because of getting the bytes left in 133 // the buffer of the input. Do another read to reduce the number of calls to this method 134 // from the native code. 135 read += 136 readFromExtractorInput( 137 extractorInput, tempBuffer, read, /* length= */ byteCount - read); 138 } 139 byteCount = read; 140 target.put(tempBuffer, 0, byteCount); 141 } else { 142 return -1; 143 } 144 return byteCount; 145 } 146 147 /** Decodes and consumes the metadata from the FLAC stream. */ decodeStreamMetadata()148 public FlacStreamMetadata decodeStreamMetadata() throws IOException { 149 FlacStreamMetadata streamMetadata = flacDecodeMetadata(nativeDecoderContext); 150 if (streamMetadata == null) { 151 throw new ParserException("Failed to decode stream metadata"); 152 } 153 return streamMetadata; 154 } 155 156 /** 157 * Decodes and consumes the next frame from the FLAC stream into the given byte buffer. If any IO 158 * error occurs, resets the stream and input to the given {@code retryPosition}. 159 * 160 * @param output The byte buffer to hold the decoded frame. 161 * @param retryPosition If any error happens, the input will be rewound to {@code retryPosition}. 162 */ decodeSampleWithBacktrackPosition(ByteBuffer output, long retryPosition)163 public void decodeSampleWithBacktrackPosition(ByteBuffer output, long retryPosition) 164 throws IOException, FlacFrameDecodeException { 165 try { 166 decodeSample(output); 167 } catch (IOException e) { 168 if (retryPosition >= 0) { 169 reset(retryPosition); 170 if (extractorInput != null) { 171 extractorInput.setRetryPosition(retryPosition, e); 172 } 173 } 174 throw e; 175 } 176 } 177 178 /** Decodes and consumes the next sample from the FLAC stream into the given byte buffer. */ 179 @SuppressWarnings("ByteBufferBackingArray") decodeSample(ByteBuffer output)180 public void decodeSample(ByteBuffer output) throws IOException, FlacFrameDecodeException { 181 output.clear(); 182 int frameSize = 183 output.isDirect() 184 ? flacDecodeToBuffer(nativeDecoderContext, output) 185 : flacDecodeToArray(nativeDecoderContext, output.array()); 186 if (frameSize < 0) { 187 if (!isDecoderAtEndOfInput()) { 188 throw new FlacFrameDecodeException("Cannot decode FLAC frame", frameSize); 189 } 190 // The decoder has read to EOI. Return a 0-size frame to indicate the EOI. 191 output.limit(0); 192 } else { 193 output.limit(frameSize); 194 } 195 } 196 197 /** 198 * Returns the position of the next data to be decoded, or -1 in case of error. 199 */ getDecodePosition()200 public long getDecodePosition() { 201 return flacGetDecodePosition(nativeDecoderContext); 202 } 203 204 /** Returns the timestamp for the first sample in the last decoded frame. */ getLastFrameTimestamp()205 public long getLastFrameTimestamp() { 206 return flacGetLastFrameTimestamp(nativeDecoderContext); 207 } 208 209 /** Returns the first sample index of the last extracted frame. */ getLastFrameFirstSampleIndex()210 public long getLastFrameFirstSampleIndex() { 211 return flacGetLastFrameFirstSampleIndex(nativeDecoderContext); 212 } 213 214 /** Returns the first sample index of the frame to be extracted next. */ getNextFrameFirstSampleIndex()215 public long getNextFrameFirstSampleIndex() { 216 return flacGetNextFrameFirstSampleIndex(nativeDecoderContext); 217 } 218 219 /** 220 * Maps a seek position in microseconds to the corresponding {@link SeekMap.SeekPoints} in the 221 * stream. 222 * 223 * @param timeUs A seek position in microseconds. 224 * @return The corresponding {@link SeekMap.SeekPoints} obtained from the seek table, or {@code 225 * null} if the stream doesn't have a seek table. 226 */ 227 @Nullable getSeekPoints(long timeUs)228 public SeekMap.SeekPoints getSeekPoints(long timeUs) { 229 long[] seekPoints = new long[4]; 230 if (!flacGetSeekPoints(nativeDecoderContext, timeUs, seekPoints)) { 231 return null; 232 } 233 SeekPoint firstSeekPoint = new SeekPoint(seekPoints[0], seekPoints[1]); 234 SeekPoint secondSeekPoint = 235 seekPoints[2] == seekPoints[0] 236 ? firstSeekPoint 237 : new SeekPoint(seekPoints[2], seekPoints[3]); 238 return new SeekMap.SeekPoints(firstSeekPoint, secondSeekPoint); 239 } 240 getStateString()241 public String getStateString() { 242 return flacGetStateString(nativeDecoderContext); 243 } 244 245 /** Returns whether the decoder has read to the end of the input. */ isDecoderAtEndOfInput()246 public boolean isDecoderAtEndOfInput() { 247 return flacIsDecoderAtEndOfStream(nativeDecoderContext); 248 } 249 flush()250 public void flush() { 251 flacFlush(nativeDecoderContext); 252 } 253 254 /** 255 * Resets internal state of the decoder and sets the stream position. 256 * 257 * @param newPosition Stream's new position. 258 */ reset(long newPosition)259 public void reset(long newPosition) { 260 flacReset(nativeDecoderContext, newPosition); 261 } 262 release()263 public void release() { 264 flacRelease(nativeDecoderContext); 265 } 266 readFromExtractorInput( ExtractorInput extractorInput, byte[] tempBuffer, int offset, int length)267 private int readFromExtractorInput( 268 ExtractorInput extractorInput, byte[] tempBuffer, int offset, int length) throws IOException { 269 int read = extractorInput.read(tempBuffer, offset, length); 270 if (read == C.RESULT_END_OF_INPUT) { 271 endOfExtractorInput = true; 272 read = 0; 273 } 274 return read; 275 } 276 flacInit()277 private native long flacInit(); 278 flacDecodeMetadata(long context)279 private native FlacStreamMetadata flacDecodeMetadata(long context) throws IOException; 280 flacDecodeToBuffer(long context, ByteBuffer outputBuffer)281 private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer) throws IOException; 282 flacDecodeToArray(long context, byte[] outputArray)283 private native int flacDecodeToArray(long context, byte[] outputArray) throws IOException; 284 flacGetDecodePosition(long context)285 private native long flacGetDecodePosition(long context); 286 flacGetLastFrameTimestamp(long context)287 private native long flacGetLastFrameTimestamp(long context); 288 flacGetLastFrameFirstSampleIndex(long context)289 private native long flacGetLastFrameFirstSampleIndex(long context); 290 flacGetNextFrameFirstSampleIndex(long context)291 private native long flacGetNextFrameFirstSampleIndex(long context); 292 flacGetSeekPoints(long context, long timeUs, long[] outSeekPoints)293 private native boolean flacGetSeekPoints(long context, long timeUs, long[] outSeekPoints); 294 flacGetStateString(long context)295 private native String flacGetStateString(long context); 296 flacIsDecoderAtEndOfStream(long context)297 private native boolean flacIsDecoderAtEndOfStream(long context); 298 flacFlush(long context)299 private native void flacFlush(long context); 300 flacReset(long context, long newPosition)301 private native void flacReset(long context, long newPosition); 302 flacRelease(long context)303 private native void flacRelease(long context); 304 305 } 306