Copy main/extensions/flac -> experimental/extensions/flac
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119544516
This commit is contained in:
parent
eb877f0cb7
commit
087cf9546f
65
extensions/flac/README.md
Normal file
65
extensions/flac/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# ExoPlayer Flac Extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The Flac Extension is a [TrackRenderer][] implementation that helps you bundle
|
||||
libFLAC (the Flac decoding library) into your app and use it along with
|
||||
ExoPlayer to play Flac audio on Android devices.
|
||||
|
||||
[TrackRenderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/TrackRenderer.html
|
||||
|
||||
## Build Instructions ##
|
||||
|
||||
* Checkout ExoPlayer along with Extensions:
|
||||
|
||||
```
|
||||
git clone https://github.com/google/ExoPlayer.git
|
||||
```
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
```
|
||||
cd "<path to exoplayer checkout>"
|
||||
EXOPLAYER_ROOT="$(pwd)"
|
||||
FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main"
|
||||
```
|
||||
|
||||
* Download the [Android NDK][] and set its location in an environment variable:
|
||||
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
```
|
||||
|
||||
* Download and extract flac-1.3.1 as "${FLAC_EXT_PATH}/jni/flac" folder:
|
||||
|
||||
```
|
||||
curl http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz | tar xJ && \
|
||||
mv flac-1.3.1 flac
|
||||
```
|
||||
|
||||
* Build the JNI native libraries from the command line:
|
||||
|
||||
```
|
||||
cd "${FLAC_EXT_PATH}"/jni && \
|
||||
${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
```
|
||||
|
||||
* In your project, you can add a dependency to the Flac Extension by using a
|
||||
rule like this:
|
||||
|
||||
```
|
||||
// in settings.gradle
|
||||
include ':..:ExoPlayer:library'
|
||||
include ':..:ExoPlayer:extension-flac'
|
||||
|
||||
// in build.gradle
|
||||
dependencies {
|
||||
compile project(':..:ExoPlayer:library')
|
||||
compile project(':..:ExoPlayer:extension-flac')
|
||||
}
|
||||
```
|
||||
|
||||
* Now, when you build your app, the Flac extension will be built and the native
|
||||
libraries will be packaged along with the APK.
|
45
extensions/flac/build.gradle
Normal file
45
extensions/flac/build.gradle
Normal file
@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 23
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'src/main/libs'
|
||||
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
}
|
||||
|
10
extensions/flac/src/main/.classpath
Normal file
10
extensions/flac/src/main/.classpath
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
97
extensions/flac/src/main/.project
Normal file
97
extensions/flac/src/main/.project
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-Flac</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>?children?</key>
|
||||
<value>?name?=outputEntries\|?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\||</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>?name?</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.append_environment</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildArguments</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildCommand</key>
|
||||
<value>ndk-build</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
|
||||
<value>clean</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.contents</key>
|
||||
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
|
||||
<value>false</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.stopOnError</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
22
extensions/flac/src/main/AndroidManifest.xml
Normal file
22
extensions/flac/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.ext.flac">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Flac decoder.
|
||||
*/
|
||||
/* package */ final class FlacDecoder extends
|
||||
SimpleDecoder<DecoderInputBuffer, FlacOutputBuffer, FlacDecoderException> {
|
||||
|
||||
private final int maxOutputBufferSize;
|
||||
private final FlacJni decoder;
|
||||
/**
|
||||
* Creates a Flac decoder.
|
||||
*
|
||||
* @param numInputBuffers The number of input buffers.
|
||||
* @param numOutputBuffers The number of output buffers.
|
||||
* @param initializationData Codec-specific initialization data.
|
||||
* @throws FlacDecoderException Thrown if an exception occurs when initializing the decoder.
|
||||
*/
|
||||
public FlacDecoder(int numInputBuffers, int numOutputBuffers, List<byte[]> initializationData)
|
||||
throws FlacDecoderException {
|
||||
super(new DecoderInputBuffer[numInputBuffers], new FlacOutputBuffer[numOutputBuffers]);
|
||||
if (initializationData.size() != 1) {
|
||||
throw new FlacDecoderException("Wrong number of initialization data");
|
||||
}
|
||||
|
||||
decoder = new FlacJni();
|
||||
|
||||
ByteBuffer metadata = ByteBuffer.wrap(initializationData.get(0));
|
||||
decoder.setData(metadata);
|
||||
FlacStreamInfo streamInfo = decoder.decodeMetadata();
|
||||
if (streamInfo == null) {
|
||||
throw new FlacDecoderException("Metadata decoding failed");
|
||||
}
|
||||
|
||||
setInitialInputBufferSize(streamInfo.maxFrameSize);
|
||||
maxOutputBufferSize = streamInfo.maxDecodedFrameSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderInputBuffer createInputBuffer() {
|
||||
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlacOutputBuffer createOutputBuffer() {
|
||||
return new FlacOutputBuffer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseOutputBuffer(FlacOutputBuffer buffer) {
|
||||
super.releaseOutputBuffer(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlacDecoderException decode(DecoderInputBuffer inputBuffer,
|
||||
FlacOutputBuffer outputBuffer) {
|
||||
ByteBuffer data = inputBuffer.data;
|
||||
outputBuffer.timestampUs = inputBuffer.timeUs;
|
||||
data.limit(data.position());
|
||||
data.position(data.position() - inputBuffer.size);
|
||||
outputBuffer.init(maxOutputBufferSize);
|
||||
decoder.setData(data);
|
||||
int result = decoder.decodeSample(outputBuffer.data);
|
||||
if (result < 0) {
|
||||
return new FlacDecoderException("Frame decoding failed");
|
||||
}
|
||||
outputBuffer.data.position(0);
|
||||
outputBuffer.data.limit(result);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
super.release();
|
||||
decoder.release();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Thrown when an Flac decoder error occurs.
|
||||
*/
|
||||
public final class FlacDecoderException extends Exception {
|
||||
|
||||
/* package */ FlacDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Facilitates the extraction of data from the FLAC container format.
|
||||
*/
|
||||
public final class FlacExtractor implements Extractor {
|
||||
/**
|
||||
* FLAC signature: first 4 is the signature word, second 4 is the sizeof STREAMINFO. 0x22 is the
|
||||
* mandatory STREAMINFO.
|
||||
*/
|
||||
private static final byte[] FLAC_SIGNATURE = {'f', 'L', 'a', 'C', 0, 0, 0, 0x22};
|
||||
|
||||
private ExtractorOutput output;
|
||||
private TrackOutput trackOutput;
|
||||
|
||||
private FlacJni decoder;
|
||||
|
||||
private boolean metadataParsed;
|
||||
|
||||
private ParsableByteArray outputBuffer;
|
||||
private ByteBuffer outputByteBuffer;
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.output = output;
|
||||
this.trackOutput = output.track(0);
|
||||
output.endTracks();
|
||||
|
||||
try {
|
||||
decoder = new FlacJni();
|
||||
} catch (FlacDecoderException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||
byte[] header = new byte[FLAC_SIGNATURE.length];
|
||||
input.peekFully(header, 0, FLAC_SIGNATURE.length);
|
||||
return Arrays.equals(header, FLAC_SIGNATURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final ExtractorInput input, PositionHolder seekPosition)
|
||||
throws IOException, InterruptedException {
|
||||
decoder.setData(input);
|
||||
|
||||
if (!metadataParsed) {
|
||||
final FlacStreamInfo streamInfo = decoder.decodeMetadata();
|
||||
if (streamInfo == null) {
|
||||
throw new IOException("Metadata decoding failed");
|
||||
}
|
||||
metadataParsed = true;
|
||||
|
||||
output.seekMap(new SeekMap() {
|
||||
final boolean isSeekable = decoder.getSeekPosition(0) != -1;
|
||||
final long durationUs = streamInfo.durationUs();
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return isSeekable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return isSeekable ? decoder.getSeekPosition(timeUs) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Format mediaFormat = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW,
|
||||
Format.NO_VALUE, streamInfo.bitRate(),
|
||||
streamInfo.channels, streamInfo.sampleRate, null, null);
|
||||
trackOutput.format(mediaFormat);
|
||||
|
||||
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
|
||||
outputByteBuffer = ByteBuffer.wrap(outputBuffer.data);
|
||||
}
|
||||
|
||||
outputBuffer.reset();
|
||||
int size = decoder.decodeSample(outputByteBuffer);
|
||||
if (size <= 0) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
}
|
||||
trackOutput.sampleData(outputBuffer, size);
|
||||
|
||||
trackOutput
|
||||
.sampleMetadata(decoder.getLastSampleTimestamp(), C.BUFFER_FLAG_KEY_FRAME, size, 0, null);
|
||||
|
||||
return decoder.isEndOfData() ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
decoder.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
decoder.release();
|
||||
decoder = null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.C;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* JNI wrapper for the libflac Flac decoder.
|
||||
*/
|
||||
/* package */ final class FlacJni {
|
||||
|
||||
/**
|
||||
* Whether the underlying libflac library is available.
|
||||
*/
|
||||
public static final boolean IS_AVAILABLE;
|
||||
static {
|
||||
boolean isAvailable;
|
||||
try {
|
||||
System.loadLibrary("flacJNI");
|
||||
isAvailable = true;
|
||||
} catch (UnsatisfiedLinkError exception) {
|
||||
isAvailable = false;
|
||||
}
|
||||
IS_AVAILABLE = isAvailable;
|
||||
}
|
||||
|
||||
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 FlacJni() throws FlacDecoderException {
|
||||
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() {
|
||||
return flacDecodeMetadata(nativeDecoderContext);
|
||||
}
|
||||
|
||||
public int decodeSample(ByteBuffer output) {
|
||||
return output.isDirect()
|
||||
? flacDecodeToBuffer(nativeDecoderContext, output)
|
||||
: flacDecodeToArray(nativeDecoderContext, output.array());
|
||||
}
|
||||
|
||||
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 void flush() {
|
||||
flacFlush(nativeDecoderContext);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer);
|
||||
|
||||
private native int flacDecodeToArray(long context, byte[] outputArray);
|
||||
|
||||
private native long flacGetLastTimestamp(long context);
|
||||
|
||||
private native long flacGetSeekPosition(long context, long timeUs);
|
||||
|
||||
private native void flacFlush(long context);
|
||||
|
||||
private native void flacRelease(long context);
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.util.extensions.OutputBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Buffer for {@link FlacDecoder} output.
|
||||
*/
|
||||
public final class FlacOutputBuffer extends OutputBuffer {
|
||||
|
||||
private final FlacDecoder owner;
|
||||
|
||||
public ByteBuffer data;
|
||||
|
||||
/* package */ FlacOutputBuffer(FlacDecoder owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/* package */ void init(int size) {
|
||||
if (data == null || data.capacity() < size) {
|
||||
data = ByteBuffer.allocateDirect(size);
|
||||
}
|
||||
data.position(0);
|
||||
data.limit(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
if (data != null) {
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
owner.releaseOutputBuffer(this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Holder for flac stream info.
|
||||
*/
|
||||
/* package */ final class FlacStreamInfo {
|
||||
public final int minBlockSize;
|
||||
public final int maxBlockSize;
|
||||
public final int minFrameSize;
|
||||
public final int maxFrameSize;
|
||||
public final int sampleRate;
|
||||
public final int channels;
|
||||
public final int bitsPerSample;
|
||||
public final long totalSamples;
|
||||
|
||||
public FlacStreamInfo(int minBlockSize, int maxBlockSize, int minFrameSize, int maxFrameSize,
|
||||
int sampleRate, int channels, int bitsPerSample, long totalSamples) {
|
||||
this.minBlockSize = minBlockSize;
|
||||
this.maxBlockSize = maxBlockSize;
|
||||
this.minFrameSize = minFrameSize;
|
||||
this.maxFrameSize = maxFrameSize;
|
||||
this.sampleRate = sampleRate;
|
||||
this.channels = channels;
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
this.totalSamples = totalSamples;
|
||||
}
|
||||
|
||||
public int maxDecodedFrameSize() {
|
||||
return maxBlockSize * channels * 2;
|
||||
}
|
||||
|
||||
public int bitRate() {
|
||||
return bitsPerSample * sampleRate;
|
||||
}
|
||||
|
||||
public long durationUs() {
|
||||
return (totalSamples * 1000000L) / sampleRate;
|
||||
}
|
||||
}
|
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* 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.CodecCounters;
|
||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.FormatHolder;
|
||||
import com.google.android.exoplayer.MediaClock;
|
||||
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.audio.AudioTrack;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Decodes and renders audio using the native Flac decoder.
|
||||
*/
|
||||
public final class LibflacAudioTrackRenderer extends SampleSourceTrackRenderer
|
||||
implements MediaClock {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link LibflacAudioTrackRenderer} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked when the {@link AudioTrack} fails to initialize.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
||||
|
||||
/**
|
||||
* Invoked when an {@link AudioTrack} write fails.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
||||
|
||||
/**
|
||||
* Invoked when decoding fails.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onDecoderError(FlacDecoderException e);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a message that can be passed to an instance of this class via
|
||||
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
|
||||
* should be a {@link Float} with 0 being silence and 1 being unity gain.
|
||||
*/
|
||||
public static final int MSG_SET_VOLUME = 1;
|
||||
|
||||
private static final int NUM_BUFFERS = 16;
|
||||
|
||||
public final CodecCounters codecCounters = new CodecCounters();
|
||||
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final FormatHolder formatHolder;
|
||||
|
||||
private Format format;
|
||||
private FlacDecoder decoder;
|
||||
private DecoderInputBuffer inputBuffer;
|
||||
private FlacOutputBuffer outputBuffer;
|
||||
|
||||
private long currentPositionUs;
|
||||
private boolean allowPositionDiscontinuity;
|
||||
private boolean inputStreamEnded;
|
||||
private boolean outputStreamEnded;
|
||||
private boolean sourceIsReady;
|
||||
|
||||
private final AudioTrack audioTrack;
|
||||
private int audioSessionId;
|
||||
|
||||
public LibflacAudioTrackRenderer() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
*/
|
||||
public LibflacAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
this.audioTrack = new AudioTrack();
|
||||
formatHolder = new FormatHolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the underlying libflac library is available.
|
||||
*/
|
||||
public static boolean isLibflacAvailable() {
|
||||
return FlacJni.IS_AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaClock getMediaClock() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int supportsFormat(Format format) {
|
||||
return MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
|
||||
? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
|
||||
throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
this.sourceIsReady = sourceIsReady;
|
||||
|
||||
// Try and read a format if we don't have one already.
|
||||
if (format == null && !readFormat()) {
|
||||
// We can't make progress without one.
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a decoder yet, we need to instantiate one.
|
||||
if (decoder == null) {
|
||||
// For flac, the format can contain only one entry in initializationData which is the flac
|
||||
// file header.
|
||||
List<byte[]> initializationData = format.initializationData;
|
||||
if (initializationData.size() < 1) {
|
||||
throw ExoPlaybackException.createForRenderer(
|
||||
new IllegalStateException("Missing initialization data"), getIndex());
|
||||
}
|
||||
try {
|
||||
decoder = new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, initializationData);
|
||||
} catch (FlacDecoderException e) {
|
||||
notifyDecoderError(e);
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
decoder.start();
|
||||
codecCounters.codecInitCount++;
|
||||
}
|
||||
|
||||
// Rendering loop.
|
||||
try {
|
||||
renderBuffer();
|
||||
while (feedInputBuffer()) {}
|
||||
} catch (AudioTrack.InitializationException e) {
|
||||
notifyAudioTrackInitializationError(e);
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
} catch (AudioTrack.WriteException e) {
|
||||
notifyAudioTrackWriteError(e);
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
} catch (FlacDecoderException e) {
|
||||
notifyDecoderError(e);
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
codecCounters.ensureUpdated();
|
||||
}
|
||||
|
||||
private void renderBuffer() throws FlacDecoderException, AudioTrack.InitializationException,
|
||||
AudioTrack.WriteException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputBuffer == null) {
|
||||
outputBuffer = decoder.dequeueOutputBuffer();
|
||||
if (outputBuffer == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputBuffer.isEndOfStream()) {
|
||||
outputStreamEnded = true;
|
||||
audioTrack.handleEndOfStream();
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audioTrack.isInitialized()) {
|
||||
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
|
||||
audioTrack.initialize(audioSessionId);
|
||||
} else {
|
||||
audioSessionId = audioTrack.initialize();
|
||||
}
|
||||
if (getState() == TrackRenderer.STATE_STARTED) {
|
||||
audioTrack.play();
|
||||
}
|
||||
}
|
||||
|
||||
int handleBufferResult;
|
||||
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.data.position(),
|
||||
outputBuffer.data.remaining(), outputBuffer.timestampUs);
|
||||
|
||||
// If we are out of sync, allow currentPositionUs to jump backwards.
|
||||
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
|
||||
allowPositionDiscontinuity = true;
|
||||
}
|
||||
|
||||
// Release the buffer if it was consumed.
|
||||
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
|
||||
codecCounters.renderedOutputBufferCount++;
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean feedInputBuffer() throws FlacDecoderException {
|
||||
if (inputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputBuffer == null) {
|
||||
inputBuffer = decoder.dequeueInputBuffer();
|
||||
if (inputBuffer == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int result = readSource(formatHolder, inputBuffer);
|
||||
if (result == TrackStream.NOTHING_READ) {
|
||||
return false;
|
||||
}
|
||||
if (result == TrackStream.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
return true;
|
||||
}
|
||||
if (inputBuffer.isEndOfStream()) {
|
||||
inputStreamEnded = true;
|
||||
}
|
||||
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
inputBuffer = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
if (outputBuffer != null) {
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
}
|
||||
decoder.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnded() {
|
||||
return outputStreamEnded && !audioTrack.hasPendingData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReady() {
|
||||
return audioTrack.hasPendingData()
|
||||
|| (format != null && (sourceIsReady || outputBuffer != null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPositionUs() {
|
||||
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
|
||||
if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
|
||||
currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs
|
||||
: Math.max(currentPositionUs, newCurrentPositionUs);
|
||||
allowPositionDiscontinuity = false;
|
||||
}
|
||||
return currentPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset(long positionUs) {
|
||||
audioTrack.reset();
|
||||
currentPositionUs = positionUs;
|
||||
allowPositionDiscontinuity = true;
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
sourceIsReady = false;
|
||||
if (decoder != null) {
|
||||
flushDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStarted() {
|
||||
audioTrack.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopped() {
|
||||
audioTrack.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisabled() {
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
format = null;
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
try {
|
||||
if (decoder != null) {
|
||||
decoder.release();
|
||||
decoder = null;
|
||||
codecCounters.codecReleaseCount++;
|
||||
}
|
||||
audioTrack.release();
|
||||
} finally {
|
||||
super.onDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readFormat() {
|
||||
int result = readSource(formatHolder, null);
|
||||
if (result == TrackStream.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
|
||||
if (messageType == MSG_SET_VOLUME) {
|
||||
audioTrack.setVolume((Float) message);
|
||||
} else {
|
||||
super.handleMessage(messageType, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onAudioTrackInitializationError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onAudioTrackWriteError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDecoderError(final FlacDecoderException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDecoderError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
38
extensions/flac/src/main/jni/Android.mk
Normal file
38
extensions/flac/src/main/jni/Android.mk
Normal file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
WORKING_DIR := $(call my-dir)
|
||||
|
||||
# build libflacJNI.so
|
||||
include $(CLEAR_VARS)
|
||||
include $(WORKING_DIR)/flac_sources.mk
|
||||
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
LOCAL_MODULE := libflacJNI
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/flac/include \
|
||||
$(LOCAL_PATH)/flac/src/libFLAC/include
|
||||
LOCAL_SRC_FILES := $(FLAC_SOURCES)
|
||||
|
||||
LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM
|
||||
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC
|
||||
LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions
|
||||
|
||||
LOCAL_LDLIBS := -llog -lz -lm
|
||||
include $(BUILD_SHARED_LIBRARY)
|
20
extensions/flac/src/main/jni/Application.mk
Normal file
20
extensions/flac/src/main/jni/Application.mk
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
APP_OPTIM := release
|
||||
APP_STL := gnustl_static
|
||||
APP_CPPFLAGS := -frtti
|
||||
APP_PLATFORM := android-9
|
141
extensions/flac/src/main/jni/flac_jni.cc
Normal file
141
extensions/flac/src/main/jni/flac_jni.cc
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "include/flac_parser.h"
|
||||
|
||||
#define LOG_TAG "FlacJniJNI"
|
||||
#define ALOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
|
||||
#define ALOGV(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
|
||||
|
||||
#define FUNC(RETURN_TYPE, NAME, ...) \
|
||||
extern "C" { \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_flac_FlacJni_##NAME( \
|
||||
JNIEnv *env, jobject thiz, ##__VA_ARGS__); \
|
||||
} \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_flac_FlacJni_##NAME( \
|
||||
JNIEnv *env, jobject thiz, ##__VA_ARGS__)
|
||||
|
||||
class JavaDataSource : public DataSource {
|
||||
public:
|
||||
void setFlacJni(JNIEnv *env, jobject flacJni) {
|
||||
this->env = env;
|
||||
this->flacJni = flacJni;
|
||||
if (mid == NULL) {
|
||||
jclass cls = env->GetObjectClass(flacJni);
|
||||
mid = env->GetMethodID(cls, "read", "(Ljava/nio/ByteBuffer;)I");
|
||||
env->DeleteLocalRef(cls);
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t readAt(off64_t offset, void *const data, size_t size) {
|
||||
jobject byteBuffer = env->NewDirectByteBuffer(data, size);
|
||||
int result = env->CallIntMethod(flacJni, mid, byteBuffer);
|
||||
if (env->ExceptionOccurred()) {
|
||||
result = -1;
|
||||
}
|
||||
env->DeleteLocalRef(byteBuffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jobject flacJni;
|
||||
jmethodID mid;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
JavaDataSource *source;
|
||||
FLACParser *parser;
|
||||
};
|
||||
|
||||
FUNC(jlong, flacInit) {
|
||||
Context *context = new Context;
|
||||
context->source = new JavaDataSource();
|
||||
context->parser = new FLACParser(context->source);
|
||||
return reinterpret_cast<intptr_t>(context);
|
||||
}
|
||||
|
||||
FUNC(jobject, flacDecodeMetadata, jlong jContext) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
context->source->setFlacJni(env, thiz);
|
||||
if (!context->parser->init()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const FLAC__StreamMetadata_StreamInfo &streamInfo =
|
||||
context->parser->getStreamInfo();
|
||||
|
||||
jclass cls = env->FindClass(
|
||||
"com/google/android/exoplayer/ext/flac/"
|
||||
"FlacStreamInfo");
|
||||
jmethodID constructor = env->GetMethodID(cls, "<init>", "(IIIIIIIJ)V");
|
||||
|
||||
return env->NewObject(cls, constructor, streamInfo.min_blocksize,
|
||||
streamInfo.max_blocksize, streamInfo.min_framesize,
|
||||
streamInfo.max_framesize, streamInfo.sample_rate,
|
||||
streamInfo.channels, streamInfo.bits_per_sample,
|
||||
streamInfo.total_samples);
|
||||
}
|
||||
|
||||
FUNC(jint, flacDecodeToBuffer, jlong jContext, jobject jOutputBuffer) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
context->source->setFlacJni(env, thiz);
|
||||
void *outputBuffer = env->GetDirectBufferAddress(jOutputBuffer);
|
||||
jint outputSize = env->GetDirectBufferCapacity(jOutputBuffer);
|
||||
return context->parser->readBuffer(outputBuffer, outputSize);
|
||||
}
|
||||
|
||||
FUNC(jint, flacDecodeToArray, jlong jContext, jbyteArray jOutputArray) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
context->source->setFlacJni(env, thiz);
|
||||
jbyte *outputBuffer = env->GetByteArrayElements(jOutputArray, NULL);
|
||||
jint outputSize = env->GetArrayLength(jOutputArray);
|
||||
int count = context->parser->readBuffer(outputBuffer, outputSize);
|
||||
env->ReleaseByteArrayElements(jOutputArray, outputBuffer, 0);
|
||||
return count;
|
||||
}
|
||||
|
||||
FUNC(jlong, flacGetLastTimestamp, jlong jContext) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
return context->parser->getLastTimestamp();
|
||||
}
|
||||
|
||||
FUNC(jlong, flacGetSeekPosition, jlong jContext, jlong timeUs) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
return context->parser->getSeekPosition(timeUs);
|
||||
}
|
||||
|
||||
FUNC(void, flacFlush, jlong jContext) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
context->parser->flush();
|
||||
}
|
||||
|
||||
FUNC(void, flacRelease, jlong jContext) {
|
||||
Context *context = reinterpret_cast<Context *>(jContext);
|
||||
delete context->parser;
|
||||
delete context->source;
|
||||
delete context;
|
||||
}
|
458
extensions/flac/src/main/jni/flac_parser.cc
Normal file
458
extensions/flac/src/main/jni/flac_parser.cc
Normal file
@ -0,0 +1,458 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "include/flac_parser.h"
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
#define LOG_TAG "FLACParser"
|
||||
#define ALOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
|
||||
#define ALOGV(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
|
||||
|
||||
#define LOG_ALWAYS_FATAL(...) \
|
||||
(__android_log_assert(NULL, LOG_TAG, ##__VA_ARGS__))
|
||||
|
||||
#define LITERAL_TO_STRING_INTERNAL(x) #x
|
||||
#define LITERAL_TO_STRING(x) LITERAL_TO_STRING_INTERNAL(x)
|
||||
|
||||
#define TRESPASS() \
|
||||
LOG_ALWAYS_FATAL(__FILE__ \
|
||||
":" LITERAL_TO_STRING(__LINE__) " Should not be here.");
|
||||
#define CHECK(x) \
|
||||
if (!(x)) ALOGE("Check failed: %s ", #x)
|
||||
|
||||
// The FLAC parser calls our C++ static callbacks using C calling conventions,
|
||||
// inside FLAC__stream_decoder_process_until_end_of_metadata
|
||||
// and FLAC__stream_decoder_process_single.
|
||||
// We immediately then call our corresponding C++ instance methods
|
||||
// with the same parameter list, but discard redundant information.
|
||||
|
||||
FLAC__StreamDecoderReadStatus FLACParser::read_callback(
|
||||
const FLAC__StreamDecoder * /* decoder */, FLAC__byte buffer[],
|
||||
size_t *bytes, void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)
|
||||
->readCallback(buffer, bytes);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderSeekStatus FLACParser::seek_callback(
|
||||
const FLAC__StreamDecoder * /* decoder */,
|
||||
FLAC__uint64 absolute_byte_offset, void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)
|
||||
->seekCallback(absolute_byte_offset);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderTellStatus FLACParser::tell_callback(
|
||||
const FLAC__StreamDecoder * /* decoder */,
|
||||
FLAC__uint64 *absolute_byte_offset, void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)
|
||||
->tellCallback(absolute_byte_offset);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderLengthStatus FLACParser::length_callback(
|
||||
const FLAC__StreamDecoder * /* decoder */, FLAC__uint64 *stream_length,
|
||||
void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)
|
||||
->lengthCallback(stream_length);
|
||||
}
|
||||
|
||||
FLAC__bool FLACParser::eof_callback(const FLAC__StreamDecoder * /* decoder */,
|
||||
void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)->eofCallback();
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus FLACParser::write_callback(
|
||||
const FLAC__StreamDecoder * /* decoder */, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buffer[], void *client_data) {
|
||||
return reinterpret_cast<FLACParser *>(client_data)
|
||||
->writeCallback(frame, buffer);
|
||||
}
|
||||
|
||||
void FLACParser::metadata_callback(const FLAC__StreamDecoder * /* decoder */,
|
||||
const FLAC__StreamMetadata *metadata,
|
||||
void *client_data) {
|
||||
reinterpret_cast<FLACParser *>(client_data)->metadataCallback(metadata);
|
||||
}
|
||||
|
||||
void FLACParser::error_callback(const FLAC__StreamDecoder * /* decoder */,
|
||||
FLAC__StreamDecoderErrorStatus status,
|
||||
void *client_data) {
|
||||
reinterpret_cast<FLACParser *>(client_data)->errorCallback(status);
|
||||
}
|
||||
|
||||
// These are the corresponding callbacks with C++ calling conventions
|
||||
|
||||
FLAC__StreamDecoderReadStatus FLACParser::readCallback(FLAC__byte buffer[],
|
||||
size_t *bytes) {
|
||||
size_t requested = *bytes;
|
||||
ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested);
|
||||
if (0 > actual) {
|
||||
*bytes = 0;
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
} else if (0 == actual) {
|
||||
*bytes = 0;
|
||||
mEOF = true;
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
} else {
|
||||
assert(actual <= requested);
|
||||
*bytes = actual;
|
||||
mCurrentPos += actual;
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderSeekStatus FLACParser::seekCallback(
|
||||
FLAC__uint64 absolute_byte_offset) {
|
||||
mCurrentPos = absolute_byte_offset;
|
||||
mEOF = false;
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderTellStatus FLACParser::tellCallback(
|
||||
FLAC__uint64 *absolute_byte_offset) {
|
||||
*absolute_byte_offset = mCurrentPos;
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback(
|
||||
FLAC__uint64 *stream_length) {
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
FLAC__bool FLACParser::eofCallback() { return mEOF; }
|
||||
|
||||
FLAC__StreamDecoderWriteStatus FLACParser::writeCallback(
|
||||
const FLAC__Frame *frame, const FLAC__int32 *const buffer[]) {
|
||||
if (mWriteRequested) {
|
||||
mWriteRequested = false;
|
||||
// FLAC parser doesn't free or realloc buffer until next frame or finish
|
||||
mWriteHeader = frame->header;
|
||||
mWriteBuffer = buffer;
|
||||
mWriteCompleted = true;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
} else {
|
||||
ALOGE("FLACParser::writeCallback unexpected");
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata) {
|
||||
switch (metadata->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
if (!mStreamInfoValid) {
|
||||
mStreamInfo = metadata->data.stream_info;
|
||||
mStreamInfoValid = true;
|
||||
} else {
|
||||
ALOGE("FLACParser::metadataCallback unexpected STREAMINFO");
|
||||
}
|
||||
break;
|
||||
case FLAC__METADATA_TYPE_SEEKTABLE:
|
||||
mSeekTable = &metadata->data.seek_table;
|
||||
break;
|
||||
default:
|
||||
ALOGE("FLACParser::metadataCallback unexpected type %u", metadata->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) {
|
||||
ALOGE("FLACParser::errorCallback status=%d", status);
|
||||
mErrorStatus = status;
|
||||
}
|
||||
|
||||
// Copy samples from FLAC native 32-bit non-interleaved to 16-bit interleaved.
|
||||
// These are candidates for optimization if needed.
|
||||
|
||||
static void copyMono8(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i] << 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyStereo8(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i] << 8;
|
||||
*dst++ = src[1][i] << 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyMultiCh8(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned nChannels) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
for (unsigned c = 0; c < nChannels; ++c) {
|
||||
*dst++ = src[c][i] << 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void copyMono16(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i];
|
||||
}
|
||||
}
|
||||
|
||||
static void copyStereo16(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i];
|
||||
*dst++ = src[1][i];
|
||||
}
|
||||
}
|
||||
|
||||
static void copyMultiCh16(int16_t *dst, const int *const *src,
|
||||
unsigned nSamples, unsigned nChannels) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
for (unsigned c = 0; c < nChannels; ++c) {
|
||||
*dst++ = src[c][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 24-bit versions should do dithering or noise-shaping, here or in AudioFlinger
|
||||
|
||||
static void copyMono24(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyStereo24(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned /* nChannels */) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
*dst++ = src[0][i] >> 8;
|
||||
*dst++ = src[1][i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyMultiCh24(int16_t *dst, const int *const *src,
|
||||
unsigned nSamples, unsigned nChannels) {
|
||||
for (unsigned i = 0; i < nSamples; ++i) {
|
||||
for (unsigned c = 0; c < nChannels; ++c) {
|
||||
*dst++ = src[c][i] >> 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void copyTrespass(int16_t * /* dst */, const int *const * /* src */,
|
||||
unsigned /* nSamples */, unsigned /* nChannels */) {
|
||||
TRESPASS();
|
||||
}
|
||||
|
||||
// FLACParser
|
||||
|
||||
FLACParser::FLACParser(DataSource *source)
|
||||
: mDataSource(source),
|
||||
mCopy(copyTrespass),
|
||||
mDecoder(NULL),
|
||||
mSeekTable(NULL),
|
||||
firstFrameOffset(0LL),
|
||||
mCurrentPos(0LL),
|
||||
mEOF(false),
|
||||
mStreamInfoValid(false),
|
||||
mWriteRequested(false),
|
||||
mWriteCompleted(false),
|
||||
mWriteBuffer(NULL),
|
||||
mErrorStatus((FLAC__StreamDecoderErrorStatus)-1) {
|
||||
ALOGV("FLACParser::FLACParser");
|
||||
memset(&mStreamInfo, 0, sizeof(mStreamInfo));
|
||||
memset(&mWriteHeader, 0, sizeof(mWriteHeader));
|
||||
}
|
||||
|
||||
FLACParser::~FLACParser() {
|
||||
ALOGV("FLACParser::~FLACParser");
|
||||
if (mDecoder != NULL) {
|
||||
FLAC__stream_decoder_delete(mDecoder);
|
||||
mDecoder = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool FLACParser::init() {
|
||||
// setup libFLAC parser
|
||||
mDecoder = FLAC__stream_decoder_new();
|
||||
if (mDecoder == NULL) {
|
||||
// The new should succeed, since probably all it does is a malloc
|
||||
// that always succeeds in Android. But to avoid dependence on the
|
||||
// libFLAC internals, we check and log here.
|
||||
ALOGE("new failed");
|
||||
return false;
|
||||
}
|
||||
FLAC__stream_decoder_set_md5_checking(mDecoder, false);
|
||||
FLAC__stream_decoder_set_metadata_ignore_all(mDecoder);
|
||||
FLAC__stream_decoder_set_metadata_respond(mDecoder,
|
||||
FLAC__METADATA_TYPE_STREAMINFO);
|
||||
FLAC__stream_decoder_set_metadata_respond(mDecoder,
|
||||
FLAC__METADATA_TYPE_SEEKTABLE);
|
||||
FLAC__StreamDecoderInitStatus initStatus;
|
||||
initStatus = FLAC__stream_decoder_init_stream(
|
||||
mDecoder, read_callback, seek_callback, tell_callback, length_callback,
|
||||
eof_callback, write_callback, metadata_callback, error_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
// A failure here probably indicates a programming error and so is
|
||||
// unlikely to happen. But we check and log here similarly to above.
|
||||
ALOGE("init_stream failed %d", initStatus);
|
||||
return false;
|
||||
}
|
||||
// parse all metadata
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) {
|
||||
ALOGE("end_of_metadata failed");
|
||||
return false;
|
||||
}
|
||||
// store first frame offset
|
||||
FLAC__stream_decoder_get_decode_position(mDecoder, &firstFrameOffset);
|
||||
|
||||
if (mStreamInfoValid) {
|
||||
// check channel count
|
||||
if (getChannels() == 0 || getChannels() > 8) {
|
||||
ALOGE("unsupported channel count %u", getChannels());
|
||||
return false;
|
||||
}
|
||||
// check bit depth
|
||||
switch (getBitsPerSample()) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 24:
|
||||
break;
|
||||
default:
|
||||
ALOGE("unsupported bits per sample %u", getBitsPerSample());
|
||||
return false;
|
||||
}
|
||||
// check sample rate
|
||||
switch (getSampleRate()) {
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
case 88200:
|
||||
case 96000:
|
||||
break;
|
||||
default:
|
||||
ALOGE("unsupported sample rate %u", getSampleRate());
|
||||
return false;
|
||||
}
|
||||
// configure the appropriate copy function, defaulting to trespass
|
||||
static const struct {
|
||||
unsigned mChannels;
|
||||
unsigned mBitsPerSample;
|
||||
void (*mCopy)(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned nChannels);
|
||||
} table[] = {
|
||||
{1, 8, copyMono8}, {2, 8, copyStereo8}, {8, 8, copyMultiCh8},
|
||||
{1, 16, copyMono16}, {2, 16, copyStereo16}, {8, 16, copyMultiCh16},
|
||||
{1, 24, copyMono24}, {2, 24, copyStereo24}, {8, 24, copyMultiCh24},
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof(table) / sizeof(table[0]); ++i) {
|
||||
if (table[i].mChannels >= getChannels() &&
|
||||
table[i].mBitsPerSample == getBitsPerSample()) {
|
||||
mCopy = table[i].mCopy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ALOGE("missing STREAMINFO");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t FLACParser::readBuffer(void *output, size_t output_size) {
|
||||
mWriteRequested = true;
|
||||
mWriteCompleted = false;
|
||||
|
||||
if (!FLAC__stream_decoder_process_single(mDecoder)) {
|
||||
ALOGE("FLACParser::readBuffer process_single failed. Status: %s",
|
||||
FLAC__stream_decoder_get_resolved_state_string(mDecoder));
|
||||
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));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// verify that block header keeps the promises made by STREAMINFO
|
||||
unsigned blocksize = mWriteHeader.blocksize;
|
||||
if (blocksize == 0 || blocksize > getMaxBlockSize()) {
|
||||
ALOGE("FLACParser::readBuffer write invalid blocksize %u", blocksize);
|
||||
return -1;
|
||||
}
|
||||
if (mWriteHeader.sample_rate != getSampleRate() ||
|
||||
mWriteHeader.channels != getChannels() ||
|
||||
mWriteHeader.bits_per_sample != getBitsPerSample()) {
|
||||
ALOGE(
|
||||
"FLACParser::readBuffer write changed parameters mid-stream: %d/%d/%d "
|
||||
"-> %d/%d/%d",
|
||||
getSampleRate(), getChannels(), getBitsPerSample(),
|
||||
mWriteHeader.sample_rate, mWriteHeader.channels,
|
||||
mWriteHeader.bits_per_sample);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t bufferSize = blocksize * getChannels() * sizeof(int16_t);
|
||||
if (bufferSize > output_size) {
|
||||
ALOGE(
|
||||
"FLACParser::readBuffer not enough space in output buffer "
|
||||
"%zu < %zu",
|
||||
output_size, bufferSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// copy PCM from FLAC write buffer to our media buffer, with interleaving.
|
||||
(*mCopy)(reinterpret_cast<int16_t *>(output), mWriteBuffer, blocksize,
|
||||
getChannels());
|
||||
|
||||
// fill in buffer metadata
|
||||
CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER);
|
||||
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
int64_t FLACParser::getSeekPosition(int64_t timeUs) {
|
||||
if (!mSeekTable) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t sample = (timeUs * getSampleRate()) / 1000000LL;
|
||||
if (sample >= getTotalSamples()) {
|
||||
sample = getTotalSamples();
|
||||
}
|
||||
|
||||
FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points;
|
||||
for (unsigned i = mSeekTable->num_points - 1; i >= 0; i--) {
|
||||
if (points[i].sample_number <= sample) {
|
||||
return firstFrameOffset + points[i].stream_offset;
|
||||
}
|
||||
}
|
||||
return firstFrameOffset;
|
||||
}
|
45
extensions/flac/src/main/jni/flac_sources.mk
Normal file
45
extensions/flac/src/main/jni/flac_sources.mk
Normal file
@ -0,0 +1,45 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
FLAC_SOURCES = \
|
||||
flac_jni.cc \
|
||||
flac_parser.cc \
|
||||
flac/src/libFLAC/bitmath.c \
|
||||
flac/src/libFLAC/bitreader.c \
|
||||
flac/src/libFLAC/bitwriter.c \
|
||||
flac/src/libFLAC/cpu.c \
|
||||
flac/src/libFLAC/crc.c \
|
||||
flac/src/libFLAC/fixed.c \
|
||||
flac/src/libFLAC/fixed_intrin_sse2.c \
|
||||
flac/src/libFLAC/fixed_intrin_ssse3.c \
|
||||
flac/src/libFLAC/float.c \
|
||||
flac/src/libFLAC/format.c \
|
||||
flac/src/libFLAC/lpc.c \
|
||||
flac/src/libFLAC/lpc_intrin_avx2.c \
|
||||
flac/src/libFLAC/lpc_intrin_sse2.c \
|
||||
flac/src/libFLAC/lpc_intrin_sse41.c \
|
||||
flac/src/libFLAC/lpc_intrin_sse.c \
|
||||
flac/src/libFLAC/md5.c \
|
||||
flac/src/libFLAC/memory.c \
|
||||
flac/src/libFLAC/metadata_iterators.c \
|
||||
flac/src/libFLAC/metadata_object.c \
|
||||
flac/src/libFLAC/stream_decoder.c \
|
||||
flac/src/libFLAC/stream_encoder.c \
|
||||
flac/src/libFLAC/stream_encoder_framing.c \
|
||||
flac/src/libFLAC/stream_encoder_intrin_avx2.c \
|
||||
flac/src/libFLAC/stream_encoder_intrin_sse2.c \
|
||||
flac/src/libFLAC/stream_encoder_intrin_ssse3.c \
|
||||
flac/src/libFLAC/window.c
|
31
extensions/flac/src/main/jni/include/data_source.h
Normal file
31
extensions/flac/src/main/jni/include/data_source.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_DATA_SOURCE_H_
|
||||
#define INCLUDE_DATA_SOURCE_H_
|
||||
|
||||
#include <jni.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
class DataSource {
|
||||
public:
|
||||
// Returns the number of bytes read, or -1 on failure. It's not an error if
|
||||
// this returns zero; it just means the given offset is equal to, or
|
||||
// beyond, the end of the source.
|
||||
virtual ssize_t readAt(off64_t offset, void* const data, size_t size) = 0;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DATA_SOURCE_H_
|
133
extensions/flac/src/main/jni/include/flac_parser.h
Normal file
133
extensions/flac/src/main/jni/include/flac_parser.h
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef FLAC_PARSER_H_
|
||||
#define FLAC_PARSER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// libFLAC parser
|
||||
#include "FLAC/stream_decoder.h"
|
||||
|
||||
#include "include/data_source.h"
|
||||
|
||||
typedef int status_t;
|
||||
|
||||
class FLACParser {
|
||||
public:
|
||||
FLACParser(DataSource *source);
|
||||
~FLACParser();
|
||||
|
||||
bool init();
|
||||
|
||||
// stream properties
|
||||
unsigned getMaxBlockSize() const { return mStreamInfo.max_blocksize; }
|
||||
unsigned getSampleRate() const { return mStreamInfo.sample_rate; }
|
||||
unsigned getChannels() const { return mStreamInfo.channels; }
|
||||
unsigned getBitsPerSample() const { return mStreamInfo.bits_per_sample; }
|
||||
FLAC__uint64 getTotalSamples() const { return mStreamInfo.total_samples; }
|
||||
|
||||
const FLAC__StreamMetadata_StreamInfo& getStreamInfo() const {
|
||||
return mStreamInfo;
|
||||
}
|
||||
|
||||
int64_t getLastTimestamp() const {
|
||||
return (1000000LL * mWriteHeader.number.sample_number) / getSampleRate();
|
||||
}
|
||||
|
||||
size_t readBuffer(void *output, size_t output_size);
|
||||
|
||||
int64_t getSeekPosition(int64_t timeUs);
|
||||
|
||||
void flush() {
|
||||
if (mDecoder != NULL) {
|
||||
FLAC__stream_decoder_flush(mDecoder);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource *mDataSource;
|
||||
|
||||
void (*mCopy)(int16_t *dst, const int *const *src, unsigned nSamples,
|
||||
unsigned nChannels);
|
||||
|
||||
// handle to underlying libFLAC parser
|
||||
FLAC__StreamDecoder *mDecoder;
|
||||
|
||||
// current position within the data source
|
||||
off64_t mCurrentPos;
|
||||
bool mEOF;
|
||||
|
||||
// cached when the STREAMINFO metadata is parsed by libFLAC
|
||||
FLAC__StreamMetadata_StreamInfo mStreamInfo;
|
||||
bool mStreamInfoValid;
|
||||
|
||||
const FLAC__StreamMetadata_SeekTable *mSeekTable;
|
||||
uint64_t firstFrameOffset;
|
||||
|
||||
// cached when a decoded PCM block is "written" by libFLAC parser
|
||||
bool mWriteRequested;
|
||||
bool mWriteCompleted;
|
||||
FLAC__FrameHeader mWriteHeader;
|
||||
const FLAC__int32 *const *mWriteBuffer;
|
||||
|
||||
// most recent error reported by libFLAC parser
|
||||
FLAC__StreamDecoderErrorStatus mErrorStatus;
|
||||
|
||||
// no copy constructor or assignment
|
||||
FLACParser(const FLACParser &);
|
||||
FLACParser &operator=(const FLACParser &);
|
||||
|
||||
// FLAC parser callbacks as C++ instance methods
|
||||
FLAC__StreamDecoderReadStatus readCallback(FLAC__byte buffer[],
|
||||
size_t *bytes);
|
||||
FLAC__StreamDecoderSeekStatus seekCallback(FLAC__uint64 absolute_byte_offset);
|
||||
FLAC__StreamDecoderTellStatus tellCallback(
|
||||
FLAC__uint64 *absolute_byte_offset);
|
||||
FLAC__StreamDecoderLengthStatus lengthCallback(FLAC__uint64 *stream_length);
|
||||
FLAC__bool eofCallback();
|
||||
FLAC__StreamDecoderWriteStatus writeCallback(
|
||||
const FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
|
||||
void metadataCallback(const FLAC__StreamMetadata *metadata);
|
||||
void errorCallback(FLAC__StreamDecoderErrorStatus status);
|
||||
|
||||
// FLAC parser callbacks as C-callable functions
|
||||
static FLAC__StreamDecoderReadStatus read_callback(
|
||||
const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes,
|
||||
void *client_data);
|
||||
static FLAC__StreamDecoderSeekStatus seek_callback(
|
||||
const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset,
|
||||
void *client_data);
|
||||
static FLAC__StreamDecoderTellStatus tell_callback(
|
||||
const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset,
|
||||
void *client_data);
|
||||
static FLAC__StreamDecoderLengthStatus length_callback(
|
||||
const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length,
|
||||
void *client_data);
|
||||
static FLAC__bool eof_callback(const FLAC__StreamDecoder *decoder,
|
||||
void *client_data);
|
||||
static FLAC__StreamDecoderWriteStatus write_callback(
|
||||
const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buffer[], void *client_data);
|
||||
static void metadata_callback(const FLAC__StreamDecoder *decoder,
|
||||
const FLAC__StreamMetadata *metadata,
|
||||
void *client_data);
|
||||
static void error_callback(const FLAC__StreamDecoder *decoder,
|
||||
FLAC__StreamDecoderErrorStatus status,
|
||||
void *client_data);
|
||||
};
|
||||
|
||||
#endif // FLAC_PARSER_H_
|
11
extensions/flac/src/main/proguard.cfg
Normal file
11
extensions/flac/src/main/proguard.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
# Proguard rules specific to the Flac extension.
|
||||
|
||||
# This prevents the names of native methods from being obfuscated.
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Some members of this class are being accessed from native methods. Keep them unobfuscated.
|
||||
-keep class com.google.android.exoplayer.ext.flac.FlacJni {
|
||||
*;
|
||||
}
|
16
extensions/flac/src/main/project.properties
Normal file
16
extensions/flac/src/main/project.properties
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-23
|
||||
android.library=true
|
||||
android.library.reference.1=../../../../library/src/main
|
@ -13,3 +13,8 @@
|
||||
// limitations under the License.
|
||||
include ':library'
|
||||
include ':demo'
|
||||
include ':extension-okhttp'
|
||||
include ':extension-flac'
|
||||
|
||||
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
|
||||
project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac')
|
||||
|
Loading…
x
Reference in New Issue
Block a user