mirror of
https://github.com/androidx/media.git
synced 2025-05-15 03:29:53 +08:00
FlacExtractorTest
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=124357946
This commit is contained in:
parent
544d88ca56
commit
4b3c72c057
@ -41,5 +41,6 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':library')
|
compile project(':library')
|
||||||
|
androidTestCompile project(':testutils')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
extensions/flac/src/androidTest/assets/bear.flac
Normal file
BIN
extensions/flac/src/androidTest/assets/bear.flac
Normal file
Binary file not shown.
162
extensions/flac/src/androidTest/assets/bear.flac.dump
Normal file
162
extensions/flac/src/androidTest/assets/bear.flac.dump
Normal file
@ -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
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -77,9 +77,16 @@ public final class FlacExtractor implements Extractor {
|
|||||||
decoder.setData(input);
|
decoder.setData(input);
|
||||||
|
|
||||||
if (!metadataParsed) {
|
if (!metadataParsed) {
|
||||||
final FlacStreamInfo streamInfo = decoder.decodeMetadata();
|
final FlacStreamInfo streamInfo;
|
||||||
if (streamInfo == null) {
|
try {
|
||||||
throw new IOException("Metadata decoding failed");
|
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;
|
metadataParsed = true;
|
||||||
|
|
||||||
@ -114,7 +121,17 @@ public final class FlacExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outputBuffer.reset();
|
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) {
|
if (size <= 0) {
|
||||||
return RESULT_END_OF_INPUT;
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
@ -128,7 +145,10 @@ public final class FlacExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(long position) {
|
public void seek(long position) {
|
||||||
decoder.flush();
|
if (position == 0) {
|
||||||
|
metadataParsed = false;
|
||||||
|
}
|
||||||
|
decoder.reset(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,6 +140,13 @@ import java.nio.ByteBuffer;
|
|||||||
: flacDecodeToArray(nativeDecoderContext, output.array());
|
: 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() {
|
public long getLastSampleTimestamp() {
|
||||||
return flacGetLastTimestamp(nativeDecoderContext);
|
return flacGetLastTimestamp(nativeDecoderContext);
|
||||||
}
|
}
|
||||||
@ -156,10 +163,23 @@ import java.nio.ByteBuffer;
|
|||||||
return flacGetSeekPosition(nativeDecoderContext, timeUs);
|
return flacGetSeekPosition(nativeDecoderContext, timeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getStateString() {
|
||||||
|
return flacGetStateString(nativeDecoderContext);
|
||||||
|
}
|
||||||
|
|
||||||
public void flush() {
|
public void flush() {
|
||||||
flacFlush(nativeDecoderContext);
|
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() {
|
public void release() {
|
||||||
flacRelease(nativeDecoderContext);
|
flacRelease(nativeDecoderContext);
|
||||||
}
|
}
|
||||||
@ -185,12 +205,18 @@ import java.nio.ByteBuffer;
|
|||||||
private native int flacDecodeToArray(long context, byte[] outputArray)
|
private native int flacDecodeToArray(long context, byte[] outputArray)
|
||||||
throws IOException, InterruptedException;
|
throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
private native long flacGetDecodePosition(long context);
|
||||||
|
|
||||||
private native long flacGetLastTimestamp(long context);
|
private native long flacGetLastTimestamp(long context);
|
||||||
|
|
||||||
private native long flacGetSeekPosition(long context, long timeUs);
|
private native long flacGetSeekPosition(long context, long timeUs);
|
||||||
|
|
||||||
|
private native String flacGetStateString(long context);
|
||||||
|
|
||||||
private native void flacFlush(long context);
|
private native void flacFlush(long context);
|
||||||
|
|
||||||
|
private native void flacReset(long context, long newPosition);
|
||||||
|
|
||||||
private native void flacRelease(long context);
|
private native void flacRelease(long context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -69,19 +69,31 @@ class JavaDataSource : public DataSource {
|
|||||||
struct Context {
|
struct Context {
|
||||||
JavaDataSource *source;
|
JavaDataSource *source;
|
||||||
FLACParser *parser;
|
FLACParser *parser;
|
||||||
|
|
||||||
|
Context() {
|
||||||
|
source = new JavaDataSource();
|
||||||
|
parser = new FLACParser(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Context() {
|
||||||
|
delete parser;
|
||||||
|
delete source;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FUNC(jlong, flacInit) {
|
FUNC(jlong, flacInit) {
|
||||||
Context *context = new Context;
|
Context *context = new Context;
|
||||||
context->source = new JavaDataSource();
|
if (!context->parser->init()) {
|
||||||
context->parser = new FLACParser(context->source);
|
delete context;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return reinterpret_cast<intptr_t>(context);
|
return reinterpret_cast<intptr_t>(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNC(jobject, flacDecodeMetadata, jlong jContext) {
|
FUNC(jobject, flacDecodeMetadata, jlong jContext) {
|
||||||
Context *context = reinterpret_cast<Context *>(jContext);
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
context->source->setFlacJni(env, thiz);
|
context->source->setFlacJni(env, thiz);
|
||||||
if (!context->parser->init()) {
|
if (!context->parser->decodeMetadata()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +130,11 @@ FUNC(jint, flacDecodeToArray, jlong jContext, jbyteArray jOutputArray) {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FUNC(jlong, flacGetDecodePosition, jlong jContext) {
|
||||||
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
|
return context->parser->getDecodePosition();
|
||||||
|
}
|
||||||
|
|
||||||
FUNC(jlong, flacGetLastTimestamp, jlong jContext) {
|
FUNC(jlong, flacGetLastTimestamp, jlong jContext) {
|
||||||
Context *context = reinterpret_cast<Context *>(jContext);
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
return context->parser->getLastTimestamp();
|
return context->parser->getLastTimestamp();
|
||||||
@ -128,14 +145,23 @@ FUNC(jlong, flacGetSeekPosition, jlong jContext, jlong timeUs) {
|
|||||||
return context->parser->getSeekPosition(timeUs);
|
return context->parser->getSeekPosition(timeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FUNC(jstring, flacGetStateString, jlong jContext) {
|
||||||
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
|
const char *str = context->parser->getDecoderStateString();
|
||||||
|
return env->NewStringUTF(str);
|
||||||
|
}
|
||||||
|
|
||||||
FUNC(void, flacFlush, jlong jContext) {
|
FUNC(void, flacFlush, jlong jContext) {
|
||||||
Context *context = reinterpret_cast<Context *>(jContext);
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
context->parser->flush();
|
context->parser->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FUNC(void, flacReset, jlong jContext, jlong newPosition) {
|
||||||
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
|
context->parser->reset(newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
FUNC(void, flacRelease, jlong jContext) {
|
FUNC(void, flacRelease, jlong jContext) {
|
||||||
Context *context = reinterpret_cast<Context *>(jContext);
|
Context *context = reinterpret_cast<Context *>(jContext);
|
||||||
delete context->parser;
|
|
||||||
delete context->source;
|
|
||||||
delete context;
|
delete context;
|
||||||
}
|
}
|
||||||
|
@ -316,9 +316,13 @@ bool FLACParser::init() {
|
|||||||
ALOGE("init_stream failed %d", initStatus);
|
ALOGE("init_stream failed %d", initStatus);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FLACParser::decodeMetadata() {
|
||||||
// parse all metadata
|
// parse all metadata
|
||||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) {
|
if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) {
|
||||||
ALOGE("end_of_metadata failed");
|
ALOGE("metadata decoding failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// store first frame offset
|
// 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)) {
|
if (!FLAC__stream_decoder_process_single(mDecoder)) {
|
||||||
ALOGE("FLACParser::readBuffer process_single failed. Status: %s",
|
ALOGE("FLACParser::readBuffer process_single failed. Status: %s",
|
||||||
FLAC__stream_decoder_get_resolved_state_string(mDecoder));
|
getDecoderStateString());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!mWriteCompleted) {
|
if (!mWriteCompleted) {
|
||||||
if (FLAC__stream_decoder_get_state(mDecoder) !=
|
if (FLAC__stream_decoder_get_state(mDecoder) !=
|
||||||
FLAC__STREAM_DECODER_END_OF_STREAM) {
|
FLAC__STREAM_DECODER_END_OF_STREAM) {
|
||||||
ALOGE("FLACParser::readBuffer write did not complete. Status: %s",
|
ALOGE("FLACParser::readBuffer write did not complete. Status: %s",
|
||||||
FLAC__stream_decoder_get_resolved_state_string(mDecoder));
|
getDecoderStateString());
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -48,16 +48,41 @@ class FLACParser {
|
|||||||
return (1000000LL * mWriteHeader.number.sample_number) / getSampleRate();
|
return (1000000LL * mWriteHeader.number.sample_number) / getSampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool decodeMetadata();
|
||||||
size_t readBuffer(void *output, size_t output_size);
|
size_t readBuffer(void *output, size_t output_size);
|
||||||
|
|
||||||
int64_t getSeekPosition(int64_t timeUs);
|
int64_t getSeekPosition(int64_t timeUs);
|
||||||
|
|
||||||
void flush() {
|
void flush() {
|
||||||
|
reset(mCurrentPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(int64_t newPosition) {
|
||||||
if (mDecoder != NULL) {
|
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:
|
private:
|
||||||
DataSource *mDataSource;
|
DataSource *mDataSource;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user