Add FFmpeg extension.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=122978403
This commit is contained in:
parent
9981a83c05
commit
ddde6edb92
3
.gitignore
vendored
3
.gitignore
vendored
@ -51,3 +51,6 @@ extensions/opus/src/main/jni/libopus
|
||||
|
||||
# FLAC extension
|
||||
extensions/flac/src/main/jni/flac
|
||||
|
||||
# FFmpeg extension
|
||||
extensions/ffmpeg/src/main/jni/ffmpeg
|
||||
|
@ -44,7 +44,8 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
demo_extCompile project(path: ':extension-opus')
|
||||
demo_extCompile project(path: ':extension-ffmpeg')
|
||||
demo_extCompile project(path: ':extension-flac')
|
||||
demo_extCompile project(path: ':extension-opus')
|
||||
demo_extCompile project(path: ':extension-vp9')
|
||||
}
|
||||
|
@ -495,24 +495,37 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
||||
Constructor<?> constructor = clazz.getConstructor(boolean.class, Handler.class,
|
||||
VideoTrackRendererEventListener.class, int.class);
|
||||
renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, this, 50));
|
||||
Log.i(TAG, "Loaded LibvpxVideoTrackRenderer.");
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "can't load LibvpxVideoTrackRenderer.");
|
||||
// Expected if the app was built without the extension.
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> clazz =
|
||||
Class.forName("com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer");
|
||||
renderersList.add((TrackRenderer) clazz.newInstance());
|
||||
Log.i(TAG, "Loaded LibopusAudioTrackRenderer.");
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "can't load LibopusAudioTrackRenderer.");
|
||||
// Expected if the app was built without the extension.
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> clazz =
|
||||
Class.forName("com.google.android.exoplayer.ext.flac.LibflacAudioTrackRenderer");
|
||||
renderersList.add((TrackRenderer) clazz.newInstance());
|
||||
Log.i(TAG, "Loaded LibflacAudioTrackRenderer.");
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "can't load LibflacAudioTrackRenderer.");
|
||||
// Expected if the app was built without the extension.
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> clazz =
|
||||
Class.forName("com.google.android.exoplayer.ext.ffmpeg.FfmpegAudioTrackRenderer");
|
||||
renderersList.add((TrackRenderer) clazz.newInstance());
|
||||
Log.i(TAG, "Loaded FfmpegAudioTrackRenderer.");
|
||||
} catch (Exception e) {
|
||||
// Expected if the app was built without the extension.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
96
extensions/ffmpeg/README.md
Normal file
96
extensions/ffmpeg/README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# FfmpegAudioTrackRenderer #
|
||||
|
||||
## Description ##
|
||||
|
||||
The FFmpeg extension is a [TrackRenderer][] implementation that uses FFmpeg to
|
||||
decode audio.
|
||||
|
||||
[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)"
|
||||
FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main"
|
||||
```
|
||||
|
||||
* Download the [Android NDK][] and set its location in an environment variable:
|
||||
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
```
|
||||
|
||||
* Fetch and build ffmpeg.
|
||||
|
||||
For example, to fetch and build for armv7a:
|
||||
|
||||
```
|
||||
cd "${FFMPEG_EXT_PATH}/jni" && \
|
||||
git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \
|
||||
./configure \
|
||||
--prefix=../../jniLibs/armeabi-v7a \
|
||||
--arch=arm \
|
||||
--cpu=armv7-a \
|
||||
--cross-prefix="${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-" \
|
||||
--target-os=android \
|
||||
--sysroot="${NDK_PATH}/platforms/android-9/arch-arm/" \
|
||||
--extra-cflags="-march=armv7-a -mfloat-abi=softfp" \
|
||||
--extra-ldflags="-Wl,--fix-cortex-a8" \
|
||||
--extra-ldexeflags=-pie \
|
||||
--disable-static \
|
||||
--enable-shared \
|
||||
--disable-doc \
|
||||
--disable-programs \
|
||||
--disable-everything \
|
||||
--disable-avdevice \
|
||||
--disable-avformat \
|
||||
--disable-swscale \
|
||||
--disable-postproc \
|
||||
--disable-avfilter \
|
||||
--disable-symver \
|
||||
--enable-avresample \
|
||||
--enable-decoder=vorbis \
|
||||
--enable-decoder=opus \
|
||||
--enable-decoder=flac \
|
||||
&& \
|
||||
make -j4 && \
|
||||
make install-libs
|
||||
```
|
||||
|
||||
* Build the JNI native libraries.
|
||||
|
||||
```
|
||||
cd "${FFMPEG_EXT_PATH}"/jni && \
|
||||
${NDK_PATH}/ndk-build APP_ABI=armeabi-v7a -j4
|
||||
```
|
||||
|
||||
TODO: Add instructions for other ABIs.
|
||||
|
||||
* In your project, you can add a dependency on the extension by using a rule
|
||||
like this:
|
||||
|
||||
```
|
||||
// in settings.gradle
|
||||
include ':..:ExoPlayer:library'
|
||||
include ':..:ExoPlayer:extension-ffmpeg'
|
||||
|
||||
// in build.gradle
|
||||
dependencies {
|
||||
compile project(':..:ExoPlayer:library')
|
||||
compile project(':..:ExoPlayer:extension-ffmpeg')
|
||||
}
|
||||
```
|
||||
|
||||
* Now, when you build your app, the extension will be built and the native
|
||||
libraries will be packaged along with the APK.
|
44
extensions/ffmpeg/build.gradle
Normal file
44
extensions/ffmpeg/build.gradle
Normal file
@ -0,0 +1,44 @@
|
||||
// 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/ffmpeg/src/main/.classpath
Normal file
10
extensions/ffmpeg/src/main/.classpath
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<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="/ExoPlayerLib"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
54
extensions/ffmpeg/src/main/.cproject
Normal file
54
extensions/ffmpeg/src/main/.cproject
Normal file
@ -0,0 +1,54 @@
|
||||
<?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-FFmpeg}/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="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-FFmpeg"/>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
</cproject>
|
97
extensions/ffmpeg/src/main/.project
Normal file
97
extensions/ffmpeg/src/main/.project
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-FFmpeg</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>?children?</key>
|
||||
<value>?name?=outputEntries\|?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\||</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>?name?</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.append_environment</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildArguments</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildCommand</key>
|
||||
<value>ndk-build</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
|
||||
<value>clean</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.contents</key>
|
||||
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
|
||||
<value>false</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.stopOnError</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
22
extensions/ffmpeg/src/main/AndroidManifest.xml
Normal file
22
extensions/ffmpeg/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.ffmpeg">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="23"/>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.ffmpeg;
|
||||
|
||||
import com.google.android.exoplayer.AudioTrackRendererEventListener;
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.extensions.AudioDecoderTrackRenderer;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* Decodes and renders audio using FFmpeg.
|
||||
*/
|
||||
public final class FfmpegAudioTrackRenderer extends AudioDecoderTrackRenderer {
|
||||
|
||||
private static final int NUM_BUFFERS = 16;
|
||||
private static final int INITIAL_INPUT_BUFFER_SIZE = 960 * 6;
|
||||
|
||||
private FfmpegDecoder decoder;
|
||||
|
||||
public FfmpegAudioTrackRenderer() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public FfmpegAudioTrackRenderer(Handler eventHandler,
|
||||
AudioTrackRendererEventListener eventListener) {
|
||||
super(eventHandler, eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int supportsFormat(Format format) {
|
||||
if (!FfmpegDecoder.IS_AVAILABLE) {
|
||||
return FORMAT_UNSUPPORTED_TYPE;
|
||||
}
|
||||
String mimeType = format.sampleMimeType;
|
||||
return FfmpegDecoder.supportsFormat(mimeType) ? FORMAT_HANDLED
|
||||
: MimeTypes.isAudio(mimeType) ? FORMAT_UNSUPPORTED_SUBTYPE : FORMAT_UNSUPPORTED_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FfmpegDecoder createDecoder(Format format) throws FfmpegDecoderException {
|
||||
decoder = new FfmpegDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
|
||||
format.sampleMimeType, format.initializationData);
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getOutputFormat() {
|
||||
int channelCount = decoder.getChannelCount();
|
||||
int sampleRate = decoder.getSampleRate();
|
||||
return Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, Format.NO_VALUE,
|
||||
Format.NO_VALUE, channelCount, sampleRate, C.ENCODING_PCM_16BIT, null, null, 0, null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.ffmpeg;
|
||||
|
||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer.extensions.SimpleDecoder;
|
||||
import com.google.android.exoplayer.extensions.SimpleOutputBuffer;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JNI wrapper for FFmpeg. Only audio decoding is supported.
|
||||
*/
|
||||
/* package */ final class FfmpegDecoder extends
|
||||
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> {
|
||||
|
||||
private static final String TAG = "FfmpegDecoder";
|
||||
|
||||
/**
|
||||
* Whether the underlying FFmpeg library is available.
|
||||
*/
|
||||
public static final boolean IS_AVAILABLE;
|
||||
static {
|
||||
boolean isAvailable;
|
||||
try {
|
||||
System.loadLibrary("avutil");
|
||||
System.loadLibrary("avresample");
|
||||
System.loadLibrary("avcodec");
|
||||
System.loadLibrary("ffmpeg");
|
||||
isAvailable = true;
|
||||
} catch (UnsatisfiedLinkError exception) {
|
||||
isAvailable = false;
|
||||
}
|
||||
IS_AVAILABLE = isAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this decoder can decode samples in the specified MIME type.
|
||||
*/
|
||||
public static boolean supportsFormat(String mimeType) {
|
||||
String codecName = getCodecName(mimeType);
|
||||
return codecName != null && nativeHasDecoder(codecName);
|
||||
}
|
||||
|
||||
// Space for 64 ms of 6 channel 48 kHz 16-bit PCM audio.
|
||||
private static final int OUTPUT_BUFFER_SIZE = 1536 * 6 * 2 * 2;
|
||||
|
||||
private final String codecName;
|
||||
private final byte[] extraData;
|
||||
|
||||
private long nativeContext; // May be reassigned on resetting the codec.
|
||||
private boolean hasOutputFormat;
|
||||
private volatile int channelCount;
|
||||
private volatile int sampleRate;
|
||||
|
||||
public FfmpegDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
|
||||
String mimeType, List<byte[]> initializationData) throws FfmpegDecoderException {
|
||||
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
|
||||
codecName = getCodecName(mimeType);
|
||||
extraData = getExtraData(mimeType, initializationData);
|
||||
nativeContext = nativeInitialize(codecName, extraData);
|
||||
if (nativeContext == 0) {
|
||||
throw new FfmpegDecoderException("Initialization failed.");
|
||||
}
|
||||
setInitialInputBufferSize(initialInputBufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ffmpeg" + nativeGetFfmpegVersion() + "-" + codecName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderInputBuffer createInputBuffer() {
|
||||
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleOutputBuffer createOutputBuffer() {
|
||||
return new SimpleOutputBuffer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FfmpegDecoderException decode(DecoderInputBuffer inputBuffer,
|
||||
SimpleOutputBuffer outputBuffer, boolean reset) {
|
||||
if (reset) {
|
||||
nativeContext = nativeReset(nativeContext, extraData);
|
||||
if (nativeContext == 0) {
|
||||
return new FfmpegDecoderException("Error resetting (see logcat).");
|
||||
}
|
||||
}
|
||||
ByteBuffer inputData = inputBuffer.data;
|
||||
int inputSize = inputData.limit();
|
||||
ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, OUTPUT_BUFFER_SIZE);
|
||||
int result = nativeDecode(nativeContext, inputData, inputSize, outputData, OUTPUT_BUFFER_SIZE);
|
||||
if (result < 0) {
|
||||
return new FfmpegDecoderException("Error decoding (see logcat). Code: " + result);
|
||||
}
|
||||
if (!hasOutputFormat) {
|
||||
channelCount = nativeGetChannelCount(nativeContext);
|
||||
sampleRate = nativeGetSampleRate(nativeContext);
|
||||
hasOutputFormat = true;
|
||||
}
|
||||
outputBuffer.data.position(0);
|
||||
outputBuffer.data.limit(result);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
super.release();
|
||||
nativeRelease(nativeContext);
|
||||
nativeContext = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel count of output audio. May only be called after {@link #decode}.
|
||||
*/
|
||||
public int getChannelCount() {
|
||||
return channelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sample rate of output audio. May only be called after {@link #decode}.
|
||||
*/
|
||||
public int getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
|
||||
* not required.
|
||||
*/
|
||||
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
|
||||
switch (mimeType) {
|
||||
case MimeTypes.AUDIO_AAC:
|
||||
case MimeTypes.AUDIO_OPUS:
|
||||
return initializationData.get(0);
|
||||
case MimeTypes.AUDIO_VORBIS:
|
||||
byte[] header0 = initializationData.get(0);
|
||||
byte[] header1 = initializationData.get(1);
|
||||
byte[] extraData = new byte[header0.length + header1.length + 6];
|
||||
extraData[0] = (byte) (header0.length >> 8);
|
||||
extraData[1] = (byte) (header0.length & 0xFF);
|
||||
System.arraycopy(header0, 0, extraData, 2, header0.length);
|
||||
extraData[header0.length + 2] = 0;
|
||||
extraData[header0.length + 3] = 0;
|
||||
extraData[header0.length + 4] = (byte) (header1.length >> 8);
|
||||
extraData[header0.length + 5] = (byte) (header1.length & 0xFF);
|
||||
System.arraycopy(header1, 0, extraData, header0.length + 6, header1.length);
|
||||
return extraData;
|
||||
default:
|
||||
// Other codecs do not require extra data.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. The codec
|
||||
* can only be used if {@link #nativeHasDecoder(String)} returns true for the returned codec name.
|
||||
*/
|
||||
private static String getCodecName(String mimeType) {
|
||||
switch (mimeType) {
|
||||
case MimeTypes.AUDIO_AAC:
|
||||
return "aac";
|
||||
case MimeTypes.AUDIO_MPEG:
|
||||
case MimeTypes.AUDIO_MPEG_L1:
|
||||
case MimeTypes.AUDIO_MPEG_L2:
|
||||
return "mp3";
|
||||
case MimeTypes.AUDIO_AC3:
|
||||
return "ac3";
|
||||
case MimeTypes.AUDIO_E_AC3:
|
||||
return "eac3";
|
||||
case MimeTypes.AUDIO_TRUEHD:
|
||||
return "truehd";
|
||||
case MimeTypes.AUDIO_DTS:
|
||||
case MimeTypes.AUDIO_DTS_HD:
|
||||
return "dca";
|
||||
case MimeTypes.AUDIO_VORBIS:
|
||||
return "vorbis";
|
||||
case MimeTypes.AUDIO_OPUS:
|
||||
return "opus";
|
||||
case MimeTypes.AUDIO_AMR_NB:
|
||||
return "amrnb";
|
||||
case MimeTypes.AUDIO_AMR_WB:
|
||||
return "amrwb";
|
||||
case MimeTypes.AUDIO_FLAC:
|
||||
return "flac";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static native String nativeGetFfmpegVersion();
|
||||
private static native boolean nativeHasDecoder(String codecName);
|
||||
private native long nativeInitialize(String codecName, byte[] extraData);
|
||||
private native int nativeDecode(long context, ByteBuffer inputData, int inputSize,
|
||||
ByteBuffer outputData, int outputSize);
|
||||
private native int nativeGetChannelCount(long context);
|
||||
private native int nativeGetSampleRate(long context);
|
||||
private native long nativeReset(long context, byte[] extraData);
|
||||
private native void nativeRelease(long context);
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.ffmpeg;
|
||||
|
||||
import com.google.android.exoplayer.extensions.AudioDecoderException;
|
||||
|
||||
/**
|
||||
* Thrown when an FFmpeg decoder error occurs.
|
||||
*/
|
||||
public final class FfmpegDecoderException extends AudioDecoderException {
|
||||
|
||||
/* package */ FfmpegDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
45
extensions/ffmpeg/src/main/jni/Android.mk
Normal file
45
extensions/ffmpeg/src/main/jni/Android.mk
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.
|
||||
#
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavcodec
|
||||
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavutil
|
||||
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavresample
|
||||
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libswresample
|
||||
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := ffmpeg
|
||||
LOCAL_SRC_FILES := ffmpeg_jni.cc
|
||||
LOCAL_C_INCLUDES := ffmpeg
|
||||
LOCAL_SHARED_LIBRARIES := libavcodec libavresample libavutil libswresample
|
||||
LOCAL_LDLIBS := -L../jniLibs/$(TARGET_ARCH_ABI)/lib -llog
|
||||
include $(BUILD_SHARED_LIBRARY)
|
20
extensions/ffmpeg/src/main/jni/Application.mk
Normal file
20
extensions/ffmpeg/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
|
324
extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc
Normal file
324
extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc
Normal file
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <android/log.h>
|
||||
|
||||
extern "C" {
|
||||
#ifdef __cplusplus
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#ifdef _STDINT_H
|
||||
#undef _STDINT_H
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavresample/avresample.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#define LOG_TAG "ffmpeg_jni"
|
||||
#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_ffmpeg_FfmpegDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
|
||||
} \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_ffmpeg_FfmpegDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
|
||||
|
||||
#define ERROR_STRING_BUFFER_LENGTH 256
|
||||
|
||||
// Request a format corresponding to AudioFormat.ENCODING_PCM_16BIT.
|
||||
static const AVSampleFormat OUTPUT_FORMAT = AV_SAMPLE_FMT_S16;
|
||||
|
||||
/**
|
||||
* Returns the AVCodec with the specified name, or NULL if it is not available.
|
||||
*/
|
||||
AVCodec *getCodecByName(JNIEnv* env, jstring codecName);
|
||||
|
||||
/**
|
||||
* Allocates and opens a new AVCodecContext for the specified codec, passing the
|
||||
* provided extraData as initialization data for the decoder if it is non-NULL.
|
||||
* Returns the created context.
|
||||
*/
|
||||
AVCodecContext *createContext(JNIEnv *env, AVCodec *codec,
|
||||
jbyteArray extraData);
|
||||
|
||||
/**
|
||||
* Decodes the packet into the output buffer, returning the number of bytes
|
||||
* written, or a negative value in the case of an error.
|
||||
*/
|
||||
int decodePacket(AVCodecContext *context, AVPacket *packet,
|
||||
uint8_t *outputBuffer, int outputSize);
|
||||
|
||||
/**
|
||||
* Outputs a log message describing the avcodec error number.
|
||||
*/
|
||||
void logError(const char *functionName, int errorNumber);
|
||||
|
||||
/**
|
||||
* Releases the specified context.
|
||||
*/
|
||||
void releaseContext(AVCodecContext *context);
|
||||
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
avcodec_register_all();
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
FUNC(jstring, nativeGetFfmpegVersion) {
|
||||
return env->NewStringUTF(LIBAVCODEC_IDENT);
|
||||
}
|
||||
|
||||
FUNC(jboolean, nativeHasDecoder, jstring codecName) {
|
||||
return getCodecByName(env, codecName) != NULL;
|
||||
}
|
||||
|
||||
FUNC(jlong, nativeInitialize, jstring codecName, jbyteArray extraData) {
|
||||
AVCodec *codec = getCodecByName(env, codecName);
|
||||
if (!codec) {
|
||||
LOGE("Codec not found.");
|
||||
return 0L;
|
||||
}
|
||||
return (jlong) createContext(env, codec, extraData);
|
||||
}
|
||||
|
||||
FUNC(jint, nativeDecode, jlong context, jobject inputData, jint inputSize,
|
||||
jobject outputData, jint outputSize) {
|
||||
if (!context) {
|
||||
LOGE("Context must be non-NULL.");
|
||||
return -1;
|
||||
}
|
||||
if (!inputData || !outputData) {
|
||||
LOGE("Input and output buffers must be non-NULL.");
|
||||
return -1;
|
||||
}
|
||||
if (inputSize < 0) {
|
||||
LOGE("Invalid input buffer size: %d.", inputSize);
|
||||
return -1;
|
||||
}
|
||||
if (outputSize < 0) {
|
||||
LOGE("Invalid output buffer length: %d", outputSize);
|
||||
return -1;
|
||||
}
|
||||
uint8_t *inputBuffer = (uint8_t *) env->GetDirectBufferAddress(inputData);
|
||||
uint8_t *outputBuffer = (uint8_t *) env->GetDirectBufferAddress(outputData);
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
packet.data = inputBuffer;
|
||||
packet.size = inputSize;
|
||||
return decodePacket((AVCodecContext *) context, &packet, outputBuffer,
|
||||
outputSize);
|
||||
}
|
||||
|
||||
FUNC(jint, nativeGetChannelCount, jlong context) {
|
||||
if (!context) {
|
||||
LOGE("Context must be non-NULL.");
|
||||
return -1;
|
||||
}
|
||||
return ((AVCodecContext *) context)->channels;
|
||||
}
|
||||
|
||||
FUNC(jint, nativeGetSampleRate, jlong context) {
|
||||
if (!context) {
|
||||
LOGE("Context must be non-NULL.");
|
||||
return -1;
|
||||
}
|
||||
return ((AVCodecContext *) context)->sample_rate;
|
||||
}
|
||||
|
||||
FUNC(jlong, nativeReset, jlong jContext, jbyteArray extraData) {
|
||||
AVCodecContext *context = (AVCodecContext *) jContext;
|
||||
if (!context) {
|
||||
LOGE("Tried to reset without a context.");
|
||||
return 0L;
|
||||
}
|
||||
|
||||
AVCodecID codecId = context->codec_id;
|
||||
if (codecId == AV_CODEC_ID_TRUEHD) {
|
||||
// Release and recreate the context if the codec is TrueHD.
|
||||
// TODO: Figure out why flushing doesn't work for this codec.
|
||||
releaseContext(context);
|
||||
AVCodec *codec = avcodec_find_decoder(codecId);
|
||||
if (!codec) {
|
||||
LOGE("Unexpected error finding codec %d.", codecId);
|
||||
return 0L;
|
||||
}
|
||||
return (jlong) createContext(env, codec, extraData);
|
||||
}
|
||||
|
||||
avcodec_flush_buffers(context);
|
||||
return (jlong) context;
|
||||
}
|
||||
|
||||
FUNC(void, nativeRelease, jlong context) {
|
||||
if (context) {
|
||||
releaseContext((AVCodecContext *) context);
|
||||
}
|
||||
}
|
||||
|
||||
AVCodec *getCodecByName(JNIEnv* env, jstring codecName) {
|
||||
if (!codecName) {
|
||||
return NULL;
|
||||
}
|
||||
const char *codecNameChars = env->GetStringUTFChars(codecName, NULL);
|
||||
AVCodec *codec = avcodec_find_decoder_by_name(codecNameChars);
|
||||
env->ReleaseStringUTFChars(codecName, codecNameChars);
|
||||
return codec;
|
||||
}
|
||||
|
||||
AVCodecContext *createContext(JNIEnv *env, AVCodec *codec,
|
||||
jbyteArray extraData) {
|
||||
AVCodecContext *context = avcodec_alloc_context3(codec);
|
||||
if (!context) {
|
||||
LOGE("Failed to allocate context.");
|
||||
return NULL;
|
||||
}
|
||||
context->request_sample_fmt = OUTPUT_FORMAT;
|
||||
if (extraData) {
|
||||
jsize size = env->GetArrayLength(extraData);
|
||||
context->extradata_size = size;
|
||||
context->extradata =
|
||||
(uint8_t *) av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!context->extradata) {
|
||||
LOGE("Failed to allocate extradata.");
|
||||
releaseContext(context);
|
||||
return NULL;
|
||||
}
|
||||
env->GetByteArrayRegion(extraData, 0, size, (jbyte *) context->extradata);
|
||||
}
|
||||
int result = avcodec_open2(context, codec, NULL);
|
||||
if (result < 0) {
|
||||
logError("avcodec_open2", result);
|
||||
releaseContext(context);
|
||||
return NULL;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
int decodePacket(AVCodecContext *context, AVPacket *packet,
|
||||
uint8_t *outputBuffer, int outputSize) {
|
||||
int result = 0;
|
||||
// Queue input data.
|
||||
result = avcodec_send_packet(context, packet);
|
||||
if (result) {
|
||||
logError("avcodec_send_packet", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Dequeue output data until it runs out.
|
||||
int outSize = 0;
|
||||
while (true) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
LOGE("Failed to allocate output frame.");
|
||||
return -1;
|
||||
}
|
||||
result = avcodec_receive_frame(context, frame);
|
||||
if (result) {
|
||||
av_frame_free(&frame);
|
||||
if (result == AVERROR(EAGAIN)) {
|
||||
break;
|
||||
}
|
||||
logError("avcodec_receive_frame", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Resample output.
|
||||
AVSampleFormat sampleFormat = context->sample_fmt;
|
||||
int channelCount = context->channels;
|
||||
int channelLayout = context->channel_layout;
|
||||
int sampleRate = context->sample_rate;
|
||||
int sampleCount = frame->nb_samples;
|
||||
int dataSize = av_samples_get_buffer_size(NULL, channelCount, sampleCount,
|
||||
sampleFormat, 1);
|
||||
AVAudioResampleContext *resampleContext;
|
||||
if (context->opaque) {
|
||||
resampleContext = (AVAudioResampleContext *)context->opaque;
|
||||
} else {
|
||||
resampleContext = avresample_alloc_context();
|
||||
av_opt_set_int(resampleContext, "in_channel_layout", channelLayout, 0);
|
||||
av_opt_set_int(resampleContext, "out_channel_layout", channelLayout, 0);
|
||||
av_opt_set_int(resampleContext, "in_sample_rate", sampleRate, 0);
|
||||
av_opt_set_int(resampleContext, "out_sample_rate", sampleRate, 0);
|
||||
av_opt_set_int(resampleContext, "in_sample_fmt", sampleFormat, 0);
|
||||
av_opt_set_int(resampleContext, "out_sample_fmt", OUTPUT_FORMAT, 0);
|
||||
result = avresample_open(resampleContext);
|
||||
if (result < 0) {
|
||||
logError("avresample_open", result);
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
context->opaque = resampleContext;
|
||||
}
|
||||
int inSampleSize = av_get_bytes_per_sample(sampleFormat);
|
||||
int outSampleSize = av_get_bytes_per_sample(OUTPUT_FORMAT);
|
||||
int outSamples = avresample_get_out_samples(resampleContext, sampleCount);
|
||||
int bufferOutSize = outSampleSize * channelCount * outSamples;
|
||||
if (outSize + bufferOutSize > outputSize) {
|
||||
LOGE("Output buffer size (%d) too small for output data (%d).",
|
||||
outputSize, outSize + bufferOutSize);
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
result = avresample_convert(resampleContext, &outputBuffer, bufferOutSize,
|
||||
outSamples, frame->data, frame->linesize[0],
|
||||
sampleCount);
|
||||
av_frame_free(&frame);
|
||||
if (result < 0) {
|
||||
logError("avresample_convert", result);
|
||||
return result;
|
||||
}
|
||||
int available = avresample_available(resampleContext);
|
||||
if (available != 0) {
|
||||
LOGE("Expected no samples remaining after resampling, but found %d.",
|
||||
available);
|
||||
return -1;
|
||||
}
|
||||
outputBuffer += bufferOutSize;
|
||||
outSize += bufferOutSize;
|
||||
}
|
||||
return outSize;
|
||||
}
|
||||
|
||||
void logError(const char *functionName, int errorNumber) {
|
||||
char *buffer = (char *) malloc(ERROR_STRING_BUFFER_LENGTH * sizeof(char));
|
||||
av_strerror(errorNumber, buffer, ERROR_STRING_BUFFER_LENGTH);
|
||||
LOGE("Error in %s: %s", functionName, buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void releaseContext(AVCodecContext *context) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
AVAudioResampleContext *resampleContext;
|
||||
if (resampleContext = (AVAudioResampleContext *)context->opaque) {
|
||||
avresample_free(&resampleContext);
|
||||
context->opaque = NULL;
|
||||
}
|
||||
avcodec_free_context(&context);
|
||||
}
|
||||
|
6
extensions/ffmpeg/src/main/proguard.cfg
Normal file
6
extensions/ffmpeg/src/main/proguard.cfg
Normal file
@ -0,0 +1,6 @@
|
||||
# Proguard rules specific to the FFmpeg extension.
|
||||
|
||||
# This prevents the names of native methods from being obfuscated.
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
16
extensions/ffmpeg/src/main/project.properties
Normal file
16
extensions/ffmpeg/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
|
@ -17,8 +17,11 @@ include ':extension-opus'
|
||||
include ':extension-vp9'
|
||||
include ':extension-okhttp'
|
||||
include ':extension-flac'
|
||||
include ':extension-ffmpeg'
|
||||
|
||||
project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus')
|
||||
project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9')
|
||||
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
|
||||
project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac')
|
||||
project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg')
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user