mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
|
# FLAC extension
|
||||||
extensions/flac/src/main/jni/flac
|
extensions/flac/src/main/jni/flac
|
||||||
|
|
||||||
|
# FFmpeg extension
|
||||||
|
extensions/ffmpeg/src/main/jni/ffmpeg
|
||||||
|
@ -44,7 +44,8 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':library')
|
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-flac')
|
||||||
|
demo_extCompile project(path: ':extension-opus')
|
||||||
demo_extCompile project(path: ':extension-vp9')
|
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,
|
Constructor<?> constructor = clazz.getConstructor(boolean.class, Handler.class,
|
||||||
VideoTrackRendererEventListener.class, int.class);
|
VideoTrackRendererEventListener.class, int.class);
|
||||||
renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, this, 50));
|
renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, this, 50));
|
||||||
|
Log.i(TAG, "Loaded LibvpxVideoTrackRenderer.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.i(TAG, "can't load LibvpxVideoTrackRenderer.");
|
// Expected if the app was built without the extension.
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> clazz =
|
Class<?> clazz =
|
||||||
Class.forName("com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer");
|
Class.forName("com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer");
|
||||||
renderersList.add((TrackRenderer) clazz.newInstance());
|
renderersList.add((TrackRenderer) clazz.newInstance());
|
||||||
|
Log.i(TAG, "Loaded LibopusAudioTrackRenderer.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.i(TAG, "can't load LibopusAudioTrackRenderer.");
|
// Expected if the app was built without the extension.
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> clazz =
|
Class<?> clazz =
|
||||||
Class.forName("com.google.android.exoplayer.ext.flac.LibflacAudioTrackRenderer");
|
Class.forName("com.google.android.exoplayer.ext.flac.LibflacAudioTrackRenderer");
|
||||||
renderersList.add((TrackRenderer) clazz.newInstance());
|
renderersList.add((TrackRenderer) clazz.newInstance());
|
||||||
|
Log.i(TAG, "Loaded LibflacAudioTrackRenderer.");
|
||||||
} catch (Exception e) {
|
} 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-vp9'
|
||||||
include ':extension-okhttp'
|
include ':extension-okhttp'
|
||||||
include ':extension-flac'
|
include ':extension-flac'
|
||||||
|
include ':extension-ffmpeg'
|
||||||
|
|
||||||
project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus')
|
project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus')
|
||||||
project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9')
|
project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9')
|
||||||
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
|
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
|
||||||
project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac')
|
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