diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index e170263d74..1a24c9ab56 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -41,5 +41,6 @@ android { dependencies { compile project(':library') + androidTestCompile project(':testutils') } diff --git a/extensions/flac/src/androidTest/assets/bear.flac b/extensions/flac/src/androidTest/assets/bear.flac new file mode 100644 index 0000000000..d9016a592b Binary files /dev/null and b/extensions/flac/src/androidTest/assets/bear.flac differ diff --git a/extensions/flac/src/androidTest/assets/bear.flac.dump b/extensions/flac/src/androidTest/assets/bear.flac.dump new file mode 100644 index 0000000000..ab88464f02 --- /dev/null +++ b/extensions/flac/src/androidTest/assets/bear.flac.dump @@ -0,0 +1,162 @@ +seekMap: + isSeekable = true + duration = 2741000 + getPosition(0) = 8304 +numberOfTracks = 1 +track 0: + format: + bitrate = 768000 + id = null + containerMimeType = null + sampleMimeType = audio/raw + maxInputSize = -1 + requiresSecureDecryption = false + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = -1 + pixelWidthHeightRatio = -1.0 + channelCount = 2 + sampleRate = 48000 + pcmEncoding = 2 + encoderDelay = -1 + encoderPadding = -1 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + sample count = 33 + sample 0: + time = 0 + flags = 1 + data = length 16384, hash 61D2C5C2 + sample 1: + time = 85333 + flags = 1 + data = length 16384, hash E6D7F214 + sample 2: + time = 170666 + flags = 1 + data = length 16384, hash 59BF0D5D + sample 3: + time = 256000 + flags = 1 + data = length 16384, hash 3625F468 + sample 4: + time = 341333 + flags = 1 + data = length 16384, hash F66A323 + sample 5: + time = 426666 + flags = 1 + data = length 16384, hash CDBAE629 + sample 6: + time = 512000 + flags = 1 + data = length 16384, hash 536F3A91 + sample 7: + time = 597333 + flags = 1 + data = length 16384, hash D4F35C9C + sample 8: + time = 682666 + flags = 1 + data = length 16384, hash EE04CEBF + sample 9: + time = 768000 + flags = 1 + data = length 16384, hash 647E2A67 + sample 10: + time = 853333 + flags = 1 + data = length 16384, hash 31583F2C + sample 11: + time = 938666 + flags = 1 + data = length 16384, hash E433A93D + sample 12: + time = 1024000 + flags = 1 + data = length 16384, hash 5E1C7051 + sample 13: + time = 1109333 + flags = 1 + data = length 16384, hash 43E6E358 + sample 14: + time = 1194666 + flags = 1 + data = length 16384, hash 5DC1B256 + sample 15: + time = 1280000 + flags = 1 + data = length 16384, hash 3D9D95CF + sample 16: + time = 1365333 + flags = 1 + data = length 16384, hash 2A5BD2C0 + sample 17: + time = 1450666 + flags = 1 + data = length 16384, hash 93E25061 + sample 18: + time = 1536000 + flags = 1 + data = length 16384, hash B81793D8 + sample 19: + time = 1621333 + flags = 1 + data = length 16384, hash 1A3BD49F + sample 20: + time = 1706666 + flags = 1 + data = length 16384, hash FB672FF1 + sample 21: + time = 1792000 + flags = 1 + data = length 16384, hash 48AB8B45 + sample 22: + time = 1877333 + flags = 1 + data = length 16384, hash 13C9640A + sample 23: + time = 1962666 + flags = 1 + data = length 16384, hash 499E4A0B + sample 24: + time = 2048000 + flags = 1 + data = length 16384, hash F9A783E6 + sample 25: + time = 2133333 + flags = 1 + data = length 16384, hash D2B77598 + sample 26: + time = 2218666 + flags = 1 + data = length 16384, hash CE5B826C + sample 27: + time = 2304000 + flags = 1 + data = length 16384, hash E99EE956 + sample 28: + time = 2389333 + flags = 1 + data = length 16384, hash F2DB1486 + sample 29: + time = 2474666 + flags = 1 + data = length 16384, hash 1636EAB + sample 30: + time = 2560000 + flags = 1 + data = length 16384, hash 23457C08 + sample 31: + time = 2645333 + flags = 1 + data = length 16384, hash 30EB8381 + sample 32: + time = 2730666 + flags = 1 + data = length 1984, hash 59CFDE1B +tracksEnded = true diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacExtractorTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacExtractorTest.java new file mode 100644 index 0000000000..8706affd94 --- /dev/null +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacExtractorTest.java @@ -0,0 +1,36 @@ +/* + * 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.exoplayer.ext.flac; + +import com.google.android.exoplayer.extractor.Extractor; +import com.google.android.exoplayer.testutil.TestUtil; + +import android.test.InstrumentationTestCase; + +/** + * Unit test for {@link FlacExtractor}. + */ +public class FlacExtractorTest extends InstrumentationTestCase { + + public void testSample() throws Exception { + TestUtil.assertOutput(new TestUtil.ExtractorFactory() { + @Override + public Extractor create() { + return new FlacExtractor(); + } + }, "bear.flac", getInstrumentation()); + } +} diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java index 1887cef26c..22c53e27e8 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java @@ -77,9 +77,16 @@ public final class FlacExtractor implements Extractor { decoder.setData(input); if (!metadataParsed) { - final FlacStreamInfo streamInfo = decoder.decodeMetadata(); - if (streamInfo == null) { - throw new IOException("Metadata decoding failed"); + final FlacStreamInfo streamInfo; + try { + streamInfo = decoder.decodeMetadata(); + if (streamInfo == null) { + throw new IOException("Metadata decoding failed"); + } + } catch (IOException e){ + decoder.reset(0); + input.setRetryPosition(0, e); + throw e; // never executes } metadataParsed = true; @@ -114,7 +121,17 @@ public final class FlacExtractor implements Extractor { } outputBuffer.reset(); - int size = decoder.decodeSample(outputByteBuffer); + long lastDecodePosition = decoder.getDecodePosition(); + int size; + try { + size = decoder.decodeSample(outputByteBuffer); + } catch (IOException e){ + if (lastDecodePosition >= 0) { + decoder.reset(lastDecodePosition); + input.setRetryPosition(lastDecodePosition, e); + } + throw e; + } if (size <= 0) { return RESULT_END_OF_INPUT; } @@ -128,7 +145,10 @@ public final class FlacExtractor implements Extractor { @Override public void seek(long position) { - decoder.flush(); + if (position == 0) { + metadataParsed = false; + } + decoder.reset(position); } @Override diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacJni.java b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacJni.java index 2d5b299b37..e661fb6f4c 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacJni.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacJni.java @@ -140,6 +140,13 @@ import java.nio.ByteBuffer; : flacDecodeToArray(nativeDecoderContext, output.array()); } + /** + * @return 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); } @@ -156,10 +163,23 @@ import java.nio.ByteBuffer; 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); } @@ -185,12 +205,18 @@ import java.nio.ByteBuffer; 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); } diff --git a/extensions/flac/src/main/jni/flac_jni.cc b/extensions/flac/src/main/jni/flac_jni.cc index 7b8cdf5bc9..c81f9843e3 100644 --- a/extensions/flac/src/main/jni/flac_jni.cc +++ b/extensions/flac/src/main/jni/flac_jni.cc @@ -69,19 +69,31 @@ class JavaDataSource : public DataSource { struct Context { JavaDataSource *source; FLACParser *parser; + + Context() { + source = new JavaDataSource(); + parser = new FLACParser(source); + } + + ~Context() { + delete parser; + delete source; + } }; FUNC(jlong, flacInit) { Context *context = new Context; - context->source = new JavaDataSource(); - context->parser = new FLACParser(context->source); + if (!context->parser->init()) { + delete context; + return 0; + } return reinterpret_cast(context); } FUNC(jobject, flacDecodeMetadata, jlong jContext) { Context *context = reinterpret_cast(jContext); context->source->setFlacJni(env, thiz); - if (!context->parser->init()) { + if (!context->parser->decodeMetadata()) { return NULL; } @@ -118,6 +130,11 @@ FUNC(jint, flacDecodeToArray, jlong jContext, jbyteArray jOutputArray) { return count; } +FUNC(jlong, flacGetDecodePosition, jlong jContext) { + Context *context = reinterpret_cast(jContext); + return context->parser->getDecodePosition(); +} + FUNC(jlong, flacGetLastTimestamp, jlong jContext) { Context *context = reinterpret_cast(jContext); return context->parser->getLastTimestamp(); @@ -128,14 +145,23 @@ FUNC(jlong, flacGetSeekPosition, jlong jContext, jlong timeUs) { return context->parser->getSeekPosition(timeUs); } +FUNC(jstring, flacGetStateString, jlong jContext) { + Context *context = reinterpret_cast(jContext); + const char *str = context->parser->getDecoderStateString(); + return env->NewStringUTF(str); +} + FUNC(void, flacFlush, jlong jContext) { Context *context = reinterpret_cast(jContext); context->parser->flush(); } +FUNC(void, flacReset, jlong jContext, jlong newPosition) { + Context *context = reinterpret_cast(jContext); + context->parser->reset(newPosition); +} + FUNC(void, flacRelease, jlong jContext) { Context *context = reinterpret_cast(jContext); - delete context->parser; - delete context->source; delete context; } diff --git a/extensions/flac/src/main/jni/flac_parser.cc b/extensions/flac/src/main/jni/flac_parser.cc index 329288169a..7d22c7fe79 100644 --- a/extensions/flac/src/main/jni/flac_parser.cc +++ b/extensions/flac/src/main/jni/flac_parser.cc @@ -316,9 +316,13 @@ bool FLACParser::init() { ALOGE("init_stream failed %d", initStatus); return false; } + return true; +} + +bool FLACParser::decodeMetadata() { // parse all metadata if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) { - ALOGE("end_of_metadata failed"); + ALOGE("metadata decoding failed"); return false; } // store first frame offset @@ -389,14 +393,14 @@ size_t FLACParser::readBuffer(void *output, size_t output_size) { if (!FLAC__stream_decoder_process_single(mDecoder)) { ALOGE("FLACParser::readBuffer process_single failed. Status: %s", - FLAC__stream_decoder_get_resolved_state_string(mDecoder)); + getDecoderStateString()); return -1; } if (!mWriteCompleted) { if (FLAC__stream_decoder_get_state(mDecoder) != FLAC__STREAM_DECODER_END_OF_STREAM) { ALOGE("FLACParser::readBuffer write did not complete. Status: %s", - FLAC__stream_decoder_get_resolved_state_string(mDecoder)); + getDecoderStateString()); } return -1; } diff --git a/extensions/flac/src/main/jni/include/flac_parser.h b/extensions/flac/src/main/jni/include/flac_parser.h index 22c17f7cff..8c302adb36 100644 --- a/extensions/flac/src/main/jni/include/flac_parser.h +++ b/extensions/flac/src/main/jni/include/flac_parser.h @@ -48,16 +48,41 @@ class FLACParser { return (1000000LL * mWriteHeader.number.sample_number) / getSampleRate(); } + bool decodeMetadata(); size_t readBuffer(void *output, size_t output_size); int64_t getSeekPosition(int64_t timeUs); void flush() { + reset(mCurrentPos); + } + + void reset(int64_t newPosition) { if (mDecoder != NULL) { - FLAC__stream_decoder_flush(mDecoder); + mCurrentPos = newPosition; + mEOF = false; + if (newPosition == 0) { + mStreamInfoValid = false; + FLAC__stream_decoder_reset(mDecoder); + } else { + FLAC__stream_decoder_flush(mDecoder); + } } } + int64_t getDecodePosition() { + uint64_t position; + if (mDecoder != NULL + && FLAC__stream_decoder_get_decode_position(mDecoder, &position)) { + return position; + } + return -1; + } + + const char *getDecoderStateString() { + return FLAC__stream_decoder_get_resolved_state_string(mDecoder); + } + private: DataSource *mDataSource;