• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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