Copy main/extensions/flac -> experimental/extensions/flac

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=119544516
This commit is contained in:
eguven 2016-04-11 10:27:27 -07:00 committed by Oliver Woodman
parent eb877f0cb7
commit 087cf9546f
22 changed files with 2090 additions and 0 deletions

65
extensions/flac/README.md Normal file
View 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.

View 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')
}

View 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>

View 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>

View 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>

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
});
}
}
}

View 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)

View 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

View 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;
}

View 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;
}

View 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

View 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_

View 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_

View 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 {
*;
}

View 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

View File

@ -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')