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.
|
// limitations under the License.
|
||||||
include ':library'
|
include ':library'
|
||||||
include ':demo'
|
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