diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java index fd5568a908..838ed1c3e9 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java @@ -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; } diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java index 6ff2894429..12f4bcf672 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java @@ -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 { - 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 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); } diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java new file mode 100644 index 0000000000..90b42c01bb --- /dev/null +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java @@ -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); + +} diff --git a/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc b/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc index c69b4ecd77..0d083a8bd4 100644 --- a/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc +++ b/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc @@ -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); } diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java index 87fef263cb..1b142d31ca 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java @@ -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(); diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java index 2cbe7964a7..969981a5df 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java @@ -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) { diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacJni.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacJni.java index cb3927d9e6..b86d66a08e 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacJni.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacJni.java @@ -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); } diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java new file mode 100644 index 0000000000..ca18051207 --- /dev/null +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacLibrary.java @@ -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(); + } + +} diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java index 498a038f50..931b5ff3d9 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java @@ -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; } diff --git a/extensions/flac/src/main/jni/flac_jni.cc b/extensions/flac/src/main/jni/flac_jni.cc index 71be6e46db..c47a9dc74c 100644 --- a/extensions/flac/src/main/jni/flac_jni.cc +++ b/extensions/flac/src/main/jni/flac_jni.cc @@ -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(...) \ diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java index dec67d1fa8..3393562104 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java @@ -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; } diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java index c18bb3448a..73fb4072e8 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java @@ -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 { - /** - * 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 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); + } diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java new file mode 100644 index 0000000000..a79ef6df3a --- /dev/null +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java @@ -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(); + +} diff --git a/extensions/opus/src/main/jni/opus_jni.cc b/extensions/opus/src/main/jni/opus_jni.cc index 79b9792cc6..0920d9e499 100644 --- a/extensions/opus/src/main/jni/opus_jni.cc +++ b/extensions/opus/src/main/jni/opus_jni.cc @@ -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(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(jDecoder); const uint8_t* inputBuffer = reinterpret_cast( @@ -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(jDecoder); opus_multistream_decoder_destroy(decoder); } -FUNC(void, opusReset, jlong jDecoder) { +DECODER_FUNC(void, opusReset, jlong jDecoder) { OpusMSDecoder* decoder = reinterpret_cast(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()); +} diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index b83db52943..cfdd962197 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -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; } diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoder.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoder.java index 6bee22b988..5407e94f42 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoder.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoder.java @@ -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 { @@ -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); + } diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java new file mode 100644 index 0000000000..6c694ebd2c --- /dev/null +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java @@ -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(); + +} diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxNativeLibraryHelper.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxNativeLibraryHelper.java deleted file mode 100644 index 81f356e1bb..0000000000 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxNativeLibraryHelper.java +++ /dev/null @@ -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(); -} diff --git a/extensions/vp9/src/main/jni/vpx_jni.cc b/extensions/vp9/src/main/jni/vpx_jni.cc index e3ecccc447..a07b30a728 100644 --- a/extensions/vp9/src/main/jni/vpx_jni.cc +++ b/extensions/vp9/src/main/jni/vpx_jni.cc @@ -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(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(jContext); const uint8_t* const buffer = reinterpret_cast(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(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(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(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()); } - diff --git a/library/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java b/library/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java new file mode 100644 index 0000000000..c12bae0a07 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java @@ -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; + } + +}