blob: c2a5b6c4f4e4c64d36ec184ae7ee9fd1ebdb0a99 [file] [log] [blame]
/*
* Copyright (C) 2012, Collabora Ltd.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_ORC
#include <orc/orc.h>
#else
#define orc_memcpy memcpy
#endif
#include "gstamc.h"
#include "gstamc-constants.h"
#include "gstamcvideodec.h"
#include "gstamcvideoenc.h"
#include "gstamcaudiodec.h"
#include <gmodule.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/audio/audio.h>
#include <string.h>
#include <jni.h>
/* getExceptionSummary() and getStackTrace() taken from Android's
* platform/libnativehelper/JNIHelp.cpp
* Modified to work with normal C strings and without C++.
*
* Copyright (C) 2006 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.
*/
/*
* Returns a human-readable summary of an exception object. The buffer will
* be populated with the "binary" class name and, if present, the
* exception message.
*/
static gchar *
getExceptionSummary (JNIEnv * env, jthrowable exception)
{
GString *gs = g_string_new ("");
jclass exceptionClass = NULL, classClass = NULL;
jmethodID classGetNameMethod, getMessage;
jstring classNameStr = NULL, messageStr = NULL;
const char *classNameChars, *messageChars;
/* get the name of the exception's class */
exceptionClass = (*env)->GetObjectClass (env, exception);
classClass = (*env)->GetObjectClass (env, exceptionClass);
classGetNameMethod =
(*env)->GetMethodID (env, classClass, "getName", "()Ljava/lang/String;");
classNameStr =
(jstring) (*env)->CallObjectMethod (env, exceptionClass,
classGetNameMethod);
if (classNameStr == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<error getting class name>");
goto done;
}
classNameChars = (*env)->GetStringUTFChars (env, classNameStr, NULL);
if (classNameChars == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<error getting class name UTF-8>");
goto done;
}
g_string_append (gs, classNameChars);
(*env)->ReleaseStringUTFChars (env, classNameStr, classNameChars);
/* if the exception has a detail message, get that */
getMessage =
(*env)->GetMethodID (env, exceptionClass, "getMessage",
"()Ljava/lang/String;");
messageStr = (jstring) (*env)->CallObjectMethod (env, exception, getMessage);
if (messageStr == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
goto done;
}
g_string_append (gs, ": ");
messageChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
if (messageChars != NULL) {
g_string_append (gs, messageChars);
(*env)->ReleaseStringUTFChars (env, messageStr, messageChars);
} else {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<error getting message>");
}
done:
if (exceptionClass)
(*env)->DeleteLocalRef (env, exceptionClass);
if (classClass)
(*env)->DeleteLocalRef (env, classClass);
if (classNameStr)
(*env)->DeleteLocalRef (env, classNameStr);
if (messageStr)
(*env)->DeleteLocalRef (env, messageStr);
return g_string_free (gs, FALSE);
}
/*
* Returns an exception (with stack trace) as a string.
*/
static gchar *
getStackTrace (JNIEnv * env, jthrowable exception)
{
GString *gs = g_string_new ("");
jclass stringWriterClass = NULL, printWriterClass = NULL;
jclass exceptionClass = NULL;
jmethodID stringWriterCtor, stringWriterToStringMethod;
jmethodID printWriterCtor, printStackTraceMethod;
jobject stringWriter = NULL, printWriter = NULL;
jstring messageStr = NULL;
const char *utfChars;
stringWriterClass = (*env)->FindClass (env, "java/io/StringWriter");
if (stringWriterClass == NULL) {
g_string_append (gs, "<error getting java.io.StringWriter class>");
goto done;
}
stringWriterCtor =
(*env)->GetMethodID (env, stringWriterClass, "<init>", "()V");
stringWriterToStringMethod =
(*env)->GetMethodID (env, stringWriterClass, "toString",
"()Ljava/lang/String;");
printWriterClass = (*env)->FindClass (env, "java/io/PrintWriter");
if (printWriterClass == NULL) {
g_string_append (gs, "<error getting java.io.PrintWriter class>");
goto done;
}
printWriterCtor =
(*env)->GetMethodID (env, printWriterClass, "<init>",
"(Ljava/io/Writer;)V");
stringWriter = (*env)->NewObject (env, stringWriterClass, stringWriterCtor);
if (stringWriter == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<error creating new StringWriter instance>");
goto done;
}
printWriter =
(*env)->NewObject (env, printWriterClass, printWriterCtor, stringWriter);
if (printWriter == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<error creating new PrintWriter instance>");
goto done;
}
exceptionClass = (*env)->GetObjectClass (env, exception);
printStackTraceMethod =
(*env)->GetMethodID (env, exceptionClass, "printStackTrace",
"(Ljava/io/PrintWriter;)V");
(*env)->CallVoidMethod (env, exception, printStackTraceMethod, printWriter);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionClear (env);
g_string_append (gs, "<exception while printing stack trace>");
goto done;
}
messageStr = (jstring) (*env)->CallObjectMethod (env, stringWriter,
stringWriterToStringMethod);
if (messageStr == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<failed to call StringWriter.toString()>");
goto done;
}
utfChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
if (utfChars == NULL) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
g_string_append (gs, "<failed to get UTF chars for message>");
goto done;
}
g_string_append (gs, utfChars);
(*env)->ReleaseStringUTFChars (env, messageStr, utfChars);
done:
if (stringWriterClass)
(*env)->DeleteLocalRef (env, stringWriterClass);
if (printWriterClass)
(*env)->DeleteLocalRef (env, printWriterClass);
if (exceptionClass)
(*env)->DeleteLocalRef (env, exceptionClass);
if (stringWriter)
(*env)->DeleteLocalRef (env, stringWriter);
if (printWriter)
(*env)->DeleteLocalRef (env, printWriter);
if (messageStr)
(*env)->DeleteLocalRef (env, messageStr);
return g_string_free (gs, FALSE);
}
#include <pthread.h>
GST_DEBUG_CATEGORY (gst_amc_debug);
#define GST_CAT_DEFAULT gst_amc_debug
GQuark gst_amc_codec_info_quark = 0;
static GList *codec_infos = NULL;
#ifdef GST_AMC_IGNORE_UNKNOWN_COLOR_FORMATS
static gboolean ignore_unknown_color_formats = TRUE;
#else
static gboolean ignore_unknown_color_formats = FALSE;
#endif
static GModule *java_module;
static jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen,
jsize * nVMs);
static jint (*create_java_vm) (JavaVM ** p_vm, JNIEnv ** p_env, void *vm_args);
static JavaVM *java_vm;
static gboolean started_java_vm = FALSE;
static gboolean accepted_color_formats (GstAmcCodecType * type,
gboolean is_encoder);
/* Global cached references */
static struct
{
jclass klass;
jmethodID constructor;
} java_string;
static struct
{
jclass klass;
jmethodID configure;
jmethodID create_by_codec_name;
jmethodID dequeue_input_buffer;
jmethodID dequeue_output_buffer;
jmethodID flush;
jmethodID get_input_buffers;
jmethodID get_output_buffers;
jmethodID get_output_format;
jmethodID queue_input_buffer;
jmethodID release;
jmethodID release_output_buffer;
jmethodID start;
jmethodID stop;
} media_codec;
static struct
{
jclass klass;
jmethodID constructor;
jfieldID flags;
jfieldID offset;
jfieldID presentation_time_us;
jfieldID size;
} media_codec_buffer_info;
static struct
{
jclass klass;
jmethodID create_audio_format;
jmethodID create_video_format;
jmethodID to_string;
jmethodID contains_key;
jmethodID get_float;
jmethodID set_float;
jmethodID get_integer;
jmethodID set_integer;
jmethodID get_string;
jmethodID set_string;
jmethodID get_byte_buffer;
jmethodID set_byte_buffer;
} media_format;
static pthread_key_t current_jni_env;
static JNIEnv *
gst_amc_attach_current_thread (void)
{
JNIEnv *env;
JavaVMAttachArgs args;
GST_DEBUG ("Attaching thread %p", g_thread_self ());
args.version = JNI_VERSION_1_6;
args.name = NULL;
args.group = NULL;
if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
GST_ERROR ("Failed to attach current thread");
return NULL;
}
return env;
}
static void
gst_amc_detach_current_thread (void *env)
{
GST_DEBUG ("Detaching thread %p", g_thread_self ());
(*java_vm)->DetachCurrentThread (java_vm);
}
static JNIEnv *
gst_amc_get_jni_env (void)
{
JNIEnv *env;
if ((env = pthread_getspecific (current_jni_env)) == NULL) {
env = gst_amc_attach_current_thread ();
pthread_setspecific (current_jni_env, env);
}
return env;
}
static gboolean
check_nativehelper (void)
{
GModule *module;
void **jni_invocation = NULL;
gboolean ret = FALSE;
module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
if (!module)
return ret;
/* Check if libnativehelper is loaded in the process and if
* it has these awful wrappers for JNI_CreateJavaVM and
* JNI_GetCreatedJavaVMs that crash the app if you don't
* create a JniInvocation instance first. If it isn't we
* just fail here and don't initialize anything.
* See this code for reference:
* https://android.googlesource.com/platform/libnativehelper/+/master/JniInvocation.cpp
*/
if (!g_module_symbol (module, "_ZN13JniInvocation15jni_invocation_E",
(gpointer *) & jni_invocation)) {
ret = TRUE;
} else {
ret = (jni_invocation != NULL && *jni_invocation != NULL);
}
g_module_close (module);
return ret;
}
static gboolean
load_java_module (const gchar * name)
{
java_module = g_module_open (name, G_MODULE_BIND_LOCAL);
if (!java_module)
goto load_failed;
if (!g_module_symbol (java_module, "JNI_CreateJavaVM",
(gpointer *) & create_java_vm))
goto symbol_error;
if (!g_module_symbol (java_module, "JNI_GetCreatedJavaVMs",
(gpointer *) & get_created_java_vms))
goto symbol_error;
return TRUE;
load_failed:
{
GST_ERROR ("Failed to load Java module '%s': %s", GST_STR_NULL (name),
g_module_error ());
return FALSE;
}
symbol_error:
{
GST_ERROR ("Failed to locate required JNI symbols in '%s': %s",
GST_STR_NULL (name), g_module_error ());
g_module_close (java_module);
java_module = NULL;
return FALSE;
}
}
static gboolean
initialize_java_vm (void)
{
jsize n_vms;
/* Returns TRUE if we can safely
* a) get the current VMs and
* b) start a VM if none is started yet
*
* FIXME: On Android >= 4.4 we won't be able to safely start a
* VM on our own without using private C++ API!
*/
if (!check_nativehelper ()) {
GST_ERROR ("Can't safely check for VMs or start a VM");
return FALSE;
}
if (!load_java_module (NULL)) {
if (!load_java_module ("libdvm"))
return FALSE;
}
n_vms = 0;
if (get_created_java_vms (&java_vm, 1, &n_vms) < 0)
goto get_created_failed;
if (n_vms > 0) {
GST_DEBUG ("Successfully got existing Java VM %p", java_vm);
} else {
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[4];
GST_DEBUG ("Found no existing Java VM, trying to start one");
options[0].optionString = "-verbose:jni";
options[1].optionString = "-verbose:gc";
options[2].optionString = "-Xcheck:jni";
options[3].optionString = "-Xdebug";
vm_args.version = JNI_VERSION_1_4;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = JNI_TRUE;
if (create_java_vm (&java_vm, &env, &vm_args) < 0)
goto create_failed;
GST_DEBUG ("Successfully created Java VM %p", java_vm);
started_java_vm = TRUE;
}
return java_vm != NULL;
get_created_failed:
{
GST_ERROR ("Failed to get already created VMs");
g_module_close (java_module);
java_module = NULL;
return FALSE;
}
create_failed:
{
GST_ERROR ("Failed to create a Java VM");
g_module_close (java_module);
java_module = NULL;
return FALSE;
}
}
static void
gst_amc_set_error_string (JNIEnv * env, GQuark domain, gint code, GError ** err,
const gchar * message)
{
jthrowable exception;
if (!err) {
if ((*env)->ExceptionCheck (env))
(*env)->ExceptionClear (env);
return;
}
if ((*env)->ExceptionCheck (env)) {
if ((exception = (*env)->ExceptionOccurred (env))) {
gchar *exception_description, *exception_stacktrace;
/* Clear exception so that we can call Java methods again */
(*env)->ExceptionClear (env);
exception_description = getExceptionSummary (env, exception);
exception_stacktrace = getStackTrace (env, exception);
g_set_error (err, domain, code, "%s: %s\n%s", message,
exception_description, exception_stacktrace);
g_free (exception_description);
g_free (exception_stacktrace);
(*env)->DeleteLocalRef (env, exception);
} else {
(*env)->ExceptionClear (env);
g_set_error (err, domain, code, "%s", message);
}
} else {
g_set_error (err, domain, code, "%s", message);
}
}
G_GNUC_PRINTF (5, 6)
static void
gst_amc_set_error (JNIEnv * env, GQuark domain, gint code,
GError ** err, const gchar * format, ...)
{
gchar *message;
va_list var_args;
va_start (var_args, format);
message = g_strdup_vprintf (format, var_args);
va_end (var_args);
gst_amc_set_error_string (env, domain, code, err, message);
g_free (message);
}
GstAmcCodec *
gst_amc_codec_new (const gchar * name, GError ** err)
{
JNIEnv *env;
GstAmcCodec *codec = NULL;
jstring name_str;
jobject object = NULL;
g_return_val_if_fail (name != NULL, NULL);
env = gst_amc_get_jni_env ();
name_str = (*env)->NewStringUTF (env, name);
if (name_str == NULL) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create Java String");
goto error;
}
codec = g_slice_new0 (GstAmcCodec);
object =
(*env)->CallStaticObjectMethod (env, media_codec.klass,
media_codec.create_by_codec_name, name_str);
if ((*env)->ExceptionCheck (env) || !object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create codec '%s'", name);
goto error;
}
codec->object = (*env)->NewGlobalRef (env, object);
if (!codec->object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create global codec reference");
goto error;
}
done:
if (object)
(*env)->DeleteLocalRef (env, object);
if (name_str)
(*env)->DeleteLocalRef (env, name_str);
name_str = NULL;
return codec;
error:
if (codec)
g_slice_free (GstAmcCodec, codec);
codec = NULL;
goto done;
}
void
gst_amc_codec_free (GstAmcCodec * codec)
{
JNIEnv *env;
g_return_if_fail (codec != NULL);
env = gst_amc_get_jni_env ();
(*env)->DeleteGlobalRef (env, codec->object);
g_slice_free (GstAmcCodec, codec);
}
gboolean
gst_amc_codec_configure (GstAmcCodec * codec, GstAmcFormat * format, gint flags,
GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
g_return_val_if_fail (format != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.configure,
format->object, NULL, NULL, flags);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, err,
"Failed to configure codec");
ret = FALSE;
goto done;
}
done:
return ret;
}
GstAmcFormat *
gst_amc_codec_get_output_format (GstAmcCodec * codec, GError ** err)
{
JNIEnv *env;
GstAmcFormat *ret = NULL;
jobject object = NULL;
g_return_val_if_fail (codec != NULL, NULL);
env = gst_amc_get_jni_env ();
object =
(*env)->CallObjectMethod (env, codec->object,
media_codec.get_output_format);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, err,
"Failed to get output format");
goto done;
}
ret = g_slice_new0 (GstAmcFormat);
ret->object = (*env)->NewGlobalRef (env, object);
if (!ret->object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_SETTINGS, err,
"Failed to create global format reference");
g_slice_free (GstAmcFormat, ret);
ret = NULL;
}
(*env)->DeleteLocalRef (env, object);
done:
return ret;
}
gboolean
gst_amc_codec_start (GstAmcCodec * codec, GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.start);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to start codec");
ret = FALSE;
goto done;
}
done:
return ret;
}
gboolean
gst_amc_codec_stop (GstAmcCodec * codec, GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.stop);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to stop codec");
ret = FALSE;
goto done;
}
done:
return ret;
}
gboolean
gst_amc_codec_flush (GstAmcCodec * codec, GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.flush);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to flush codec");
ret = FALSE;
goto done;
}
done:
return ret;
}
gboolean
gst_amc_codec_release (GstAmcCodec * codec, GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.release);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to release codec");
ret = FALSE;
goto done;
}
done:
return ret;
}
void
gst_amc_codec_free_buffers (GstAmcBuffer * buffers, gsize n_buffers)
{
JNIEnv *env;
jsize i;
g_return_if_fail (buffers != NULL);
env = gst_amc_get_jni_env ();
for (i = 0; i < n_buffers; i++) {
if (buffers[i].object)
(*env)->DeleteGlobalRef (env, buffers[i].object);
}
g_free (buffers);
}
GstAmcBuffer *
gst_amc_codec_get_output_buffers (GstAmcCodec * codec, gsize * n_buffers,
GError ** err)
{
JNIEnv *env;
jobject output_buffers = NULL;
jsize n_output_buffers;
GstAmcBuffer *ret = NULL;
jsize i;
g_return_val_if_fail (codec != NULL, NULL);
g_return_val_if_fail (n_buffers != NULL, NULL);
*n_buffers = 0;
env = gst_amc_get_jni_env ();
output_buffers =
(*env)->CallObjectMethod (env, codec->object,
media_codec.get_output_buffers);
if ((*env)->ExceptionCheck (env) || !output_buffers) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get output buffers");
goto done;
}
n_output_buffers = (*env)->GetArrayLength (env, output_buffers);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get output buffers array length");
goto done;
}
*n_buffers = n_output_buffers;
ret = g_new0 (GstAmcBuffer, n_output_buffers);
for (i = 0; i < n_output_buffers; i++) {
jobject buffer = NULL;
buffer = (*env)->GetObjectArrayElement (env, output_buffers, i);
if ((*env)->ExceptionCheck (env) || !buffer) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get output buffer %d", i);
goto error;
}
ret[i].object = (*env)->NewGlobalRef (env, buffer);
(*env)->DeleteLocalRef (env, buffer);
if (!ret[i].object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create global output buffer reference %d", i);
goto error;
}
ret[i].data = (*env)->GetDirectBufferAddress (env, ret[i].object);
if (!ret[i].data) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get output buffer address %d", i);
goto error;
}
ret[i].size = (*env)->GetDirectBufferCapacity (env, ret[i].object);
}
done:
if (output_buffers)
(*env)->DeleteLocalRef (env, output_buffers);
output_buffers = NULL;
return ret;
error:
if (ret)
gst_amc_codec_free_buffers (ret, n_output_buffers);
ret = NULL;
*n_buffers = 0;
goto done;
}
GstAmcBuffer *
gst_amc_codec_get_input_buffers (GstAmcCodec * codec, gsize * n_buffers,
GError ** err)
{
JNIEnv *env;
jobject input_buffers = NULL;
jsize n_input_buffers;
GstAmcBuffer *ret = NULL;
jsize i;
g_return_val_if_fail (codec != NULL, NULL);
g_return_val_if_fail (n_buffers != NULL, NULL);
*n_buffers = 0;
env = gst_amc_get_jni_env ();
input_buffers =
(*env)->CallObjectMethod (env, codec->object,
media_codec.get_input_buffers);
if ((*env)->ExceptionCheck (env) || !input_buffers) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get input buffers");
goto done;
}
n_input_buffers = (*env)->GetArrayLength (env, input_buffers);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get input buffers array length");
goto done;
}
*n_buffers = n_input_buffers;
ret = g_new0 (GstAmcBuffer, n_input_buffers);
for (i = 0; i < n_input_buffers; i++) {
jobject buffer = NULL;
buffer = (*env)->GetObjectArrayElement (env, input_buffers, i);
if ((*env)->ExceptionCheck (env) || !buffer) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get input buffer %d", i);
goto error;
}
ret[i].object = (*env)->NewGlobalRef (env, buffer);
(*env)->DeleteLocalRef (env, buffer);
if (!ret[i].object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create global input buffer reference %d", i);
goto error;
}
ret[i].data = (*env)->GetDirectBufferAddress (env, ret[i].object);
if (!ret[i].data) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get input buffer address %d", i);
goto error;
}
ret[i].size = (*env)->GetDirectBufferCapacity (env, ret[i].object);
}
done:
if (input_buffers)
(*env)->DeleteLocalRef (env, input_buffers);
input_buffers = NULL;
return ret;
error:
if (ret)
gst_amc_codec_free_buffers (ret, n_input_buffers);
ret = NULL;
*n_buffers = 0;
goto done;
}
gint
gst_amc_codec_dequeue_input_buffer (GstAmcCodec * codec, gint64 timeoutUs,
GError ** err)
{
JNIEnv *env;
gint ret = G_MININT;
g_return_val_if_fail (codec != NULL, G_MININT);
env = gst_amc_get_jni_env ();
ret =
(*env)->CallIntMethod (env, codec->object,
media_codec.dequeue_input_buffer, timeoutUs);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to dequeue input buffer");
ret = G_MININT;
goto done;
}
done:
return ret;
}
static gboolean
gst_amc_codec_fill_buffer_info (JNIEnv * env, jobject buffer_info,
GstAmcBufferInfo * info, GError ** err)
{
g_return_val_if_fail (buffer_info != NULL, FALSE);
info->flags =
(*env)->GetIntField (env, buffer_info, media_codec_buffer_info.flags);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get buffer info flags");
return FALSE;
}
info->offset =
(*env)->GetIntField (env, buffer_info, media_codec_buffer_info.offset);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get buffer info offset");
return FALSE;
}
info->presentation_time_us =
(*env)->GetLongField (env, buffer_info,
media_codec_buffer_info.presentation_time_us);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get buffer info pts");
return FALSE;
}
info->size =
(*env)->GetIntField (env, buffer_info, media_codec_buffer_info.size);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionClear (env);
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get buffer info size");
return FALSE;
}
return TRUE;
}
gint
gst_amc_codec_dequeue_output_buffer (GstAmcCodec * codec,
GstAmcBufferInfo * info, gint64 timeoutUs, GError ** err)
{
JNIEnv *env;
gint ret = G_MININT;
jobject info_o = NULL;
g_return_val_if_fail (codec != NULL, G_MININT);
env = gst_amc_get_jni_env ();
info_o =
(*env)->NewObject (env, media_codec_buffer_info.klass,
media_codec_buffer_info.constructor);
if (!info_o) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create buffer info instance");
goto done;
}
ret =
(*env)->CallIntMethod (env, codec->object,
media_codec.dequeue_output_buffer, info_o, timeoutUs);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to dequeue output buffer");
ret = G_MININT;
goto done;
}
if (!gst_amc_codec_fill_buffer_info (env, info_o, info, err)) {
ret = G_MININT;
goto done;
}
done:
if (info_o)
(*env)->DeleteLocalRef (env, info_o);
info_o = NULL;
return ret;
}
gboolean
gst_amc_codec_queue_input_buffer (GstAmcCodec * codec, gint index,
const GstAmcBufferInfo * info, GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
g_return_val_if_fail (info != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.queue_input_buffer,
index, info->offset, info->size, info->presentation_time_us, info->flags);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to queue input buffer");
ret = FALSE;
goto done;
}
done:
return ret;
}
gboolean
gst_amc_codec_release_output_buffer (GstAmcCodec * codec, gint index,
GError ** err)
{
JNIEnv *env;
gboolean ret = TRUE;
g_return_val_if_fail (codec != NULL, FALSE);
env = gst_amc_get_jni_env ();
(*env)->CallVoidMethod (env, codec->object, media_codec.release_output_buffer,
index, JNI_FALSE);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to release output buffer");
ret = FALSE;
goto done;
}
done:
return ret;
}
GstAmcFormat *
gst_amc_format_new_audio (const gchar * mime, gint sample_rate, gint channels,
GError ** err)
{
JNIEnv *env;
GstAmcFormat *format = NULL;
jstring mime_str;
jobject object = NULL;
g_return_val_if_fail (mime != NULL, NULL);
env = gst_amc_get_jni_env ();
mime_str = (*env)->NewStringUTF (env, mime);
if (mime_str == NULL) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create Java string");
goto error;
}
format = g_slice_new0 (GstAmcFormat);
object =
(*env)->CallStaticObjectMethod (env, media_format.klass,
media_format.create_audio_format, mime_str, sample_rate, channels);
if ((*env)->ExceptionCheck (env) || !object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create format instance '%s'", mime);
goto error;
}
format->object = (*env)->NewGlobalRef (env, object);
if (!format->object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create global format reference");
goto error;
}
done:
if (object)
(*env)->DeleteLocalRef (env, object);
if (mime_str)
(*env)->DeleteLocalRef (env, mime_str);
mime_str = NULL;
return format;
error:
if (format)
g_slice_free (GstAmcFormat, format);
format = NULL;
goto done;
}
GstAmcFormat *
gst_amc_format_new_video (const gchar * mime, gint width, gint height,
GError ** err)
{
JNIEnv *env;
GstAmcFormat *format = NULL;
jstring mime_str;
jobject object = NULL;
g_return_val_if_fail (mime != NULL, NULL);
env = gst_amc_get_jni_env ();
mime_str = (*env)->NewStringUTF (env, mime);
if (mime_str == NULL) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create Java string");
goto error;
}
format = g_slice_new0 (GstAmcFormat);
object =
(*env)->CallStaticObjectMethod (env, media_format.klass,
media_format.create_video_format, mime_str, width, height);
if ((*env)->ExceptionCheck (env) || !object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create format instance '%s'", mime);
goto error;
}
format->object = (*env)->NewGlobalRef (env, object);
if (!format->object) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_INIT, err,
"Failed to create global format reference");
goto error;
}
done:
if (object)
(*env)->DeleteLocalRef (env, object);
if (mime_str)
(*env)->DeleteLocalRef (env, mime_str);
mime_str = NULL;
return format;
error:
if (format)
g_slice_free (GstAmcFormat, format);
format = NULL;
goto done;
}
void
gst_amc_format_free (GstAmcFormat * format)
{
JNIEnv *env;
g_return_if_fail (format != NULL);
env = gst_amc_get_jni_env ();
(*env)->DeleteGlobalRef (env, format->object);
g_slice_free (GstAmcFormat, format);
}
gchar *
gst_amc_format_to_string (GstAmcFormat * format, GError ** err)
{
JNIEnv *env;
jstring v_str = NULL;
const gchar *v = NULL;
gchar *ret = NULL;
g_return_val_if_fail (format != NULL, FALSE);
env = gst_amc_get_jni_env ();
v_str =
(*env)->CallObjectMethod (env, format->object, media_format.to_string);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to convert format to string");
goto done;
}
v = (*env)->GetStringUTFChars (env, v_str, NULL);
if (!v) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to get UTF8 string");
goto done;
}
ret = g_strdup (v);
done:
if (v)
(*env)->ReleaseStringUTFChars (env, v_str, v);
if (v_str)
(*env)->DeleteLocalRef (env, v_str);
return ret;
}
gboolean
gst_amc_format_contains_key (GstAmcFormat * format, const gchar * key,
GError ** err)
{
JNIEnv *env;
gboolean ret = FALSE;
jstring key_str = NULL;
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (key != NULL, FALSE);
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
ret =
(*env)->CallBooleanMethod (env, format->object, media_format.contains_key,
key_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to check if format contains key '%s'", key);
goto done;
}
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
return ret;
}
gboolean
gst_amc_format_get_float (GstAmcFormat * format, const gchar * key,
gfloat * value, GError ** err)
{
JNIEnv *env;
gboolean ret = FALSE;
jstring key_str = NULL;
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
*value = 0;
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
*value =
(*env)->CallFloatMethod (env, format->object, media_format.get_float,
key_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get float key '%s'", key);
goto done;
}
ret = TRUE;
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
return ret;
}
void
gst_amc_format_set_float (GstAmcFormat * format, const gchar * key,
gfloat value, GError ** err)
{
JNIEnv *env;
jstring key_str = NULL;
g_return_if_fail (format != NULL);
g_return_if_fail (key != NULL);
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
(*env)->CallVoidMethod (env, format->object, media_format.set_float, key_str,
value);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed set float key '%s'", key);
goto done;
}
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
}
gboolean
gst_amc_format_get_int (GstAmcFormat * format, const gchar * key, gint * value,
GError ** err)
{
JNIEnv *env;
gboolean ret = FALSE;
jstring key_str = NULL;
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
*value = 0;
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
*value =
(*env)->CallIntMethod (env, format->object, media_format.get_integer,
key_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get integer key '%s'", key);
goto done;
}
ret = TRUE;
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
return ret;
}
void
gst_amc_format_set_int (GstAmcFormat * format, const gchar * key, gint value,
GError ** err)
{
JNIEnv *env;
jstring key_str = NULL;
g_return_if_fail (format != NULL);
g_return_if_fail (key != NULL);
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
(*env)->CallVoidMethod (env, format->object, media_format.set_integer,
key_str, value);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed set integer key '%s'", key);
goto done;
}
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
}
gboolean
gst_amc_format_get_string (GstAmcFormat * format, const gchar * key,
gchar ** value, GError ** err)
{
JNIEnv *env;
gboolean ret = FALSE;
jstring key_str = NULL;
jstring v_str = NULL;
const gchar *v = NULL;
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
*value = 0;
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
v_str =
(*env)->CallObjectMethod (env, format->object, media_format.get_string,
key_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get string key '%s'", key);
goto done;
}
v = (*env)->GetStringUTFChars (env, v_str, NULL);
if (!v) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get string UTF8 characters");
goto done;
}
*value = g_strdup (v);
ret = TRUE;
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
if (v)
(*env)->ReleaseStringUTFChars (env, v_str, v);
if (v_str)
(*env)->DeleteLocalRef (env, v_str);
return ret;
}
void
gst_amc_format_set_string (GstAmcFormat * format, const gchar * key,
const gchar * value, GError ** err)
{
JNIEnv *env;
jstring key_str = NULL;
jstring v_str = NULL;
g_return_if_fail (format != NULL);
g_return_if_fail (key != NULL);
g_return_if_fail (value != NULL);
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
v_str = (*env)->NewStringUTF (env, value);
if (!v_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
(*env)->CallVoidMethod (env, format->object, media_format.set_string, key_str,
v_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed set string key '%s'", key);
goto done;
}
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
if (v_str)
(*env)->DeleteLocalRef (env, v_str);
}
gboolean
gst_amc_format_get_buffer (GstAmcFormat * format, const gchar * key,
guint8 ** data, gsize * size, GError ** err)
{
JNIEnv *env;
gboolean ret = FALSE;
jstring key_str = NULL;
jobject v = NULL;
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
g_return_val_if_fail (size != NULL, FALSE);
*data = NULL;
*size = 0;
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
v = (*env)->CallObjectMethod (env, format->object,
media_format.get_byte_buffer, key_str);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get buffer key '%s'", key);
goto done;
}
*data = (*env)->GetDirectBufferAddress (env, v);
if (!data) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed get buffer address");
goto done;
}
*size = (*env)->GetDirectBufferCapacity (env, v);
*data = g_memdup (*data, *size);
ret = TRUE;
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
if (v)
(*env)->DeleteLocalRef (env, v);
return ret;
}
void
gst_amc_format_set_buffer (GstAmcFormat * format, const gchar * key,
guint8 * data, gsize size, GError ** err)
{
JNIEnv *env;
jstring key_str = NULL;
jobject v = NULL;
g_return_if_fail (format != NULL);
g_return_if_fail (key != NULL);
g_return_if_fail (data != NULL);
env = gst_amc_get_jni_env ();
key_str = (*env)->NewStringUTF (env, key);
if (!key_str) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed to create Java string");
goto done;
}
/* FIXME: The memory must remain valid until the codec is stopped */
v = (*env)->NewDirectByteBuffer (env, data, size);
if (!v) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed create Java byte buffer");
goto done;
}
(*env)->CallVoidMethod (env, format->object, media_format.set_byte_buffer,
key_str, v);
if ((*env)->ExceptionCheck (env)) {
gst_amc_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err,
"Failed set buffer key '%s'", key);
goto done;
}
done:
if (key_str)
(*env)->DeleteLocalRef (env, key_str);
if (v)
(*env)->DeleteLocalRef (env, v);
}
static gboolean
get_java_classes (void)
{
gboolean ret = TRUE;
JNIEnv *env;
jclass tmp;
GST_DEBUG ("Retrieving Java classes");
env = gst_amc_get_jni_env ();
tmp = (*env)->FindClass (env, "java/lang/String");
if (!tmp) {
ret = FALSE;
GST_ERROR ("Failed to get string class");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
java_string.klass = (*env)->NewGlobalRef (env, tmp);
if (!java_string.klass) {
ret = FALSE;
GST_ERROR ("Failed to get string class global reference");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
(*env)->DeleteLocalRef (env, tmp);
tmp = NULL;
java_string.constructor =
(*env)->GetMethodID (env, java_string.klass, "<init>", "([C)V");
if (!java_string.constructor) {
ret = FALSE;
GST_ERROR ("Failed to get string methods");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
tmp = (*env)->FindClass (env, "android/media/MediaCodec");
if (!tmp) {
ret = FALSE;
GST_ERROR ("Failed to get codec class");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
media_codec.klass = (*env)->NewGlobalRef (env, tmp);
if (!media_codec.klass) {
ret = FALSE;
GST_ERROR ("Failed to get codec class global reference");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
(*env)->DeleteLocalRef (env, tmp);
tmp = NULL;
media_codec.create_by_codec_name =
(*env)->GetStaticMethodID (env, media_codec.klass, "createByCodecName",
"(Ljava/lang/String;)Landroid/media/MediaCodec;");
media_codec.configure =
(*env)->GetMethodID (env, media_codec.klass, "configure",
"(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;I)V");
media_codec.dequeue_input_buffer =
(*env)->GetMethodID (env, media_codec.klass, "dequeueInputBuffer",
"(J)I");
media_codec.dequeue_output_buffer =
(*env)->GetMethodID (env, media_codec.klass, "dequeueOutputBuffer",
"(Landroid/media/MediaCodec$BufferInfo;J)I");
media_codec.flush =
(*env)->GetMethodID (env, media_codec.klass, "flush", "()V");
media_codec.get_input_buffers =
(*env)->GetMethodID (env, media_codec.klass, "getInputBuffers",
"()[Ljava/nio/ByteBuffer;");
media_codec.get_output_buffers =
(*env)->GetMethodID (env, media_codec.klass, "getOutputBuffers",
"()[Ljava/nio/ByteBuffer;");
media_codec.get_output_format =
(*env)->GetMethodID (env, media_codec.klass, "getOutputFormat",
"()Landroid/media/MediaFormat;");
media_codec.queue_input_buffer =
(*env)->GetMethodID (env, media_codec.klass, "queueInputBuffer",
"(IIIJI)V");
media_codec.release =
(*env)->GetMethodID (env, media_codec.klass, "release", "()V");
media_codec.release_output_buffer =
(*env)->GetMethodID (env, media_codec.klass, "releaseOutputBuffer",
"(IZ)V");
media_codec.start =
(*env)->GetMethodID (env, media_codec.klass, "start", "()V");
media_codec.stop =
(*env)->GetMethodID (env, media_codec.klass, "stop", "()V");
if (!media_codec.configure ||
!media_codec.create_by_codec_name ||
!media_codec.dequeue_input_buffer ||
!media_codec.dequeue_output_buffer ||
!media_codec.flush ||
!media_codec.get_input_buffers ||
!media_codec.get_output_buffers ||
!media_codec.get_output_format ||
!media_codec.queue_input_buffer ||
!media_codec.release ||
!media_codec.release_output_buffer ||
!media_codec.start || !media_codec.stop) {
ret = FALSE;
GST_ERROR ("Failed to get codec methods");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
tmp = (*env)->FindClass (env, "android/media/MediaCodec$BufferInfo");
if (!tmp) {
ret = FALSE;
(*env)->ExceptionClear (env);
GST_ERROR ("Failed to get codec buffer info class");
goto done;
}
media_codec_buffer_info.klass = (*env)->NewGlobalRef (env, tmp);
if (!media_codec_buffer_info.klass) {
ret = FALSE;
GST_ERROR ("Failed to get codec buffer info class global reference");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
(*env)->DeleteLocalRef (env, tmp);
tmp = NULL;
media_codec_buffer_info.constructor =
(*env)->GetMethodID (env, media_codec_buffer_info.klass, "<init>", "()V");
media_codec_buffer_info.flags =
(*env)->GetFieldID (env, media_codec_buffer_info.klass, "flags", "I");
media_codec_buffer_info.offset =
(*env)->GetFieldID (env, media_codec_buffer_info.klass, "offset", "I");
media_codec_buffer_info.presentation_time_us =
(*env)->GetFieldID (env, media_codec_buffer_info.klass,
"presentationTimeUs", "J");
media_codec_buffer_info.size =
(*env)->GetFieldID (env, media_codec_buffer_info.klass, "size", "I");
if (!media_codec_buffer_info.constructor || !media_codec_buffer_info.flags
|| !media_codec_buffer_info.offset
|| !media_codec_buffer_info.presentation_time_us
|| !media_codec_buffer_info.size) {
ret = FALSE;
GST_ERROR ("Failed to get buffer info methods and fields");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
tmp = (*env)->FindClass (env, "android/media/MediaFormat");
if (!tmp) {
ret = FALSE;
GST_ERROR ("Failed to get format class");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
media_format.klass = (*env)->NewGlobalRef (env, tmp);
if (!media_format.klass) {
ret = FALSE;
GST_ERROR ("Failed to get format class global reference");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
(*env)->DeleteLocalRef (env, tmp);
tmp = NULL;
media_format.create_audio_format =
(*env)->GetStaticMethodID (env, media_format.klass, "createAudioFormat",
"(Ljava/lang/String;II)Landroid/media/MediaFormat;");
media_format.create_video_format =
(*env)->GetStaticMethodID (env, media_format.klass, "createVideoFormat",
"(Ljava/lang/String;II)Landroid/media/MediaFormat;");
media_format.to_string =
(*env)->GetMethodID (env, media_format.klass, "toString",
"()Ljava/lang/String;");
media_format.contains_key =
(*env)->GetMethodID (env, media_format.klass, "containsKey",
"(Ljava/lang/String;)Z");
media_format.get_float =
(*env)->GetMethodID (env, media_format.klass, "getFloat",
"(Ljava/lang/String;)F");
media_format.set_float =
(*env)->GetMethodID (env, media_format.klass, "setFloat",
"(Ljava/lang/String;F)V");
media_format.get_integer =
(*env)->GetMethodID (env, media_format.klass, "getInteger",
"(Ljava/lang/String;)I");
media_format.set_integer =
(*env)->GetMethodID (env, media_format.klass, "setInteger",
"(Ljava/lang/String;I)V");
media_format.get_string =
(*env)->GetMethodID (env, media_format.klass, "getString",
"(Ljava/lang/String;)Ljava/lang/String;");
media_format.set_string =
(*env)->GetMethodID (env, media_format.klass, "setString",
"(Ljava/lang/String;Ljava/lang/String;)V");
media_format.get_byte_buffer =
(*env)->GetMethodID (env, media_format.klass, "getByteBuffer",
"(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
media_format.set_byte_buffer =
(*env)->GetMethodID (env, media_format.klass, "setByteBuffer",
"(Ljava/lang/String;Ljava/nio/ByteBuffer;)V");
if (!media_format.create_audio_format || !media_format.create_video_format
|| !media_format.contains_key || !media_format.get_float
|| !media_format.set_float || !media_format.get_integer
|| !media_format.set_integer || !media_format.get_string
|| !media_format.set_string || !media_format.get_byte_buffer
|| !media_format.set_byte_buffer) {
ret = FALSE;
GST_ERROR ("Failed to get format methods");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
done:
if (tmp)
(*env)->DeleteLocalRef (env, tmp);
tmp = NULL;
return ret;
}
static gboolean
scan_codecs (GstPlugin * plugin)
{
gboolean ret = TRUE;
JNIEnv *env;
jclass codec_list_class = NULL;
jmethodID get_codec_count_id, get_codec_info_at_id;
jint codec_count, i;
const GstStructure *cache_data;
GST_DEBUG ("Scanning codecs");
if ((cache_data = gst_plugin_get_cache_data (plugin))) {
const GValue *arr = gst_structure_get_value (cache_data, "codecs");
guint i, n;
GST_DEBUG ("Getting codecs from cache");
n = gst_value_array_get_size (arr);
for (i = 0; i < n; i++) {
const GValue *cv = gst_value_array_get_value (arr, i);
const GstStructure *cs = gst_value_get_structure (cv);
const gchar *name;
gboolean is_encoder;
const GValue *starr;
guint j, n2;
GstAmcCodecInfo *gst_codec_info;
gst_codec_info = g_new0 (GstAmcCodecInfo, 1);
name = gst_structure_get_string (cs, "name");
gst_structure_get_boolean (cs, "is-encoder", &is_encoder);
gst_codec_info->name = g_strdup (name);
gst_codec_info->is_encoder = is_encoder;
starr = gst_structure_get_value (cs, "supported-types");
n2 = gst_value_array_get_size (starr);
gst_codec_info->n_supported_types = n2;
gst_codec_info->supported_types = g_new0 (GstAmcCodecType, n2);
for (j = 0; j < n2; j++) {
const GValue *stv = gst_value_array_get_value (starr, j);
const GstStructure *sts = gst_value_get_structure (stv);
const gchar *mime;
const GValue *cfarr;
const GValue *plarr;
guint k, n3;
GstAmcCodecType *gst_codec_type = &gst_codec_info->supported_types[j];
mime = gst_structure_get_string (sts, "mime");
gst_codec_type->mime = g_strdup (mime);
cfarr = gst_structure_get_value (sts, "color-formats");
n3 = gst_value_array_get_size (cfarr);
gst_codec_type->n_color_formats = n3;
gst_codec_type->color_formats = g_new0 (gint, n3);
for (k = 0; k < n3; k++) {
const GValue *cfv = gst_value_array_get_value (cfarr, k);
gint cf = g_value_get_int (cfv);
gst_codec_type->color_formats[k] = cf;
}
plarr = gst_structure_get_value (sts, "profile-levels");
n3 = gst_value_array_get_size (plarr);
gst_codec_type->n_profile_levels = n3;
gst_codec_type->profile_levels =
g_malloc0 (sizeof (gst_codec_type->profile_levels[0]) * n3);
for (k = 0; k < n3; k++) {
const GValue *plv = gst_value_array_get_value (plarr, k);
const GValue *p, *l;
p = gst_value_array_get_value (plv, 0);
l = gst_value_array_get_value (plv, 1);
gst_codec_type->profile_levels[k].profile = g_value_get_int (p);
gst_codec_type->profile_levels[k].level = g_value_get_int (l);
}
}
codec_infos = g_list_append (codec_infos, gst_codec_info);
}
return TRUE;
}
env = gst_amc_get_jni_env ();
codec_list_class = (*env)->FindClass (env, "android/media/MediaCodecList");
if (!codec_list_class) {
ret = FALSE;
GST_ERROR ("Failed to get codec list class");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
get_codec_count_id =
(*env)->GetStaticMethodID (env, codec_list_class, "getCodecCount", "()I");
get_codec_info_at_id =
(*env)->GetStaticMethodID (env, codec_list_class, "getCodecInfoAt",
"(I)Landroid/media/MediaCodecInfo;");
if (!get_codec_count_id || !get_codec_info_at_id) {
ret = FALSE;
GST_ERROR ("Failed to get codec list method IDs");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
goto done;
}
codec_count =
(*env)->CallStaticIntMethod (env, codec_list_class, get_codec_count_id);
if ((*env)->ExceptionCheck (env)) {
ret = FALSE;
GST_ERROR ("Failed to get number of available codecs");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
goto done;
}
GST_LOG ("Found %d available codecs", codec_count);
for (i = 0; i < codec_count; i++) {
GstAmcCodecInfo *gst_codec_info;
jobject codec_info = NULL;
jclass codec_info_class = NULL;
jmethodID get_capabilities_for_type_id, get_name_id;
jmethodID get_supported_types_id, is_encoder_id;
jobject name = NULL;
const gchar *name_str = NULL;
jboolean is_encoder;
jarray supported_types = NULL;
jsize n_supported_types;
jsize j;
gboolean valid_codec = TRUE;
gst_codec_info = g_new0 (GstAmcCodecInfo, 1);
codec_info =
(*env)->CallStaticObjectMethod (env, codec_list_class,
get_codec_info_at_id, i);
if ((*env)->ExceptionCheck (env) || !codec_info) {
GST_ERROR ("Failed to get codec info %d", i);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
valid_codec = FALSE;
goto next_codec;
}
codec_info_class = (*env)->GetObjectClass (env, codec_info);
if (!codec_list_class) {
GST_ERROR ("Failed to get codec info class");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
valid_codec = FALSE;
goto next_codec;
}
get_capabilities_for_type_id =
(*env)->GetMethodID (env, codec_info_class, "getCapabilitiesForType",
"(Ljava/lang/String;)Landroid/media/MediaCodecInfo$CodecCapabilities;");
get_name_id =
(*env)->GetMethodID (env, codec_info_class, "getName",
"()Ljava/lang/String;");
get_supported_types_id =
(*env)->GetMethodID (env, codec_info_class, "getSupportedTypes",
"()[Ljava/lang/String;");
is_encoder_id =
(*env)->GetMethodID (env, codec_info_class, "isEncoder", "()Z");
if (!get_capabilities_for_type_id || !get_name_id
|| !get_supported_types_id || !is_encoder_id) {
GST_ERROR ("Failed to get codec info method IDs");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
}
valid_codec = FALSE;
goto next_codec;
}
name = (*env)->CallObjectMethod (env, codec_info, get_name_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get codec name");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_codec;
}
name_str = (*env)->GetStringUTFChars (env, name, NULL);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to convert codec name to UTF8");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_codec;
}
GST_INFO ("Checking codec '%s'", name_str);
/* Compatibility codec names */
if (strcmp (name_str, "AACEncoder") == 0 ||
strcmp (name_str, "OMX.google.raw.decoder") == 0) {
GST_INFO ("Skipping compatibility codec '%s'", name_str);
valid_codec = FALSE;
goto next_codec;
}
if (g_str_has_suffix (name_str, ".secure")) {
GST_INFO ("Skipping DRM codec '%s'", name_str);
valid_codec = FALSE;
goto next_codec;
}
/* FIXME: Non-Google codecs usually just don't work and hang forever
* or crash when not used from a process that started the Java
* VM via the non-public AndroidRuntime class. Can we somehow
* initialize all this?
*/
if (started_java_vm && !g_str_has_prefix (name_str, "OMX.google.")) {
GST_INFO ("Skipping non-Google codec '%s' in standalone mode", name_str);
valid_codec = FALSE;
goto next_codec;
}
if (g_str_has_prefix (name_str, "OMX.ARICENT.")) {
GST_INFO ("Skipping possible broken codec '%s'", name_str);
valid_codec = FALSE;
goto next_codec;
}
/* FIXME:
* - Vorbis: Generates clicks for multi-channel streams
* - *Law: Generates output with too low frequencies
*/
if (strcmp (name_str, "OMX.google.vorbis.decoder") == 0 ||
strcmp (name_str, "OMX.google.g711.alaw.decoder") == 0 ||
strcmp (name_str, "OMX.google.g711.mlaw.decoder") == 0) {
GST_INFO ("Skipping known broken codec '%s'", name_str);
valid_codec = FALSE;
goto next_codec;
}
gst_codec_info->name = g_strdup (name_str);
is_encoder = (*env)->CallBooleanMethod (env, codec_info, is_encoder_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to detect if codec is an encoder");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_codec;
}
gst_codec_info->is_encoder = is_encoder;
supported_types =
(*env)->CallObjectMethod (env, codec_info, get_supported_types_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get supported types");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_codec;
}
n_supported_types = (*env)->GetArrayLength (env, supported_types);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get supported types array length");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_codec;
}
GST_INFO ("Codec '%s' has %d supported types", name_str, n_supported_types);
gst_codec_info->supported_types =
g_new0 (GstAmcCodecType, n_supported_types);
gst_codec_info->n_supported_types = n_supported_types;
if (n_supported_types == 0) {
valid_codec = FALSE;
GST_ERROR ("Codec has no supported types");
goto next_codec;
}
for (j = 0; j < n_supported_types; j++) {
GstAmcCodecType *gst_codec_type;
jobject supported_type = NULL;
const gchar *supported_type_str = NULL;
jobject capabilities = NULL;
jclass capabilities_class = NULL;
jfieldID color_formats_id, profile_levels_id;
jobject color_formats = NULL;
jobject profile_levels = NULL;
jint *color_formats_elems = NULL;
jsize n_elems, k;
gst_codec_type = &gst_codec_info->supported_types[j];
supported_type = (*env)->GetObjectArrayElement (env, supported_types, j);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get %d-th supported type", j);
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
supported_type_str =
(*env)->GetStringUTFChars (env, supported_type, NULL);
if ((*env)->ExceptionCheck (env) || !supported_type_str) {
GST_ERROR ("Failed to convert supported type to UTF8");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
GST_INFO ("Supported type '%s'", supported_type_str);
gst_codec_type->mime = g_strdup (supported_type_str);
capabilities =
(*env)->CallObjectMethod (env, codec_info,
get_capabilities_for_type_id, supported_type);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get capabilities for supported type");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
capabilities_class = (*env)->GetObjectClass (env, capabilities);
if (!capabilities_class) {
GST_ERROR ("Failed to get capabilities class");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
color_formats_id =
(*env)->GetFieldID (env, capabilities_class, "colorFormats", "[I");
profile_levels_id =
(*env)->GetFieldID (env, capabilities_class, "profileLevels",
"[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
if (!color_formats_id || !profile_levels_id) {
GST_ERROR ("Failed to get capabilities field IDs");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
color_formats =
(*env)->GetObjectField (env, capabilities, color_formats_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get color formats");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
n_elems = (*env)->GetArrayLength (env, color_formats);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get color formats array length");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
gst_codec_type->n_color_formats = n_elems;
gst_codec_type->color_formats = g_new0 (gint, n_elems);
color_formats_elems =
(*env)->GetIntArrayElements (env, color_formats, NULL);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get color format elements");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
for (k = 0; k < n_elems; k++) {
GST_INFO ("Color format %d: 0x%x", k, color_formats_elems[k]);
gst_codec_type->color_formats[k] = color_formats_elems[k];
}
if (g_str_has_prefix (gst_codec_type->mime, "video/")) {
if (!n_elems) {
GST_ERROR ("No supported color formats for video codec");
valid_codec = FALSE;
goto next_supported_type;
}
if (!ignore_unknown_color_formats
&& !accepted_color_formats (gst_codec_type, is_encoder)) {
GST_ERROR ("%s codec has unknown color formats, ignoring",
is_encoder ? "Encoder" : "Decoder");
valid_codec = FALSE;
goto next_supported_type;
}
}
profile_levels =
(*env)->GetObjectField (env, capabilities, profile_levels_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get profile/levels");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
n_elems = (*env)->GetArrayLength (env, profile_levels);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get profile/levels array length");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_supported_type;
}
gst_codec_type->n_profile_levels = n_elems;
gst_codec_type->profile_levels =
g_malloc0 (sizeof (gst_codec_type->profile_levels[0]) * n_elems);
for (k = 0; k < n_elems; k++) {
jobject profile_level = NULL;
jclass profile_level_class = NULL;
jfieldID level_id, profile_id;
jint level, profile;
profile_level = (*env)->GetObjectArrayElement (env, profile_levels, k);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get %d-th profile/level", k);
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_profile_level;
}
profile_level_class = (*env)->GetObjectClass (env, profile_level);
if (!profile_level_class) {
GST_ERROR ("Failed to get profile/level class");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_profile_level;
}
level_id = (*env)->GetFieldID (env, profile_level_class, "level", "I");
profile_id =
(*env)->GetFieldID (env, profile_level_class, "profile", "I");
if (!level_id || !profile_id) {
GST_ERROR ("Failed to get profile/level field IDs");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_profile_level;
}
level = (*env)->GetIntField (env, profile_level, level_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get level");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_profile_level;
}
GST_INFO ("Level %d: 0x%08x", k, level);
gst_codec_type->profile_levels[k].level = level;
profile = (*env)->GetIntField (env, profile_level, profile_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to get profile");
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
valid_codec = FALSE;
goto next_profile_level;
}
GST_INFO ("Profile %d: 0x%08x", k, profile);
gst_codec_type->profile_levels[k].profile = profile;
next_profile_level:
if (profile_level)
(*env)->DeleteLocalRef (env, profile_level);
profile_level = NULL;
if (profile_level_class)
(*env)->DeleteLocalRef (env, profile_level_class);
profile_level_class = NULL;
if (!valid_codec)
break;
}
next_supported_type:
if (color_formats_elems)
(*env)->ReleaseIntArrayElements (env, color_formats,
color_formats_elems, JNI_ABORT);
color_formats_elems = NULL;
if (color_formats)
(*env)->DeleteLocalRef (env, color_formats);
color_formats = NULL;
if (profile_levels)
(*env)->DeleteLocalRef (env, profile_levels);
color_formats = NULL;
if (capabilities)
(*env)->DeleteLocalRef (env, capabilities);
capabilities = NULL;
if (capabilities_class)
(*env)->DeleteLocalRef (env, capabilities_class);
capabilities_class = NULL;
if (supported_type_str)
(*env)->ReleaseStringUTFChars (env, supported_type, supported_type_str);
supported_type_str = NULL;
if (supported_type)
(*env)->DeleteLocalRef (env, supported_type);
supported_type = NULL;
if (!valid_codec)
break;
}
/* We need at least a valid supported type */
if (valid_codec) {
GST_LOG ("Successfully scanned codec '%s'", name_str);
codec_infos = g_list_append (codec_infos, gst_codec_info);
gst_codec_info = NULL;
}
/* Clean up of all local references we got */
next_codec:
if (name_str)
(*env)->ReleaseStringUTFChars (env, name, name_str);
name_str = NULL;
if (name)
(*env)->DeleteLocalRef (env, name);
name = NULL;
if (supported_types)
(*env)->DeleteLocalRef (env, supported_types);
supported_types = NULL;
if (codec_info)
(*env)->DeleteLocalRef (env, codec_info);
codec_info = NULL;
if (codec_info_class)
(*env)->DeleteLocalRef (env, codec_info_class);
codec_info_class = NULL;
if (gst_codec_info) {
gint j;
for (j = 0; j < gst_codec_info->n_supported_types; j++) {
GstAmcCodecType *gst_codec_type = &gst_codec_info->supported_types[j];
g_free (gst_codec_type->mime);
g_free (gst_codec_type->color_formats);
g_free (gst_codec_type->profile_levels);
}
g_free (gst_codec_info->supported_types);
g_free (gst_codec_info->name);
g_free (gst_codec_info);
}
gst_codec_info = NULL;
valid_codec = TRUE;
}
ret = codec_infos != NULL;
/* If successful we store a cache of the codec information in
* the registry. Otherwise we would always load all codecs during
* plugin initialization which can take quite some time (because
* of hardware) and also loads lots of shared libraries (which
* number is limited by 64 in Android).
*/
if (ret) {
GstStructure *new_cache_data = gst_structure_new_empty ("gst-amc-cache");
GList *l;
GValue arr = { 0, };
g_value_init (&arr, GST_TYPE_ARRAY);
for (l = codec_infos; l; l = l->next) {
GstAmcCodecInfo *gst_codec_info = l->data;
GValue cv = { 0, };
GstStructure *cs = gst_structure_new_empty ("gst-amc-codec");
GValue starr = { 0, };
gint i;
gst_structure_set (cs, "name", G_TYPE_STRING, gst_codec_info->name,
"is-encoder", G_TYPE_BOOLEAN, gst_codec_info->is_encoder, NULL);
g_value_init (&starr, GST_TYPE_ARRAY);
for (i = 0; i < gst_codec_info->n_supported_types; i++) {
GstAmcCodecType *gst_codec_type = &gst_codec_info->supported_types[i];
GstStructure *sts = gst_structure_new_empty ("gst-amc-supported-type");
GValue stv = { 0, };
GValue tmparr = { 0, };
gint j;
gst_structure_set (sts, "mime", G_TYPE_STRING, gst_codec_type->mime,
NULL);
g_value_init (&tmparr, GST_TYPE_ARRAY);
for (j = 0; j < gst_codec_type->n_color_formats; j++) {
GValue tmp = { 0, };
g_value_init (&tmp, G_TYPE_INT);
g_value_set_int (&tmp, gst_codec_type->color_formats[j]);
gst_value_array_append_value (&tmparr, &tmp);
g_value_unset (&tmp);
}
gst_structure_set_value (sts, "color-formats", &tmparr);
g_value_unset (&tmparr);
g_value_init (&tmparr, GST_TYPE_ARRAY);
for (j = 0; j < gst_codec_type->n_profile_levels; j++) {
GValue tmparr2 = { 0, };
GValue tmp = { 0, };
g_value_init (&tmparr2, GST_TYPE_ARRAY);
g_value_init (&tmp, G_TYPE_INT);
g_value_set_int (&tmp, gst_codec_type->profile_levels[j].profile);
gst_value_array_append_value (&tmparr2, &tmp);
g_value_set_int (&tmp, gst_codec_type->profile_levels[j].level);
gst_value_array_append_value (&tmparr2, &tmp);
gst_value_array_append_value (&tmparr, &tmparr2);
g_value_unset (&tmp);
g_value_unset (&tmparr2);
}
gst_structure_set_value (sts, "profile-levels", &tmparr);
g_value_init (&stv, GST_TYPE_STRUCTURE);
gst_value_set_structure (&stv, sts);
gst_value_array_append_value (&starr, &stv);
g_value_unset (&tmparr);
gst_structure_free (sts);
}
gst_structure_set_value (cs, "supported-types", &starr);
g_value_unset (&starr);
g_value_init (&cv, GST_TYPE_STRUCTURE);
gst_value_set_structure (&cv, cs);
gst_value_array_append_value (&arr, &cv);
g_value_unset (&cv);
gst_structure_free (cs);
}
gst_structure_set_value (new_cache_data, "codecs", &arr);
g_value_unset (&arr);
gst_plugin_set_cache_data (plugin, new_cache_data);
}
done:
if (codec_list_class)
(*env)->DeleteLocalRef (env, codec_list_class);
return ret;
}
static const struct
{
gint color_format;
GstVideoFormat video_format;
} color_format_mapping_table[] = {
{
COLOR_FormatYUV420Planar, GST_VIDEO_FORMAT_I420}, {
COLOR_FormatYUV420SemiPlanar, GST_VIDEO_FORMAT_NV12}, {
COLOR_TI_FormatYUV420PackedSemiPlanar, GST_VIDEO_FORMAT_NV12}, {
COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced, GST_VIDEO_FORMAT_NV12}, {
COLOR_QCOM_FormatYUV420SemiPlanar, GST_VIDEO_FORMAT_NV12}, {
COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka, GST_VIDEO_FORMAT_NV12}, {
COLOR_QCOM_FormatYVU420SemiPlanar32m, GST_VIDEO_FORMAT_NV12}, {
COLOR_OMX_SEC_FormatNV12Tiled, GST_VIDEO_FORMAT_NV12}, {
COLOR_FormatYCbYCr, GST_VIDEO_FORMAT_YUY2}
};
static gboolean
accepted_color_formats (GstAmcCodecType * type, gboolean is_encoder)
{
gint i, j;
gint accepted = 0, all = type->n_color_formats;
for (i = 0; i < type->n_color_formats; i++) {
gboolean found = FALSE;
/* We ignore this one */
if (type->color_formats[i] == COLOR_FormatAndroidOpaque)
all--;
for (j = 0; j < G_N_ELEMENTS (color_format_mapping_table); j++) {
if (color_format_mapping_table[j].color_format == type->color_formats[i]) {
found = TRUE;
accepted++;
break;
}
}
if (!found) {
GST_DEBUG ("Unknown color format 0x%x, ignoring", type->color_formats[i]);
}
}
if (is_encoder)
return accepted > 0;
else
return accepted == all && all > 0;
}
GstVideoFormat
gst_amc_color_format_to_video_format (const GstAmcCodecInfo * codec_info,
const gchar * mime, gint color_format)
{
gint i;
if (color_format == COLOR_FormatYCbYCr) {
if (strcmp (codec_info->name, "OMX.k3.video.decoder.avc") == 0) {
GST_INFO
("OMX.k3.video.decoder.avc: COLOR_FormatYCbYCr is actually GST_VIDEO_FORMAT_NV12.");
return GST_VIDEO_FORMAT_NV12;
}
/* FIXME COLOR_FormatYCbYCr doesn't work properly for OMX.k3.video.encoder.avc temporarily. */
if (strcmp (codec_info->name, "OMX.k3.video.encoder.avc") == 0) {
GST_INFO
("OMX.k3.video.encoder.avc: COLOR_FormatYCbYCr is not supported yet.");
return GST_VIDEO_FORMAT_UNKNOWN;
}
/* FIXME COLOR_FormatYCbYCr is not supported in gst_amc_color_format_info_set yet, mask it. */
return GST_VIDEO_FORMAT_UNKNOWN;
}
if (color_format == COLOR_FormatYUV420SemiPlanar) {
if (strcmp (codec_info->name, "OMX.k3.video.encoder.avc") == 0) {
GST_INFO
("OMX.k3.video.encoder.avc: COLOR_FormatYUV420SemiPlanar is actually GST_VIDEO_FORMAT_NV21.");
return GST_VIDEO_FORMAT_NV21;
}
}
for (i = 0; i < G_N_ELEMENTS (color_format_mapping_table); i++) {
if (color_format_mapping_table[i].color_format == color_format)
return color_format_mapping_table[i].video_format;
}
return GST_VIDEO_FORMAT_UNKNOWN;
}
gint
gst_amc_video_format_to_color_format (const GstAmcCodecInfo * codec_info,
const gchar * mime, GstVideoFormat video_format)
{
const GstAmcCodecType *codec_type = NULL;
gint i, j;
for (i = 0; i < codec_info->n_supported_types; i++) {
if (strcmp (codec_info->supported_types[i].mime, mime) == 0) {
codec_type = &codec_info->supported_types[i];
break;
}
}
if (!codec_type)
return -1;
if (video_format == GST_VIDEO_FORMAT_NV12) {
if (strcmp (codec_info->name, "OMX.k3.video.decoder.avc") == 0) {
GST_INFO
("OMX.k3.video.decoder.avc: GST_VIDEO_FORMAT_NV12 is reported as COLOR_FormatYCbYCr.");
return COLOR_FormatYCbYCr;
}
}
if (video_format == GST_VIDEO_FORMAT_NV21) {
if (strcmp (codec_info->name, "OMX.k3.video.encoder.avc") == 0) {
GST_INFO
("OMX.k3.video.encoder.avc: GST_VIDEO_FORMAT_NV21 is reported as COLOR_FormatYUV420SemiPlanar.");
return COLOR_FormatYUV420SemiPlanar;
}
}
for (i = 0; i < G_N_ELEMENTS (color_format_mapping_table); i++) {
if (color_format_mapping_table[i].video_format == video_format) {
gint color_format = color_format_mapping_table[i].color_format;
for (j = 0; j < codec_type->n_color_formats; j++)
if (color_format == codec_type->color_formats[j])
return color_format;
}
}
return -1;
}
/*
* The format is called QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka.
* Which is actually NV12 (interleaved U&V).
*/
#define TILE_WIDTH 64
#define TILE_HEIGHT 32
#define TILE_SIZE (TILE_WIDTH * TILE_HEIGHT)
#define TILE_GROUP_SIZE (4 * TILE_SIZE)
/* get frame tile coordinate. XXX: nothing to be understood here, don't try. */
static size_t
tile_pos (size_t x, size_t y, size_t w, size_t h)
{
size_t flim = x + (y & ~1) * w;
if (y & 1) {
flim += (x & ~3) + 2;
} else if ((h & 1) == 0 || y != (h - 1)) {
flim += (x + 2) & ~3;
}
return flim;
}
gboolean
gst_amc_color_format_info_set (GstAmcColorFormatInfo * color_format_info,
const GstAmcCodecInfo * codec_info, const gchar * mime, gint color_format,
gint width, gint height, gint stride, gint slice_height, gint crop_left,
gint crop_right, gint crop_top, gint crop_bottom)
{
gint frame_size = 0;
if (color_format == COLOR_FormatYCbYCr) {
if (strcmp (codec_info->name, "OMX.k3.video.decoder.avc") == 0)
color_format = COLOR_FormatYUV420SemiPlanar;
}
/* Samsung Galaxy S3 seems to report wrong strides.
* I.e. BigBuckBunny 854x480 H264 reports a stride of 864 when it is
* actually 854, so we use width instead of stride here.
* This is obviously bound to break in the future. */
if (g_str_has_prefix (codec_info->name, "OMX.SEC.")) {
stride = width;
}
if (strcmp (codec_info->name, "OMX.k3.video.decoder.avc") == 0) {
stride = width;
slice_height = height;
}
if (slice_height == 0) {
/* NVidia Tegra 3 on Nexus 7 does not set this */
if (g_str_has_prefix (codec_info->name, "OMX.Nvidia."))
slice_height = GST_ROUND_UP_32 (height);
}
if (width == 0 || height == 0) {
GST_ERROR ("Width or height is 0");
return FALSE;
}
switch (color_format) {
case COLOR_FormatYUV420Planar:{
if (stride == 0 || slice_height == 0) {
GST_ERROR ("Stride or slice height is 0");
return FALSE;
}
frame_size =
stride * slice_height + 2 * (((stride + 1) / 2) * (slice_height +
1) / 2);
break;
}
case COLOR_TI_FormatYUV420PackedSemiPlanar:
case COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced:{
if (stride == 0 || slice_height == 0) {
GST_ERROR ("Stride or slice height is 0");
return FALSE;
}
frame_size =
stride * (slice_height - crop_top / 2) +
(GST_ROUND_UP_2 (stride) * ((slice_height + 1) / 2));
break;
}
case COLOR_QCOM_FormatYUV420SemiPlanar:
case COLOR_QCOM_FormatYVU420SemiPlanar32m:
case COLOR_FormatYUV420SemiPlanar:{
if (stride == 0 || slice_height == 0) {
GST_ERROR ("Stride or slice height is 0");
return FALSE;
}
frame_size = stride * slice_height + stride * ((slice_height + 1) / 2);
break;
}
case COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka:{
const size_t tile_w = (width - 1) / TILE_WIDTH + 1;
const size_t tile_w_align = (tile_w + 1) & ~1;
const size_t tile_h_luma = (height - 1) / TILE_HEIGHT + 1;
frame_size =
tile_pos (tile_w, tile_h_luma, tile_w_align, tile_h_luma) * TILE_SIZE;
break;
}
default:
GST_ERROR ("Unsupported color format %d", color_format);
return FALSE;
break;
}
color_format_info->color_format = color_format;
color_format_info->width = width;
color_format_info->height = height;
color_format_info->stride = stride;
color_format_info->slice_height = slice_height;
color_format_info->crop_left = crop_left;
color_format_info->crop_right = crop_right;
color_format_info->crop_top = crop_top;
color_format_info->crop_bottom = crop_bottom;
color_format_info->frame_size = frame_size;
return TRUE;
}
/* The weird handling of cropping, alignment and everything is taken from
* platform/frameworks/media/libstagefright/colorconversion/ColorConversion.cpp
*/
gboolean
gst_amc_color_format_copy (GstAmcColorFormatInfo * cinfo,
GstAmcBuffer * cbuffer, const GstAmcBufferInfo * cbuffer_info,
GstVideoInfo * vinfo, GstBuffer * vbuffer,
GstAmcColorFormatCopyDirection direction)
{
gboolean ret = FALSE;
guint8 *cptr = NULL, *vptr = NULL;
guint8 **src, **dest;
if (direction == COLOR_FORMAT_COPY_OUT) {
src = &cptr;
dest = &vptr;
} else {
src = &vptr;
dest = &cptr;
}
/* Same video format */
if (cbuffer_info->size == gst_buffer_get_size (vbuffer)) {
GstMapInfo minfo;
GST_DEBUG ("Buffer sizes equal, doing fast copy");
gst_buffer_map (vbuffer, &minfo, GST_MAP_WRITE);
cptr = cbuffer->data + cbuffer_info->offset;
vptr = minfo.data;
orc_memcpy (*dest, *src, cbuffer_info->size);
gst_buffer_unmap (vbuffer, &minfo);
ret = TRUE;
goto done;
}
GST_DEBUG ("Sizes not equal (%d vs %d), doing slow line-by-line copying",
cbuffer_info->size, gst_buffer_get_size (vbuffer));
/* Different video format, try to convert */
switch (cinfo->color_format) {
case COLOR_FormatYUV420Planar:{
GstVideoFrame vframe;
gint i, j, height;
gint stride, slice_height;
gint c_stride, v_stride;
gint row_length;
stride = cinfo->stride;
slice_height = cinfo->slice_height;
g_assert (stride > 0 && slice_height > 0);
gst_video_frame_map (&vframe, vinfo, vbuffer, GST_MAP_WRITE);
for (i = 0; i < 3; i++) {
if (i == 0) {
c_stride = stride;
v_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, i);
} else {
c_stride = (stride + 1) / 2;
v_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, i);
}
cptr = cbuffer->data + cbuffer_info->offset;
if (i == 0) {
cptr += cinfo->crop_top * stride;
cptr += cinfo->crop_left;
row_length = cinfo->width;
} else if (i > 0) {
/* skip the Y plane */
cptr += slice_height * stride;
/* crop_top/crop_left divided by two
* because one byte of the U/V planes
* corresponds to two pixels horizontally/vertically */
cptr += cinfo->crop_top / 2 * c_stride;
cptr += cinfo->crop_left / 2;
row_length = (cinfo->width + 1) / 2;
}
if (i == 2) {
/* skip the U plane */
cptr += ((slice_height + 1) / 2) * ((stride + 1) / 2);
}
vptr = GST_VIDEO_FRAME_COMP_DATA (&vframe, i);
height = GST_VIDEO_FRAME_COMP_HEIGHT (&vframe, i);
for (j = 0; j < height; j++) {
orc_memcpy (*dest, *src, row_length);
cptr += c_stride;
vptr += v_stride;
}
}
gst_video_frame_unmap (&vframe);
ret = TRUE;
break;
}
case COLOR_TI_FormatYUV420PackedSemiPlanar:
case COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced:{
gint i, j, height;
gint c_stride, v_stride;
gint row_length;
GstVideoFrame vframe;
/* This should always be set */
g_assert (cinfo->stride > 0 && cinfo->slice_height > 0);
/* FIXME: This does not work for odd widths or heights
* but might as well be a bug in the codec */
gst_video_frame_map (&vframe, vinfo, vbuffer, GST_MAP_WRITE);
for (i = 0; i < 2; i++) {
if (i == 0) {
c_stride = cinfo->stride;
v_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, i);
} else {
c_stride = GST_ROUND_UP_2 (cinfo->stride);
v_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, i);
}
cptr = cbuffer->data + cbuffer_info->offset;
if (i == 0) {
row_length = cinfo->width;
} else if (i == 1) {
cptr += (cinfo->slice_height - cinfo->crop_top / 2) * cinfo->stride;
row_length = GST_ROUND_UP_2 (cinfo->width);
}
vptr = GST_VIDEO_FRAME_COMP_DATA (&vframe, i);
height = GST_VIDEO_FRAME_COMP_HEIGHT (&vframe, i);
for (j = 0; j < height; j++) {
orc_memcpy (*dest, *src, row_length);
cptr += c_stride;
vptr += v_stride;
}
}
gst_video_frame_unmap (&vframe);
ret = TRUE;
break;
}
case COLOR_QCOM_FormatYUV420SemiPlanar:
case COLOR_QCOM_FormatYVU420SemiPlanar32m:
case COLOR_FormatYUV420SemiPlanar:{
gint i, j, height;
gint c_stride, v_stride;
gint row_length;