Copy opus extension v1->v2

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=119643009
This commit is contained in:
eguven 2016-04-12 08:55:44 -07:00 committed by Oliver Woodman
parent 767c7ab169
commit 192f566a1b
23 changed files with 1318 additions and 8 deletions

View File

@ -74,7 +74,10 @@ import java.util.List;
@Override
public FlacDecoderException decode(DecoderInputBuffer inputBuffer,
FlacOutputBuffer outputBuffer) {
FlacOutputBuffer outputBuffer, boolean reset) {
if (reset) {
decoder.flush();
}
ByteBuffer data = inputBuffer.data;
outputBuffer.timestampUs = inputBuffer.timeUs;
data.limit(data.position());

77
extensions/opus/README.md Normal file
View File

@ -0,0 +1,77 @@
# ExoPlayer Opus Extension #
## Description ##
The Opus Extension is a [TrackRenderer][] implementation that helps you bundle
libopus (the Opus decoding library) into your app and use it along with
ExoPlayer to play Opus 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)"
OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main"
```
* Download the [Android NDK][] and set its location in an environment variable:
```
NDK_PATH="<path to Android NDK>"
```
* Fetch libopus:
```
cd "${OPUS_EXT_PATH}/jni" && \
git clone git://git.opus-codec.org/opus.git libopus
```
* Run the script to convert arm assembly to NDK compatible format:
```
cd ${OPUS_EXT_PATH}/jni && ./convert_android_asm.sh
```
* Build the JNI native libraries from the command line:
```
cd "${OPUS_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4
```
* In your project, you can add a dependency to the Opus Extension by using a
rule like this:
```
// in settings.gradle
include ':..:ExoPlayer:library'
include ':..:ExoPlayer:extension-opus'
// in build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
compile project(':..:ExoPlayer:extension-opus')
}
```
* Now, when you build your app, the Opus extension will be built and the native
libraries will be packaged along with the APK.
## Notes ##
* Every time there is a change to the libopus checkout:
* Arm assembly should be converted by running `convert_android_asm.sh`
* Clean and re-build the project.
* If you want to use your own version of libopus, place it in
`${OPUS_EXT_PATH}/jni/libopus`.

View File

@ -0,0 +1,45 @@
// Copyright (C) 2014 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,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="com.android.toolchain.gcc.423224913">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.android.toolchain.gcc.423224913" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="com.android.toolchain.gcc.423224913" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="com.android.toolchain.gcc.423224913.1376674556" name="/" resourcePath="">
<toolChain id="com.android.toolchain.gcc.1798416430" name="Android GCC" superClass="com.android.toolchain.gcc">
<targetPlatform binaryParser="org.eclipse.cdt.core.ELF" id="com.android.targetPlatform.1132129264" isAbstract="false" superClass="com.android.targetPlatform"/>
<builder buildPath="${workspace_loc:/ExoPlayerExt-Opus}/jni" id="com.android.builder.532503968" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Android Builder" superClass="com.android.builder">
<outputEntries>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="obj"/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="libs"/>
</outputEntries>
</builder>
<tool id="com.android.gcc.compiler.906450637" name="Android GCC Compiler" superClass="com.android.gcc.compiler">
<inputType id="com.android.gcc.inputType.835889068" superClass="com.android.gcc.inputType"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="jni"/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="ExoPlayerExt-Opus.null.1840202624" name="ExoPlayerExt-Opus"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="com.android.toolchain.gcc.423224913;com.android.toolchain.gcc.423224913.1376674556;com.android.gcc.compiler.906450637;com.android.gcc.inputType.835889068">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="com.android.AndroidPerProjectProfile"/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="Default">
<resource resourceType="PROJECT" workspacePath="/ExoPlayerExt-Opus"/>
</configuration>
</storageModule>
</cproject>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ExoPlayerExt-Opus</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,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 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.opus">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
</manifest>

View File

@ -0,0 +1,397 @@
/*
* Copyright (C) 2014 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.opus;
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 Opus decoder.
*/
public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link LibopusAudioTrackRenderer} 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(OpusDecoderException 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;
private static final int INITIAL_INPUT_BUFFER_SIZE = 960 * 6;
public final CodecCounters codecCounters = new CodecCounters();
private final Handler eventHandler;
private final EventListener eventListener;
private final AudioTrack audioTrack;
private final FormatHolder formatHolder;
private Format format;
private OpusDecoder decoder;
private DecoderInputBuffer inputBuffer;
private OpusOutputBuffer outputBuffer;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean sourceIsReady;
private int audioSessionId;
public LibopusAudioTrackRenderer() {
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 LibopusAudioTrackRenderer(Handler eventHandler, EventListener eventListener) {
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack();
formatHolder = new FormatHolder();
}
/**
* Returns whether the underlying libopus library is available.
*/
public static boolean isLibopusAvailable() {
return OpusDecoder.IS_AVAILABLE;
}
/**
* Returns the version of the underlying libopus library if available, otherwise {@code null}.
*/
public static String getLibopusVersion() {
return isLibopusAvailable() ? OpusDecoder.getLibopusVersion() : null;
}
@Override
protected MediaClock getMediaClock() {
return this;
}
@Override
protected int supportsFormat(Format format) {
return MimeTypes.AUDIO_OPUS.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 opus, the format can contain upto 3 entries in initializationData in the following
// exact order:
// 1) Opus Header Information (required)
// 2) Codec Delay in nanoseconds (required if Seek Preroll is present)
// 3) Seek Preroll in nanoseconds (required if Codec Delay is present)
List<byte[]> initializationData = format.initializationData;
if (initializationData.size() < 1) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Missing initialization data"), getIndex());
}
try {
decoder = new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
initializationData);
} catch (OpusDecoderException 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 (OpusDecoderException e) {
notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
codecCounters.ensureUpdated();
}
private void renderBuffer() throws OpusDecoderException, 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 OpusDecoderException {
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 OpusDecoderException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderError(e);
}
});
}
}
}

View File

@ -0,0 +1,217 @@
/*
* Copyright (C) 2014 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.opus;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
/**
* JNI wrapper for the libopus Opus decoder.
*/
/* package */ final class OpusDecoder extends
SimpleDecoder<DecoderInputBuffer, OpusOutputBuffer, OpusDecoderException> {
/**
* Whether the underlying libopus library is available.
*/
public static final boolean IS_AVAILABLE;
static {
boolean isAvailable;
try {
System.loadLibrary("opus");
System.loadLibrary("opusJNI");
isAvailable = true;
} catch (UnsatisfiedLinkError exception) {
isAvailable = false;
}
IS_AVAILABLE = isAvailable;
}
/**
* Returns the version string of the underlying libopus decoder.
*/
public static native String getLibopusVersion();
private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
/**
* Opus streams are always decoded at 48000 Hz.
*/
private static final int SAMPLE_RATE = 48000;
private final int channelCount;
private final int headerSkipSamples;
private final int headerSeekPreRollSamples;
private final long nativeDecoderContext;
private int skipSamples;
/**
* Creates an Opus decoder.
*
* @param numInputBuffers The number of input buffers.
* @param numOutputBuffers The number of output buffers.
* @param initialInputBufferSize The initial size of each input buffer.
* @param initializationData Codec-specific initialization data. The first element must contain an
* opus header. Optionally, the list may contain two additional buffers, which must contain
* the encoder delay and seek pre roll values in nanoseconds, encoded as longs.
* @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder.
*/
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
List<byte[]> initializationData) throws OpusDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new OpusOutputBuffer[numOutputBuffers]);
byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) {
throw new OpusDecoderException("Header size is too small.");
}
channelCount = headerBytes[9] & 0xFF;
if (channelCount > 8) {
throw new OpusDecoderException("Invalid channel count: " + channelCount);
}
int preskip = readLittleEndian16(headerBytes, 10);
int gain = readLittleEndian16(headerBytes, 16);
byte[] streamMap = new byte[8];
int numStreams, numCoupled;
if (headerBytes[18] == 0) { // Channel mapping
// If there is no channel mapping, use the defaults.
if (channelCount > 2) { // Maximum channel count with default layout.
throw new OpusDecoderException("Invalid Header, missing stream map.");
}
numStreams = 1;
numCoupled = (channelCount == 2) ? 1 : 0;
streamMap[0] = 0;
streamMap[1] = 1;
} else {
if (headerBytes.length < 21 + channelCount) {
throw new OpusDecoderException("Header size is too small.");
}
// Read the channel mapping.
numStreams = headerBytes[19] & 0xFF;
numCoupled = headerBytes[20] & 0xFF;
for (int i = 0; i < channelCount; i++) {
streamMap[i] = headerBytes[21 + i];
}
}
if (initializationData.size() == 3) {
if (initializationData.get(1).length != 8 || initializationData.get(2).length != 8) {
throw new OpusDecoderException("Invalid Codec Delay or Seek Preroll");
}
long codecDelayNs =
ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.LITTLE_ENDIAN).getLong();
long seekPreRollNs =
ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.LITTLE_ENDIAN).getLong();
headerSkipSamples = nsToSamples(codecDelayNs);
headerSeekPreRollSamples = nsToSamples(seekPreRollNs);
} else {
headerSkipSamples = preskip;
headerSeekPreRollSamples = DEFAULT_SEEK_PRE_ROLL_SAMPLES;
}
nativeDecoderContext = opusInit(SAMPLE_RATE, channelCount, numStreams, numCoupled, gain,
streamMap);
if (nativeDecoderContext == 0) {
throw new OpusDecoderException("Failed to initialize decoder");
}
setInitialInputBufferSize(initialInputBufferSize);
}
@Override
public DecoderInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
}
@Override
public OpusOutputBuffer createOutputBuffer() {
return new OpusOutputBuffer(this);
}
@Override
protected void releaseOutputBuffer(OpusOutputBuffer buffer) {
super.releaseOutputBuffer(buffer);
}
@Override
public OpusDecoderException decode(DecoderInputBuffer inputBuffer,
OpusOutputBuffer outputBuffer, boolean reset) {
if (reset) {
opusReset(nativeDecoderContext);
// When seeking to 0, skip number of samples as specified in opus header. When seeking to
// any other time, skip number of samples as specified by seek preroll.
skipSamples =
(inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples;
}
outputBuffer.timestampUs = inputBuffer.timeUs;
inputBuffer.data.position(inputBuffer.data.position() - inputBuffer.size);
int requiredOutputBufferSize =
opusGetRequiredOutputBufferSize(inputBuffer.data, inputBuffer.size, SAMPLE_RATE);
if (requiredOutputBufferSize < 0) {
return new OpusDecoderException("Error when computing required output buffer size.");
}
outputBuffer.init(requiredOutputBufferSize);
int result = opusDecode(nativeDecoderContext, inputBuffer.data, inputBuffer.size,
outputBuffer.data, outputBuffer.data.capacity());
if (result < 0) {
return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result));
}
outputBuffer.data.position(0);
outputBuffer.data.limit(result);
if (skipSamples > 0) {
int bytesPerSample = channelCount * 2;
int skipBytes = skipSamples * bytesPerSample;
if (result <= skipBytes) {
skipSamples -= result / bytesPerSample;
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
outputBuffer.data.position(result);
} else {
skipSamples = 0;
outputBuffer.data.position(skipBytes);
}
}
return null;
}
@Override
public void release() {
super.release();
opusClose(nativeDecoderContext);
}
private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled,
int gain, byte[] streamMap);
private native int opusDecode(long decoder, ByteBuffer inputBuffer, int inputSize,
ByteBuffer outputBuffer, int outputSize);
private native int opusGetRequiredOutputBufferSize(
ByteBuffer inputBuffer, int inputSize, int sampleRate);
private native void opusClose(long decoder);
private native void opusReset(long decoder);
private native String opusGetErrorMessage(int errorCode);
private static int nsToSamples(long ns) {
return (int) (ns * SAMPLE_RATE / 1000000000);
}
private static int readLittleEndian16(byte[] input, int offset) {
int value = input[offset] & 0xFF;
value |= (input[offset + 1] & 0xFF) << 8;
return value;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2014 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.opus;
/**
* Thrown when an Opus decoder error occurs.
*/
public final class OpusDecoderException extends Exception {
/* package */ OpusDecoderException(String message) {
super(message);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 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.opus;
import com.google.android.exoplayer.util.extensions.OutputBuffer;
import java.nio.ByteBuffer;
/**
* Buffer for {@link OpusDecoder} output.
*/
public final class OpusOutputBuffer extends OutputBuffer {
private final OpusDecoder owner;
public ByteBuffer data;
/* package */ OpusOutputBuffer(OpusDecoder 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,33 @@
#
# Copyright (C) 2014 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)
include $(CLEAR_VARS)
# build libopus.so
LOCAL_PATH := $(WORKING_DIR)
include libopus.mk
# build libopusJNI.so
include $(CLEAR_VARS)
LOCAL_PATH := $(WORKING_DIR)
LOCAL_MODULE := libopusJNI
LOCAL_ARM_MODE := arm
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := opus_jni.cc
LOCAL_LDLIBS := -llog -lz -lm
LOCAL_SHARED_LIBRARIES := libopus
include $(BUILD_SHARED_LIBRARY)

View File

@ -0,0 +1,20 @@
#
# Copyright (C) 2014 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,47 @@
#!/bin/bash
#
# Copyright (C) 2014 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.
#
set -e
ASM_CONVERTER="./libopus/celt/arm/arm2gnu.pl"
if [[ ! -x "${ASM_CONVERTER}" ]]; then
echo "Please make sure you have checked out libopus."
exit
fi
while read file; do
# This check is required because the ASM conversion script doesn't seem to be
# idempotent.
if [[ ! "${file}" =~ .*_gnu\.s$ ]]; then
gnu_file="${file%.s}_gnu.s"
${ASM_CONVERTER} "${file}" > "${gnu_file}"
# The ASM conversion script replaces includes with *_gnu.S. So, replace
# occurences of "*-gnu.S" with "*_gnu.s".
perl -pi -e "s/-gnu\.S/_gnu\.s/g" "${gnu_file}"
rm -f "${file}"
fi
done < <(find . -iname '*.s')
# Generate armopts.s from armopts.s.in
sed \
-e "s/@OPUS_ARM_MAY_HAVE_EDSP@/1/g" \
-e "s/@OPUS_ARM_MAY_HAVE_MEDIA@/1/g" \
-e "s/@OPUS_ARM_MAY_HAVE_NEON@/1/g" \
libopus/celt/arm/armopts.s.in > libopus/celt/arm/armopts.s.temp
${ASM_CONVERTER} "libopus/celt/arm/armopts.s.temp" > "libopus/celt/arm/armopts_gnu.s"
rm "libopus/celt/arm/armopts.s.temp"
echo "Converted all ASM files and generated armopts.s successfully."

View File

@ -0,0 +1,50 @@
#
# Copyright (C) 2014 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.
#
LOCAL_PATH := $(call my-dir)/libopus
include $(CLEAR_VARS)
include $(LOCAL_PATH)/celt_headers.mk
include $(LOCAL_PATH)/celt_sources.mk
include $(LOCAL_PATH)/opus_headers.mk
include $(LOCAL_PATH)/opus_sources.mk
include $(LOCAL_PATH)/silk_headers.mk
include $(LOCAL_PATH)/silk_sources.mk
LOCAL_MODULE := libopus
LOCAL_ARM_MODE := arm
LOCAL_CFLAGS := -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT \
-DHAVE_LRINTF
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src \
$(LOCAL_PATH)/silk $(LOCAL_PATH)/celt \
$(LOCAL_PATH)/silk/fixed
LOCAL_SRC_FILES := $(CELT_SOURCES) $(OPUS_SOURCES) $(OPUS_SOURCES_FLOAT) \
$(SILK_SOURCES) $(SILK_SOURCES_FIXED)
ifneq ($(findstring armeabi-v7a, $(TARGET_ARCH_ABI)),)
LOCAL_SRC_FILES += $(CELT_SOURCES_ARM)
LOCAL_SRC_FILES += celt/arm/armopts_gnu.s.neon
LOCAL_SRC_FILES += $(subst .s,_gnu.s.neon,$(CELT_SOURCES_ARM_ASM))
LOCAL_CFLAGS += -DOPUS_ARM_ASM -DOPUS_ARM_INLINE_ASM -DOPUS_ARM_INLINE_EDSP \
-DOPUS_ARM_INLINE_MEDIA -DOPUS_ARM_INLINE_NEON \
-DOPUS_ARM_MAY_HAVE_NEON -DOPUS_ARM_MAY_HAVE_MEDIA \
-DOPUS_ARM_MAY_HAVE_EDSP
endif
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(BUILD_SHARED_LIBRARY)

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2014 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 "opus.h" // NOLINT
#include "opus_multistream.h" // NOLINT
#define LOG_TAG "libopus_native"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
__VA_ARGS__))
#define FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer_ext_opus_OpusDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
} \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer_ext_opus_OpusDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples.
static int channelCount;
FUNC(jlong, opusInit, jint sampleRate, jint channelCount, jint numStreams,
jint numCoupled, jint gain, jbyteArray jStreamMap) {
int status = OPUS_INVALID_STATE;
::channelCount = channelCount;
jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0);
uint8_t* streamMap = reinterpret_cast<uint8_t*>(streamMapBytes);
OpusMSDecoder* decoder = opus_multistream_decoder_create(
sampleRate, channelCount, numStreams, numCoupled, streamMap, &status);
env->ReleaseByteArrayElements(jStreamMap, streamMapBytes, 0);
if (!decoder || status != OPUS_OK) {
LOGE("Failed to create Opus Decoder; status=%s", opus_strerror(status));
return 0;
}
status = opus_multistream_decoder_ctl(decoder, OPUS_SET_GAIN(gain));
if (status != OPUS_OK) {
LOGE("Failed to set Opus header gain; status=%s", opus_strerror(status));
return 0;
}
return reinterpret_cast<intptr_t>(decoder);
}
FUNC(jint, opusDecode, jlong jDecoder, jobject jInputBuffer, jint inputSize,
jobject jOutputBuffer, jint outputSize) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
const uint8_t* inputBuffer =
reinterpret_cast<const uint8_t*>(
env->GetDirectBufferAddress(jInputBuffer));
int16_t* outputBuffer = reinterpret_cast<int16_t*>(
env->GetDirectBufferAddress(jOutputBuffer));
int sampleCount = opus_multistream_decode(decoder, inputBuffer, inputSize,
outputBuffer, outputSize, 0);
return (sampleCount < 0) ? sampleCount
: sampleCount * kBytesPerSample * channelCount;
}
FUNC(jint, opusGetRequiredOutputBufferSize, jobject jInputBuffer,
jint inputSize, jint sampleRate) {
const uint8_t* inputBuffer = reinterpret_cast<const uint8_t*>(
env->GetDirectBufferAddress(jInputBuffer));
const int32_t sampleCount =
opus_packet_get_nb_samples(inputBuffer, inputSize, sampleRate);
return sampleCount * kBytesPerSample * channelCount;
}
FUNC(void, opusClose, jlong jDecoder) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
opus_multistream_decoder_destroy(decoder);
}
FUNC(void, opusReset, jlong jDecoder) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
}
FUNC(jstring, getLibopusVersion) {
return env->NewStringUTF(opus_get_version_string());
}
FUNC(jstring, opusGetErrorMessage, jint errorCode) {
return env->NewStringUTF(opus_strerror(errorCode));
}

View File

@ -0,0 +1,6 @@
# Proguard rules specific to the Opus extension.
# This prevents the names of native methods from being obfuscated.
-keepclasseswithmembernames class * {
native <methods>;
}

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

@ -0,0 +1,2 @@
This file is needed to make sure the res directory is present.
The file is ignored by the Android toolchain because its name starts with a dot.

View File

@ -46,7 +46,7 @@ public abstract class SubtitleParser extends
@Override
protected final ParserException decode(SubtitleInputBuffer inputBuffer,
SubtitleOutputBuffer outputBuffer) {
SubtitleOutputBuffer outputBuffer, boolean reset) {
try {
Subtitle subtitle = decode(inputBuffer.data.array(), inputBuffer.size);
outputBuffer.setOutput(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs);

View File

@ -52,7 +52,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
private I dequeuedInputBuffer;
private E exception;
private boolean flushDecodedOutputBuffer;
private boolean flushed;
private boolean released;
/**
@ -138,7 +138,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
@Override
public final void flush() {
synchronized (lock) {
flushDecodedOutputBuffer = true;
flushed = true;
if (dequeuedInputBuffer != null) {
releaseInputBufferInternal(dequeuedInputBuffer);
dequeuedInputBuffer = null;
@ -203,6 +203,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
private boolean decode() throws InterruptedException {
I inputBuffer;
O outputBuffer;
boolean resetDecoder;
// Wait until we have an input buffer to decode, and an output buffer to decode into.
synchronized (lock) {
@ -214,7 +215,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
}
inputBuffer = queuedInputBuffers.removeFirst();
outputBuffer = availableOutputBuffers[--availableOutputBufferCount];
flushDecodedOutputBuffer = false;
resetDecoder = flushed;
flushed = false;
}
if (inputBuffer.isEndOfStream()) {
@ -223,7 +225,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
if (inputBuffer.isDecodeOnly()) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
exception = decode(inputBuffer, outputBuffer);
exception = decode(inputBuffer, outputBuffer, resetDecoder);
if (exception != null) {
// Memory barrier to ensure that the decoder exception is visible from the playback thread.
synchronized (lock) {}
@ -232,7 +234,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
}
synchronized (lock) {
if (flushDecodedOutputBuffer || outputBuffer.isDecodeOnly()) {
if (flushed || outputBuffer.isDecodeOnly()) {
// If a flush occurred while decoding or the buffer was only for decoding (not presentation)
// then make the output buffer available again rather than queueing it to be consumed.
releaseOutputBufferInternal(outputBuffer);
@ -279,8 +281,9 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
* {@link C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on
* {@code inputBuffer}, but the decoder may set/unset the flag if required. If the flag is set
* after this method returns, any output will not be presented.
* @param reset True if the decoder must be reset before decoding.
* @return A decoder exception if an error occurred, or null if decoding was successful.
*/
protected abstract E decode(I inputBuffer, O outputBuffer);
protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset);
}

View File

@ -13,8 +13,10 @@
// limitations under the License.
include ':library'
include ':demo'
include ':extension-opus'
include ':extension-okhttp'
include ':extension-flac'
project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus')
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac')