Allow replacement of libraries for Opus/Flac/Ffmpeg too

- Also make some of the naming more concise + misc style cleanup.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132899979
This commit is contained in:
olly 2016-09-12 11:52:41 -07:00 committed by Oliver Woodman
parent c46c6f18c2
commit de67fa5017
20 changed files with 432 additions and 300 deletions

View file

@ -62,11 +62,11 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
@Override
public int supportsFormat(Format format) {
if (!FfmpegDecoder.IS_AVAILABLE) {
if (!FfmpegLibrary.isAvailable()) {
return FORMAT_UNSUPPORTED_TYPE;
}
String mimeType = format.sampleMimeType;
return FfmpegDecoder.supportsFormat(mimeType) ? FORMAT_HANDLED
return FfmpegLibrary.supportsFormat(mimeType) ? FORMAT_HANDLED
: MimeTypes.isAudio(mimeType) ? FORMAT_UNSUPPORTED_SUBTYPE : FORMAT_UNSUPPORTED_TYPE;
}

View file

@ -23,39 +23,11 @@ import java.nio.ByteBuffer;
import java.util.List;
/**
* JNI wrapper for FFmpeg. Only audio decoding is supported.
* FFmpeg audio decoder.
*/
/* 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;
@ -70,9 +42,12 @@ import java.util.List;
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);
if (!FfmpegLibrary.isAvailable()) {
throw new FfmpegDecoderException("Failed to load decoder native libraries.");
}
codecName = FfmpegLibrary.getCodecName(mimeType);
extraData = getExtraData(mimeType, initializationData);
nativeContext = nativeInitialize(codecName, extraData);
nativeContext = ffmpegInitialize(codecName, extraData);
if (nativeContext == 0) {
throw new FfmpegDecoderException("Initialization failed.");
}
@ -81,7 +56,7 @@ import java.util.List;
@Override
public String getName() {
return "ffmpeg" + nativeGetFfmpegVersion() + "-" + codecName;
return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName;
}
@Override
@ -98,7 +73,7 @@ import java.util.List;
public FfmpegDecoderException decode(DecoderInputBuffer inputBuffer,
SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) {
nativeContext = nativeReset(nativeContext, extraData);
nativeContext = ffmpegReset(nativeContext, extraData);
if (nativeContext == 0) {
return new FfmpegDecoderException("Error resetting (see logcat).");
}
@ -106,13 +81,13 @@ import java.util.List;
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);
int result = ffmpegDecode(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);
channelCount = ffmpegGetChannelCount(nativeContext);
sampleRate = ffmpegGetSampleRate(nativeContext);
hasOutputFormat = true;
}
outputBuffer.data.position(0);
@ -123,7 +98,7 @@ import java.util.List;
@Override
public void release() {
super.release();
nativeRelease(nativeContext);
ffmpegRelease(nativeContext);
nativeContext = 0;
}
@ -169,50 +144,12 @@ import java.util.List;
}
}
/**
* 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,
private native long ffmpegInitialize(String codecName, byte[] extraData);
private native int ffmpegDecode(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);
private native int ffmpegGetChannelCount(long context);
private native int ffmpegGetSampleRate(long context);
private native long ffmpegReset(long context, byte[] extraData);
private native void ffmpegRelease(long context);
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.ffmpeg;
import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.android.exoplayer2.util.MimeTypes;
/**
* Configures and queries the underlying native library.
*/
public final class FfmpegLibrary {
private static final LibraryLoader LOADER =
new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg");
private FfmpegLibrary() {}
/**
* Override the names of the FFmpeg native libraries. If an application wishes to call this
* method, it must do so before calling any other method defined by this class, and before
* instantiating a {@link FfmpegAudioRenderer} instance.
*/
public static void setLibraries(String... libraries) {
LOADER.setLibraries(libraries);
}
/**
* Returns whether the underlying library is available, loading it if necessary.
*/
public static boolean isAvailable() {
return LOADER.isAvailable();
}
/**
* Returns the version of the underlying library if available, or null otherwise.
*/
public static String getVersion() {
return isAvailable() ? ffmpegGetVersion() : null;
}
/**
* Returns whether the underlying library supports the specified MIME type.
*/
public static boolean supportsFormat(String mimeType) {
if (!isAvailable()) {
return false;
}
String codecName = getCodecName(mimeType);
return codecName != null && ffmpegHasDecoder(codecName);
}
/**
* Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}.
*/
/* package */ 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 ffmpegGetVersion();
private static native boolean ffmpegHasDecoder(String codecName);
}

View file

@ -34,7 +34,8 @@ extern "C" {
#define LOG_TAG "ffmpeg_jni"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
__VA_ARGS__))
#define FUNC(RETURN_TYPE, NAME, ...) \
#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegDecoder_ ## NAME \
@ -44,6 +45,16 @@ extern "C" {
Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
} \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
#define ERROR_STRING_BUFFER_LENGTH 256
// Request a format corresponding to AudioFormat.ENCODING_PCM_16BIT.
@ -88,15 +99,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_VERSION_1_6;
}
FUNC(jstring, nativeGetFfmpegVersion) {
LIBRARY_FUNC(jstring, ffmpegGetVersion) {
return env->NewStringUTF(LIBAVCODEC_IDENT);
}
FUNC(jboolean, nativeHasDecoder, jstring codecName) {
LIBRARY_FUNC(jboolean, ffmpegHasDecoder, jstring codecName) {
return getCodecByName(env, codecName) != NULL;
}
FUNC(jlong, nativeInitialize, jstring codecName, jbyteArray extraData) {
DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, jbyteArray extraData) {
AVCodec *codec = getCodecByName(env, codecName);
if (!codec) {
LOGE("Codec not found.");
@ -105,8 +116,8 @@ FUNC(jlong, nativeInitialize, jstring codecName, jbyteArray extraData) {
return (jlong) createContext(env, codec, extraData);
}
FUNC(jint, nativeDecode, jlong context, jobject inputData, jint inputSize,
jobject outputData, jint outputSize) {
DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
jint inputSize, jobject outputData, jint outputSize) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@ -133,7 +144,7 @@ FUNC(jint, nativeDecode, jlong context, jobject inputData, jint inputSize,
outputSize);
}
FUNC(jint, nativeGetChannelCount, jlong context) {
DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@ -141,7 +152,7 @@ FUNC(jint, nativeGetChannelCount, jlong context) {
return ((AVCodecContext *) context)->channels;
}
FUNC(jint, nativeGetSampleRate, jlong context) {
DECODER_FUNC(jint, ffmpegGetSampleRate, jlong context) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@ -149,7 +160,7 @@ FUNC(jint, nativeGetSampleRate, jlong context) {
return ((AVCodecContext *) context)->sample_rate;
}
FUNC(jlong, nativeReset, jlong jContext, jbyteArray extraData) {
DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) {
AVCodecContext *context = (AVCodecContext *) jContext;
if (!context) {
LOGE("Tried to reset without a context.");
@ -173,7 +184,7 @@ FUNC(jlong, nativeReset, jlong jContext, jbyteArray extraData) {
return (jlong) context;
}
FUNC(void, nativeRelease, jlong context) {
DECODER_FUNC(void, ffmpegRelease, jlong context) {
if (context) {
releaseContext((AVCodecContext *) context);
}

View file

@ -45,13 +45,10 @@ import java.util.List;
throws FlacDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (initializationData.size() != 1) {
throw new FlacDecoderException("Wrong number of initialization data");
throw new FlacDecoderException("Initialization data must be of length 1");
}
decoder = new FlacJni();
ByteBuffer metadata = ByteBuffer.wrap(initializationData.get(0));
decoder.setData(metadata);
decoder.setData(ByteBuffer.wrap(initializationData.get(0)));
FlacStreamInfo streamInfo;
try {
streamInfo = decoder.decodeMetadata();

View file

@ -69,7 +69,6 @@ public final class FlacExtractor implements Extractor {
extractorOutput = output;
trackOutput = extractorOutput.track(0);
extractorOutput.endTracks();
try {
decoder = new FlacJni();
} catch (FlacDecoderException e) {

View file

@ -26,32 +26,19 @@ import java.nio.ByteBuffer;
*/
/* package */ final class FlacJni {
/**
* Whether the underlying libflac library is available.
*/
public static final boolean IS_AVAILABLE;
static {
boolean isAvailable;
try {
System.loadLibrary("flacJNI");
isAvailable = true;
} catch (UnsatisfiedLinkError exception) {
isAvailable = false;
}
IS_AVAILABLE = isAvailable;
}
private static final int TEMP_BUFFER_SIZE = 8192; // The same buffer size which libflac has
private final long nativeDecoderContext;
private ByteBuffer byteBufferData;
private ExtractorInput extractorInput;
private boolean endOfExtractorInput;
private byte[] tempBuffer;
public FlacJni() throws FlacDecoderException {
if (!FlacLibrary.isAvailable()) {
throw new FlacDecoderException("Failed to load decoder native libraries.");
}
nativeDecoderContext = flacInit();
if (nativeDecoderContext == 0) {
throw new FlacDecoderException("Failed to initialize decoder");
@ -194,28 +181,18 @@ import java.nio.ByteBuffer;
}
private native long flacInit();
private native FlacStreamInfo flacDecodeMetadata(long context)
throws IOException, InterruptedException;
private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer)
throws IOException, InterruptedException;
private native int flacDecodeToArray(long context, byte[] outputArray)
throws IOException, InterruptedException;
private native long flacGetDecodePosition(long context);
private native long flacGetLastTimestamp(long context);
private native long flacGetSeekPosition(long context, long timeUs);
private native String flacGetStateString(long context);
private native void flacFlush(long context);
private native void flacReset(long context, long newPosition);
private native void flacRelease(long context);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.flac;
import com.google.android.exoplayer2.util.LibraryLoader;
/**
* Configures and queries the underlying native library.
*/
public final class FlacLibrary {
private static final LibraryLoader LOADER = new LibraryLoader("flacJNI");
private FlacLibrary() {}
/**
* Override the names of the Flac native libraries. If an application wishes to call this method,
* it must do so before calling any other method defined by this class, and before instantiating
* any {@link LibflacAudioRenderer} and {@link FlacExtractor} instances.
*/
public static void setLibraries(String... libraries) {
LOADER.setLibraries(libraries);
}
/**
* Returns whether the underlying library is available, loading it if necessary.
*/
public static boolean isAvailable() {
return LOADER.isAvailable();
}
}

View file

@ -30,13 +30,6 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
private static final int NUM_BUFFERS = 16;
/**
* Returns whether the underlying libflac library is available.
*/
public static boolean isLibflacAvailable() {
return FlacJni.IS_AVAILABLE;
}
public LibflacAudioRenderer() {
this(null, null);
}
@ -65,7 +58,7 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
@Override
public int supportsFormat(Format format) {
return isLibflacAvailable() && MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
return FlacLibrary.isAvailable() && MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
}

View file

@ -22,7 +22,7 @@
#include "include/flac_parser.h"
#define LOG_TAG "FlacJniJNI"
#define LOG_TAG "flac_jni"
#define ALOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#define ALOGV(...) \

View file

@ -31,20 +31,6 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
private static final int NUM_BUFFERS = 16;
private static final int INITIAL_INPUT_BUFFER_SIZE = 960 * 6;
/**
* Returns whether the underlying libopus library is available.
*/
public static boolean isLibopusAvailable() {
return OpusDecoder.IS_AVAILABLE;
}
/**
* Returns the version of the underlying libopus library if available, otherwise {@code null}.
*/
public static String getLibopusVersion() {
return isLibopusAvailable() ? OpusDecoder.getLibopusVersion() : null;
}
public LibopusAudioRenderer() {
this(null, null);
}
@ -73,7 +59,7 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
@Override
public int supportsFormat(Format format) {
return isLibopusAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)
return OpusLibrary.isAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)
? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
}

View file

@ -24,32 +24,11 @@ import java.nio.ByteOrder;
import java.util.List;
/**
* JNI wrapper for the libopus Opus decoder.
* Opus decoder.
*/
/* package */ final class OpusDecoder extends
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, OpusDecoderException> {
/**
* Whether the underlying libopus library is available.
*/
public static final boolean IS_AVAILABLE;
static {
boolean isAvailable;
try {
System.loadLibrary("opus");
System.loadLibrary("opusJNI");
isAvailable = true;
} catch (UnsatisfiedLinkError exception) {
isAvailable = false;
}
IS_AVAILABLE = isAvailable;
}
/**
* Returns the version string of the underlying libopus decoder.
*/
public static native String getLibopusVersion();
private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
/**
@ -78,6 +57,9 @@ import java.util.List;
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
List<byte[]> initializationData) throws OpusDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (!OpusLibrary.isAvailable()) {
throw new OpusDecoderException("Failed to load decoder native libraries.");
}
byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) {
throw new OpusDecoderException("Header size is too small.");
@ -90,7 +72,8 @@ import java.util.List;
int gain = readLittleEndian16(headerBytes, 16);
byte[] streamMap = new byte[8];
int numStreams, numCoupled;
int numStreams;
int numCoupled;
if (headerBytes[18] == 0) { // Channel mapping
// If there is no channel mapping, use the defaults.
if (channelCount > 2) { // Maximum channel count with default layout.
@ -133,7 +116,7 @@ import java.util.List;
@Override
public String getName() {
return "libopus" + getLibopusVersion();
return "libopus" + OpusLibrary.getVersion();
}
@Override
@ -185,14 +168,6 @@ import java.util.List;
opusClose(nativeDecoderContext);
}
private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled,
int gain, byte[] streamMap);
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize,
SimpleOutputBuffer outputBuffer, int sampleRate);
private native void opusClose(long decoder);
private native void opusReset(long decoder);
private native String opusGetErrorMessage(int errorCode);
private static int nsToSamples(long ns) {
return (int) (ns * SAMPLE_RATE / 1000000000);
}
@ -203,4 +178,12 @@ import java.util.List;
return value;
}
private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled,
int gain, byte[] streamMap);
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize,
SimpleOutputBuffer outputBuffer, int sampleRate);
private native void opusClose(long decoder);
private native void opusReset(long decoder);
private native String opusGetErrorMessage(int errorCode);
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.opus;
import com.google.android.exoplayer2.util.LibraryLoader;
/**
* Configures and queries the underlying native library.
*/
public final class OpusLibrary {
private static final LibraryLoader LOADER = new LibraryLoader("opus", "opusJNI");
private OpusLibrary() {}
/**
* Override the names of the Opus native libraries. If an application wishes to call this method,
* it must do so before calling any other method defined by this class, and before instantiating a
* {@link LibopusAudioRenderer} instance.
*/
public static void setLibraries(String... libraries) {
LOADER.setLibraries(libraries);
}
/**
* Returns whether the underlying library is available, loading it if necessary.
*/
public static boolean isAvailable() {
return LOADER.isAvailable();
}
/**
* Returns the version of the underlying library if available, or null otherwise.
*/
public static String getVersion() {
return isAvailable() ? opusGetVersion() : null;
}
public static native String opusGetVersion();
}

View file

@ -23,11 +23,11 @@
#include "opus.h" // NOLINT
#include "opus_multistream.h" // NOLINT
#define LOG_TAG "libopus_native"
#define LOG_TAG "opus_jni"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
__VA_ARGS__))
#define FUNC(RETURN_TYPE, NAME, ...) \
#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_opus_OpusDecoder_ ## NAME \
@ -37,6 +37,16 @@
Java_com_google_android_exoplayer2_ext_opus_OpusDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_opus_OpusLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
} \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_opus_OpusLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
// JNI references for SimpleOutputBuffer class.
static jmethodID outputBufferInit;
@ -51,8 +61,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples.
static int channelCount;
FUNC(jlong, opusInit, jint sampleRate, jint channelCount, jint numStreams,
jint numCoupled, jint gain, jbyteArray jStreamMap) {
DECODER_FUNC(jlong, opusInit, jint sampleRate, jint channelCount,
jint numStreams, jint numCoupled, jint gain, jbyteArray jStreamMap) {
int status = OPUS_INVALID_STATE;
::channelCount = channelCount;
jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0);
@ -79,8 +89,9 @@ FUNC(jlong, opusInit, jint sampleRate, jint channelCount, jint numStreams,
return reinterpret_cast<intptr_t>(decoder);
}
FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, jobject jInputBuffer,
jint inputSize, jobject jOutputBuffer, jint sampleRate) {
DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs,
jobject jInputBuffer, jint inputSize, jobject jOutputBuffer,
jint sampleRate) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
const uint8_t* inputBuffer =
reinterpret_cast<const uint8_t*>(
@ -102,20 +113,20 @@ FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, jobject jInputBuffer,
: sampleCount * kBytesPerSample * channelCount;
}
FUNC(void, opusClose, jlong jDecoder) {
DECODER_FUNC(void, opusClose, jlong jDecoder) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
opus_multistream_decoder_destroy(decoder);
}
FUNC(void, opusReset, jlong jDecoder) {
DECODER_FUNC(void, opusReset, jlong jDecoder) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
}
FUNC(jstring, getLibopusVersion) {
return env->NewStringUTF(opus_get_version_string());
}
FUNC(jstring, opusGetErrorMessage, jint errorCode) {
DECODER_FUNC(jstring, opusGetErrorMessage, jint errorCode) {
return env->NewStringUTF(opus_strerror(errorCode));
}
LIBRARY_FUNC(jstring, opusGetVersion) {
return env->NewStringUTF(opus_get_version_string());
}

View file

@ -118,8 +118,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
@Override
public int supportsFormat(Format format) {
return VpxNativeLibraryHelper.isLibvpxAvailable()
&& MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)
return VpxLibrary.isAvailable() && MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
}

View file

@ -21,7 +21,7 @@ import com.google.android.exoplayer2.decoder.SimpleDecoder;
import java.nio.ByteBuffer;
/**
* JNI wrapper for the libvpx VP9 decoder.
* Vpx decoder.
*/
/* package */ final class VpxDecoder extends
SimpleDecoder<DecoderInputBuffer, VpxOutputBuffer, VpxDecoderException> {
@ -45,7 +45,7 @@ import java.nio.ByteBuffer;
public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize)
throws VpxDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]);
if (!VpxNativeLibraryHelper.isLibvpxAvailable()) {
if (!VpxLibrary.isAvailable()) {
throw new VpxDecoderException("Failed to load decoder native libraries.");
}
vpxDecContext = vpxInit();
@ -57,7 +57,7 @@ import java.nio.ByteBuffer;
@Override
public String getName() {
return "libvpx" + VpxNativeLibraryHelper.getLibvpxVersion();
return "libvpx" + VpxLibrary.getVersion();
}
/**
@ -111,4 +111,5 @@ import java.nio.ByteBuffer;
private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native String vpxGetErrorMessage(long context);
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.vp9;
import com.google.android.exoplayer2.util.LibraryLoader;
/**
* Configures and queries the underlying native library.
*/
public final class VpxLibrary {
private static final LibraryLoader LOADER = new LibraryLoader("vpx", "vpxJNI");
private VpxLibrary() {}
/**
* Override the names of the Vpx native libraries. If an application wishes to call this method,
* it must do so before calling any other method defined by this class, and before instantiating a
* {@link LibvpxVideoRenderer} instance.
*/
public static void setLibraries(String... libraries) {
LOADER.setLibraries(libraries);
}
/**
* Returns whether the underlying library is available, loading it if necessary.
*/
public static boolean isAvailable() {
return LOADER.isAvailable();
}
/**
* Returns the version of the underlying library if available, or null otherwise.
*/
public static String getVersion() {
return isAvailable() ? vpxGetVersion() : null;
}
/**
* Returns the configuration string with which the underlying library was built if available, or
* null otherwise.
*/
public static String getBuildConfig() {
return isAvailable() ? vpxGetBuildConfig() : null;
}
private static native String vpxGetVersion();
private static native String vpxGetBuildConfig();
}

View file

@ -1,88 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.vp9;
import com.google.android.exoplayer2.util.Assertions;
/**
* Configure the native libraries that are used by the {@link VpxDecoder}.
*/
public final class VpxNativeLibraryHelper {
private static boolean loadAttempted;
private static boolean isAvailable;
private static String[] nativeLibs = { "vpx", "vpxJNI" };
private static final Object lock = new Object();
private VpxNativeLibraryHelper() {}
/**
* Override the default set of native libraries loaded for Vpx decoder support.
* If an application wishes to call this method, it must do so before calling any other method
* defined by this class, and before instantiating a {@link LibvpxVideoRenderer} instance.
*/
public static void setNativeLibraries(String... libs) {
synchronized (lock) {
Assertions.checkState(!loadAttempted,
"Vpx native libs must be set earlier, they have already been loaded.");
nativeLibs = libs;
}
}
/**
* Returns whether the underlying libvpx library is available.
*/
public static boolean isLibvpxAvailable() {
return loadNativeLibraries();
}
/**
* Returns the version of the underlying libvpx library if available, otherwise {@code null}.
*/
public static String getLibvpxVersion() {
return isLibvpxAvailable() ? nativeGetLibvpxConfig() : null;
}
/**
* Returns the configuration string with which the underlying libvpx library was built.
*/
public static String getLibvpxConfig() {
return isLibvpxAvailable() ? nativeGetLibvpxVersion() : null;
}
private static boolean loadNativeLibraries() {
synchronized (lock) {
if (loadAttempted) {
return isAvailable;
}
loadAttempted = true;
try {
for (String lib : VpxNativeLibraryHelper.nativeLibs) {
System.loadLibrary(lib);
}
isAvailable = true;
} catch (UnsatisfiedLinkError exception) {
isAvailable = false;
}
return isAvailable;
}
}
private static native String nativeGetLibvpxConfig();
private static native String nativeGetLibvpxVersion();
}

View file

@ -30,11 +30,11 @@
#include "vpx/vpx_decoder.h"
#include "vpx/vp8dx.h"
#define LOG_TAG "LIBVPX_DEC"
#define LOG_TAG "vpx_jni"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
__VA_ARGS__))
#define VPX_DECODER_FUNC(RETURN_TYPE, NAME, ...) \
#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_vp9_VpxDecoder_ ## NAME \
@ -44,14 +44,14 @@
Java_com_google_android_exoplayer2_ext_vp9_VpxDecoder_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
#define VPX_NATIVE_LIBRARY_HELPER_FUNC(RETURN_TYPE, NAME, ...) \
#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_vp9_VpxNativeLibraryHelper_ ## NAME \
Java_com_google_android_exoplayer2_ext_vp9_VpxLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
} \
JNIEXPORT RETURN_TYPE \
Java_com_google_android_exoplayer2_ext_vp9_VpxNativeLibraryHelper_ ## NAME \
Java_com_google_android_exoplayer2_ext_vp9_VpxLibrary_ ## NAME \
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
// JNI references for VpxOutputBuffer class.
@ -68,7 +68,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return JNI_VERSION_1_6;
}
VPX_DECODER_FUNC(jlong, vpxInit) {
DECODER_FUNC(jlong, vpxInit) {
vpx_codec_ctx_t* context = new vpx_codec_ctx_t();
vpx_codec_dec_cfg_t cfg = {0, 0, 0};
cfg.threads = android_getCpuCount();
@ -91,7 +91,7 @@ VPX_DECODER_FUNC(jlong, vpxInit) {
return reinterpret_cast<intptr_t>(context);
}
VPX_DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
const uint8_t* const buffer =
reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
@ -104,14 +104,14 @@ VPX_DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
return 0;
}
VPX_DECODER_FUNC(jlong, vpxClose, jlong jContext) {
DECODER_FUNC(jlong, vpxClose, jlong jContext) {
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
vpx_codec_destroy(context);
delete context;
return 0;
}
VPX_DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
vpx_codec_iter_t iter = NULL;
const vpx_image_t* const img = vpx_codec_get_frame(context, &iter);
@ -176,16 +176,15 @@ VPX_DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
return 0;
}
VPX_DECODER_FUNC(jstring, vpxGetErrorMessage, jlong jContext) {
DECODER_FUNC(jstring, vpxGetErrorMessage, jlong jContext) {
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
return env->NewStringUTF(vpx_codec_error(context));
}
VPX_NATIVE_LIBRARY_HELPER_FUNC(jstring, nativeGetLibvpxVersion) {
LIBRARY_FUNC(jstring, vpxGetVersion) {
return env->NewStringUTF(vpx_codec_version_str());
}
VPX_NATIVE_LIBRARY_HELPER_FUNC(jstring, nativeGetLibvpxConfig) {
LIBRARY_FUNC(jstring, vpxGetBuildConfig) {
return env->NewStringUTF(vpx_codec_build_config());
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.util;
/**
* Configurable loader for native libraries.
*/
public final class LibraryLoader {
private String[] nativeLibraries;
private boolean loadAttempted;
private boolean isAvailable;
/**
* @param libraries The names of the libraries to load.
*/
public LibraryLoader(String... libraries) {
nativeLibraries = libraries;
}
/**
* Overrides the names of the libraries to load. Must be called before any call to
* {@link #isAvailable()}.
*/
public synchronized void setLibraries(String... libraries) {
Assertions.checkState(!loadAttempted, "Cannot set libraries after loading");
nativeLibraries = libraries;
}
/**
* Returns whether the underlying libraries are available, loading them if necessary.
*/
public synchronized boolean isAvailable() {
if (loadAttempted) {
return isAvailable;
}
loadAttempted = true;
try {
for (String lib : nativeLibraries) {
System.loadLibrary(lib);
}
isAvailable = true;
} catch (UnsatisfiedLinkError exception) {
// Do nothing.
}
return isAvailable;
}
}