Copy opus extension v1->v2
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=119643009
This commit is contained in:
parent
767c7ab169
commit
192f566a1b
@ -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
77
extensions/opus/README.md
Normal 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`.
|
45
extensions/opus/build.gradle
Normal file
45
extensions/opus/build.gradle
Normal 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')
|
||||
}
|
||||
|
10
extensions/opus/src/main/.classpath
Normal file
10
extensions/opus/src/main/.classpath
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
57
extensions/opus/src/main/.cproject
Normal file
57
extensions/opus/src/main/.cproject
Normal 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>
|
97
extensions/opus/src/main/.project
Normal file
97
extensions/opus/src/main/.project
Normal 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>
|
@ -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
|
22
extensions/opus/src/main/AndroidManifest.xml
Normal file
22
extensions/opus/src/main/AndroidManifest.xml
Normal 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>
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
33
extensions/opus/src/main/jni/Android.mk
Normal file
33
extensions/opus/src/main/jni/Android.mk
Normal 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)
|
20
extensions/opus/src/main/jni/Application.mk
Normal file
20
extensions/opus/src/main/jni/Application.mk
Normal 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
|
47
extensions/opus/src/main/jni/convert_android_asm.sh
Executable file
47
extensions/opus/src/main/jni/convert_android_asm.sh
Executable 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."
|
50
extensions/opus/src/main/jni/libopus.mk
Normal file
50
extensions/opus/src/main/jni/libopus.mk
Normal 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)
|
111
extensions/opus/src/main/jni/opus_jni.cc
Normal file
111
extensions/opus/src/main/jni/opus_jni.cc
Normal 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));
|
||||
}
|
6
extensions/opus/src/main/proguard.cfg
Normal file
6
extensions/opus/src/main/proguard.cfg
Normal 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>;
|
||||
}
|
16
extensions/opus/src/main/project.properties
Normal file
16
extensions/opus/src/main/project.properties
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-23
|
||||
android.library=true
|
||||
android.library.reference.1=../../../../library/src/main
|
2
extensions/opus/src/main/res/.README.txt
Normal file
2
extensions/opus/src/main/res/.README.txt
Normal 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.
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user