| /* |
| * 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_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_FormatYUV420SemiPlanar:{ |
| 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); |
| |
| gst_video_frame_map (&vframe, vinfo, vbuffer, GST_MAP_WRITE); |
| |
| for (i = 0; i < 2; i++) { |
| c_stride = cinfo->stride; |
| v_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, i); |
| |
| cptr = cbuffer->data + cbuffer_info->offset; |
| if (i == 0) { |
| cptr += cinfo->crop_top * cinfo->stride; |
| cptr += cinfo->crop_left; |
| row_length = cinfo->width; |
| } else if (i == 1) { |
| cptr += cinfo->slice_height * cinfo->stride; |
| cptr += cinfo->crop_top * cinfo->stride; |
| cptr += cinfo->crop_left; |
| row_length = 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; |
| } |
| /* FIXME: This should be in libgstvideo as MT12 or similar, see v4l2 */ |
| case COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka:{ |
| GstVideoFrame vframe; |
| gint width = cinfo->width; |
| gint height = cinfo->height; |
| gint v_luma_stride, v_chroma_stride; |
| guint8 *cdata = cbuffer->data + cbuffer_info->offset; |
| guint8 *v_luma, *v_chroma; |
| gint y; |
| 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; |
| const size_t tile_h_chroma = (height / 2 - 1) / TILE_HEIGHT + 1; |
| size_t luma_size = tile_w_align * tile_h_luma * TILE_SIZE; |
| |
| gst_video_frame_map (&vframe, vinfo, vbuffer, GST_MAP_WRITE); |
| v_luma = GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); |
| v_chroma = GST_VIDEO_FRAME_PLANE_DATA (&vframe, 1); |
| v_luma_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 0); |
| v_chroma_stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 1); |
| |
| if ((luma_size % TILE_GROUP_SIZE) != 0) |
| luma_size = (((luma_size - 1) / TILE_GROUP_SIZE) + 1) * TILE_GROUP_SIZE; |
| |
| for (y = 0; y < tile_h_luma; y++) { |
| size_t row_width = width; |
| gint x; |
| |
| for (x = 0; x < tile_w; x++) { |
| size_t tile_width = row_width; |
| size_t tile_height = height; |
| gint luma_idx; |
| gint chroma_idx; |
| /* luma source pointer for this tile */ |
| uint8_t *c_luma = |
| cdata + tile_pos (x, y, tile_w_align, tile_h_luma) * TILE_SIZE; |
| |
| /* chroma source pointer for this tile */ |
| uint8_t *c_chroma = |
| cdata + luma_size + tile_pos (x, y / 2, tile_w_align, |
| tile_h_chroma) * TILE_SIZE; |
| if (y & 1) |
| c_chroma += TILE_SIZE / 2; |
| |
| /* account for right columns */ |
| if (tile_width > TILE_WIDTH) |
| tile_width = TILE_WIDTH; |
| |
| /* account for bottom rows */ |
| if (tile_height > TILE_HEIGHT) |
| tile_height = TILE_HEIGHT; |
| |
| /* vptr luma memory index for this tile */ |
| luma_idx = y * TILE_HEIGHT * v_luma_stride + x * TILE_WIDTH; |
| |
| /* vptr chroma memory index for this tile */ |
| /* XXX: remove divisions */ |
| chroma_idx = y * TILE_HEIGHT / 2 * v_chroma_stride + x * TILE_WIDTH; |
| |
| tile_height /= 2; // we copy 2 luma lines at once |
| while (tile_height--) { |
| vptr = v_luma + luma_idx; |
| cptr = c_luma; |
| memcpy (*dest, *src, tile_width); |
| c_luma += TILE_WIDTH; |
| luma_idx += v_luma_stride; |
| |
| vptr = v_luma + luma_idx; |
| cptr = c_luma; |
| memcpy (*dest, *src, tile_width); |
| c_luma += TILE_WIDTH; |
| luma_idx += v_luma_stride; |
| |
| vptr = v_chroma + chroma_idx; |
| cptr = c_chroma; |
| memcpy (*dest, *src, tile_width); |
| c_chroma += TILE_WIDTH; |
| chroma_idx += v_chroma_stride; |
| } |
| row_width -= TILE_WIDTH; |
| } |
| height -= TILE_HEIGHT; |
| } |
| gst_video_frame_unmap (&vframe); |
| ret = TRUE; |
| break; |
| |
| } |
| default: |
| GST_ERROR ("Unsupported color format %d", cinfo->color_format); |
| goto done; |
| break; |
| } |
| |
| done: |
| return ret; |
| } |
| |
| static const struct |
| { |
| gint id; |
| const gchar *str; |
| const gchar *alt_str; |
| } avc_profile_mapping_table[] = { |
| { |
| AVCProfileBaseline, "baseline", "constrained-baseline"}, { |
| AVCProfileMain, "main", NULL}, { |
| AVCProfileExtended, "extended", NULL}, { |
| AVCProfileHigh, "high"}, { |
| AVCProfileHigh10, "high-10", "high-10-intra"}, { |
| AVCProfileHigh422, "high-4:2:2", "high-4:2:2-intra"}, { |
| AVCProfileHigh444, "high-4:4:4", "high-4:4:4-intra"} |
| }; |
| |
| const gchar * |
| gst_amc_avc_profile_to_string (gint profile, const gchar ** alternative) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (avc_profile_mapping_table); i++) { |
| if (avc_profile_mapping_table[i].id == profile) { |
| *alternative = avc_profile_mapping_table[i].alt_str; |
| return avc_profile_mapping_table[i].str; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| gint |
| gst_amc_avc_profile_from_string (const gchar * profile) |
| { |
| gint i; |
| |
| g_return_val_if_fail (profile != NULL, -1); |
| |
| for (i = 0; i < G_N_ELEMENTS (avc_profile_mapping_table); i++) { |
| if (strcmp (avc_profile_mapping_table[i].str, profile) == 0 || |
| (avc_profile_mapping_table[i].alt_str && |
| strcmp (avc_profile_mapping_table[i].alt_str, profile) == 0)) |
| return avc_profile_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| const gchar *str; |
| } avc_level_mapping_table[] = { |
| { |
| AVCLevel1, "1"}, { |
| AVCLevel1b, "1b"}, { |
| AVCLevel11, "1.1"}, { |
| AVCLevel12, "1.2"}, { |
| AVCLevel13, "1.3"}, { |
| AVCLevel2, "2"}, { |
| AVCLevel21, "2.1"}, { |
| AVCLevel22, "2.2"}, { |
| AVCLevel3, "3"}, { |
| AVCLevel31, "3.1"}, { |
| AVCLevel32, "3.2"}, { |
| AVCLevel4, "4"}, { |
| AVCLevel41, "4.1"}, { |
| AVCLevel42, "4.2"}, { |
| AVCLevel5, "5"}, { |
| AVCLevel51, "5.1"} |
| }; |
| |
| const gchar * |
| gst_amc_avc_level_to_string (gint level) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (avc_level_mapping_table); i++) { |
| if (avc_level_mapping_table[i].id == level) |
| return avc_level_mapping_table[i].str; |
| } |
| |
| return NULL; |
| } |
| |
| gint |
| gst_amc_avc_level_from_string (const gchar * level) |
| { |
| gint i; |
| |
| g_return_val_if_fail (level != NULL, -1); |
| |
| for (i = 0; i < G_N_ELEMENTS (avc_level_mapping_table); i++) { |
| if (strcmp (avc_level_mapping_table[i].str, level) == 0) |
| return avc_level_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| gint gst_id; |
| } h263_profile_mapping_table[] = { |
| { |
| H263ProfileBaseline, 0}, { |
| H263ProfileH320Coding, 1}, { |
| H263ProfileBackwardCompatible, 2}, { |
| H263ProfileISWV2, 3}, { |
| H263ProfileISWV3, 4}, { |
| H263ProfileHighCompression, 5}, { |
| H263ProfileInternet, 6}, { |
| H263ProfileInterlace, 7}, { |
| H263ProfileHighLatency, 8} |
| }; |
| |
| gint |
| gst_amc_h263_profile_to_gst_id (gint profile) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (h263_profile_mapping_table); i++) { |
| if (h263_profile_mapping_table[i].id == profile) |
| return h263_profile_mapping_table[i].gst_id; |
| } |
| |
| return -1; |
| } |
| |
| gint |
| gst_amc_h263_profile_from_gst_id (gint profile) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (h263_profile_mapping_table); i++) { |
| if (h263_profile_mapping_table[i].gst_id == profile) |
| return h263_profile_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| gint gst_id; |
| } h263_level_mapping_table[] = { |
| { |
| H263Level10, 10}, { |
| H263Level20, 20}, { |
| H263Level30, 30}, { |
| H263Level40, 40}, { |
| H263Level50, 50}, { |
| H263Level60, 60}, { |
| H263Level70, 70} |
| }; |
| |
| gint |
| gst_amc_h263_level_to_gst_id (gint level) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (h263_level_mapping_table); i++) { |
| if (h263_level_mapping_table[i].id == level) |
| return h263_level_mapping_table[i].gst_id; |
| } |
| |
| return -1; |
| } |
| |
| gint |
| gst_amc_h263_level_from_gst_id (gint level) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (h263_level_mapping_table); i++) { |
| if (h263_level_mapping_table[i].gst_id == level) |
| return h263_level_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| const gchar *str; |
| } mpeg4_profile_mapping_table[] = { |
| { |
| MPEG4ProfileSimple, "simple"}, { |
| MPEG4ProfileSimpleScalable, "simple-scalable"}, { |
| MPEG4ProfileCore, "core"}, { |
| MPEG4ProfileMain, "main"}, { |
| MPEG4ProfileNbit, "n-bit"}, { |
| MPEG4ProfileScalableTexture, "scalable"}, { |
| MPEG4ProfileSimpleFace, "simple-face"}, { |
| MPEG4ProfileSimpleFBA, "simple-fba"}, { |
| MPEG4ProfileBasicAnimated, "basic-animated-texture"}, { |
| MPEG4ProfileHybrid, "hybrid"}, { |
| MPEG4ProfileAdvancedRealTime, "advanced-real-time"}, { |
| MPEG4ProfileCoreScalable, "core-scalable"}, { |
| MPEG4ProfileAdvancedCoding, "advanced-coding-efficiency"}, { |
| MPEG4ProfileAdvancedCore, "advanced-core"}, { |
| MPEG4ProfileAdvancedScalable, "advanced-scalable-texture"}, { |
| MPEG4ProfileAdvancedSimple, "advanced-simple"} |
| }; |
| |
| const gchar * |
| gst_amc_mpeg4_profile_to_string (gint profile) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (mpeg4_profile_mapping_table); i++) { |
| if (mpeg4_profile_mapping_table[i].id == profile) |
| return mpeg4_profile_mapping_table[i].str; |
| } |
| |
| return NULL; |
| } |
| |
| gint |
| gst_amc_avc_mpeg4_profile_from_string (const gchar * profile) |
| { |
| gint i; |
| |
| g_return_val_if_fail (profile != NULL, -1); |
| |
| for (i = 0; i < G_N_ELEMENTS (mpeg4_profile_mapping_table); i++) { |
| if (strcmp (mpeg4_profile_mapping_table[i].str, profile) == 0) |
| return mpeg4_profile_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| const gchar *str; |
| } mpeg4_level_mapping_table[] = { |
| { |
| MPEG4Level0, "0"}, { |
| MPEG4Level0b, "0b"}, { |
| MPEG4Level1, "1"}, { |
| MPEG4Level2, "2"}, { |
| MPEG4Level3, "3"}, { |
| MPEG4Level4, "4"}, { |
| MPEG4Level4a, "4a"}, { |
| MPEG4Level5, "5"},}; |
| |
| const gchar * |
| gst_amc_mpeg4_level_to_string (gint level) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (mpeg4_level_mapping_table); i++) { |
| if (mpeg4_level_mapping_table[i].id == level) |
| return mpeg4_level_mapping_table[i].str; |
| } |
| |
| return NULL; |
| } |
| |
| gint |
| gst_amc_mpeg4_level_from_string (const gchar * level) |
| { |
| gint i; |
| |
| g_return_val_if_fail (level != NULL, -1); |
| |
| for (i = 0; i < G_N_ELEMENTS (mpeg4_level_mapping_table); i++) { |
| if (strcmp (mpeg4_level_mapping_table[i].str, level) == 0) |
| return mpeg4_level_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| gint id; |
| const gchar *str; |
| } aac_profile_mapping_table[] = { |
| { |
| AACObjectMain, "main"}, { |
| AACObjectLC, "lc"}, { |
| AACObjectSSR, "ssr"}, { |
| AACObjectLTP, "ltp"} |
| }; |
| |
| const gchar * |
| gst_amc_aac_profile_to_string (gint profile) |
| { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (aac_profile_mapping_table); i++) { |
| if (aac_profile_mapping_table[i].id == profile) |
| return aac_profile_mapping_table[i].str; |
| } |
| |
| return NULL; |
| } |
| |
| gint |
| gst_amc_aac_profile_from_string (const gchar * profile) |
| { |
| gint i; |
| |
| g_return_val_if_fail (profile != NULL, -1); |
| |
| for (i = 0; i < G_N_ELEMENTS (aac_profile_mapping_table); i++) { |
| if (strcmp (aac_profile_mapping_table[i].str, profile) == 0) |
| return aac_profile_mapping_table[i].id; |
| } |
| |
| return -1; |
| } |
| |
| static const struct |
| { |
| guint32 mask; |
| GstAudioChannelPosition pos; |
| } channel_mapping_table[] = { |
| { |
| CHANNEL_OUT_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT}, { |
| CHANNEL_OUT_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, { |
| CHANNEL_OUT_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, { |
| CHANNEL_OUT_LOW_FREQUENCY, GST_AUDIO_CHANNEL_POSITION_LFE1}, { |
| CHANNEL_OUT_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_REAR_LEFT}, { |
| CHANNEL_OUT_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, { |
| CHANNEL_OUT_FRONT_LEFT_OF_CENTER, |
| GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER}, { |
| CHANNEL_OUT_FRONT_RIGHT_OF_CENTER, |
| GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, { |
| CHANNEL_OUT_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, { |
| CHANNEL_OUT_SIDE_LEFT, GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT}, { |
| CHANNEL_OUT_SIDE_RIGHT, GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}, { |
| CHANNEL_OUT_TOP_CENTER, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_INVALID}, { |
| CHANNEL_OUT_TOP_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_INVALID} |
| }; |
| |
| gboolean |
| gst_amc_audio_channel_mask_to_positions (guint32 channel_mask, gint channels, |
| GstAudioChannelPosition * pos) |
| { |
| gint i, j; |
| |
| if (channel_mask == 0) { |
| if (channels == 1) { |
| pos[0] = GST_AUDIO_CHANNEL_POSITION_MONO; |
| return TRUE; |
| } |
| if (channels == 2) { |
| pos[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| pos[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| return TRUE; |
| } |
| |
| /* Now let the guesswork begin, these are the |
| * AAC default channel assignments for these numbers |
| * of channels */ |
| if (channels == 3) { |
| channel_mask = |
| CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | |
| CHANNEL_OUT_FRONT_CENTER; |
| } else if (channels == 4) { |
| channel_mask = |
| CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | |
| CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER; |
| } else if (channels == 5) { |
| channel_mask = |
| CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | |
| CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_LEFT | |
| CHANNEL_OUT_BACK_RIGHT; |
| } else if (channels == 6) { |
| channel_mask = |
| CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | |
| CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_LEFT | |
| CHANNEL_OUT_BACK_RIGHT | CHANNEL_OUT_LOW_FREQUENCY; |
| } else if (channels == 8) { |
| channel_mask = |
| CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | |
| CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_LEFT | |
| CHANNEL_OUT_BACK_RIGHT | CHANNEL_OUT_LOW_FREQUENCY | |
| CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER; |
| } |
| } |
| |
| for (i = 0, j = 0; i < G_N_ELEMENTS (channel_mapping_table); i++) { |
| if ((channel_mask & channel_mapping_table[i].mask)) { |
| pos[j++] = channel_mapping_table[i].pos; |
| if (channel_mapping_table[i].pos == GST_AUDIO_CHANNEL_POSITION_INVALID) { |
| memset (pos, 0, sizeof (GstAudioChannelPosition) * channels); |
| GST_ERROR ("Unable to map channel mask 0x%08x", |
| channel_mapping_table[i].mask); |
| return FALSE; |
| } |
| if (j == channels) |
| break; |
| } |
| } |
| |
| if (j != channels) { |
| memset (pos, 0, sizeof (GstAudioChannelPosition) * channels); |
| GST_ERROR ("Unable to map all channel positions in mask 0x%08x", |
| channel_mask); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| guint32 |
| gst_amc_audio_channel_mask_from_positions (GstAudioChannelPosition * positions, |
| gint channels) |
| { |
| gint i, j; |
| guint32 channel_mask = 0; |
| |
| if (channels == 1 && !positions) |
| return CHANNEL_OUT_FRONT_CENTER; |
| if (channels == 2 && !positions) |
| return CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT; |
| |
| for (i = 0; i < channels; i++) { |
| if (positions[i] == GST_AUDIO_CHANNEL_POSITION_INVALID) |
| return 0; |
| |
| for (j = 0; j < G_N_ELEMENTS (channel_mapping_table); j++) { |
| if (channel_mapping_table[j].pos == positions[i]) { |
| channel_mask |= channel_mapping_table[j].mask; |
| break; |
| } |
| } |
| |
| if (j == G_N_ELEMENTS (channel_mapping_table)) { |
| GST_ERROR ("Unable to map channel position %d", positions[i]); |
| return 0; |
| } |
| } |
| |
| return channel_mask; |
| } |
| |
| static gchar * |
| create_type_name (const gchar * parent_name, const gchar * codec_name) |
| { |
| gchar *typified_name; |
| gint i, k; |
| gint parent_name_len = strlen (parent_name); |
| gint codec_name_len = strlen (codec_name); |
| gboolean upper = TRUE; |
| |
| typified_name = g_new0 (gchar, parent_name_len + 1 + strlen (codec_name) + 1); |
| memcpy (typified_name, parent_name, parent_name_len); |
| typified_name[parent_name_len] = '-'; |
| |
| for (i = 0, k = 0; i < codec_name_len; i++) { |
| if (g_ascii_isalnum (codec_name[i])) { |
| if (upper) |
| typified_name[parent_name_len + 1 + k++] = |
| g_ascii_toupper (codec_name[i]); |
| else |
| typified_name[parent_name_len + 1 + k++] = |
| g_ascii_tolower (codec_name[i]); |
| |
| upper = FALSE; |
| } else { |
| /* Skip all non-alnum chars and start a new upper case word */ |
| upper = TRUE; |
| } |
| } |
| |
| return typified_name; |
| } |
| |
| static gchar * |
| create_element_name (gboolean video, gboolean encoder, const gchar * codec_name) |
| { |
| #define PREFIX_LEN 10 |
| static const gchar *prefixes[] = { |
| "amcviddec-", |
| "amcauddec-", |
| "amcvidenc-", |
| "amcaudenc-" |
| }; |
| gchar *element_name; |
| gint i, k; |
| gint codec_name_len = strlen (codec_name); |
| const gchar *prefix; |
| |
| if (video && !encoder) |
| prefix = prefixes[0]; |
| else if (!video && !encoder) |
| prefix = prefixes[1]; |
| else if (video && encoder) |
| prefix = prefixes[2]; |
| else |
| prefix = prefixes[3]; |
| |
| element_name = g_new0 (gchar, PREFIX_LEN + strlen (codec_name) + 1); |
| memcpy (element_name, prefix, PREFIX_LEN); |
| |
| for (i = 0, k = 0; i < codec_name_len; i++) { |
| if (g_ascii_isalnum (codec_name[i])) { |
| element_name[PREFIX_LEN + k++] = g_ascii_tolower (codec_name[i]); |
| } |
| /* Skip all non-alnum chars */ |
| } |
| |
| return element_name; |
| } |
| |
| #undef PREFIX_LEN |
| |
| static gboolean |
| register_codecs (GstPlugin * plugin) |
| { |
| gboolean ret = TRUE; |
| GList *l; |
| |
| GST_DEBUG ("Registering plugins"); |
| |
| for (l = codec_infos; l; l = l->next) { |
| GstAmcCodecInfo *codec_info = l->data; |
| gboolean is_audio = FALSE; |
| gboolean is_video = FALSE; |
| gint i; |
| gint n_types; |
| |
| GST_DEBUG ("Registering codec '%s'", codec_info->name); |
| for (i = 0; i < codec_info->n_supported_types; i++) { |
| GstAmcCodecType *codec_type = &codec_info->supported_types[i]; |
| |
| if (g_str_has_prefix (codec_type->mime, "audio/")) |
| is_audio = TRUE; |
| else if (g_str_has_prefix (codec_type->mime, "video/")) |
| is_video = TRUE; |
| } |
| |
| n_types = 0; |
| if (is_audio) |
| n_types++; |
| if (is_video) |
| n_types++; |
| |
| for (i = 0; i < n_types; i++) { |
| GTypeQuery type_query; |
| GTypeInfo type_info = { 0, }; |
| GType type, subtype; |
| gchar *type_name, *element_name; |
| guint rank; |
| |
| if (is_video) { |
| if (codec_info->is_encoder) |
| type = gst_amc_video_enc_get_type (); |
| else |
| type = gst_amc_video_dec_get_type (); |
| } else if (is_audio && !codec_info->is_encoder) { |
| type = gst_amc_audio_dec_get_type (); |
| } else { |
| GST_DEBUG ("Skipping unsupported codec type"); |
| continue; |
| } |
| |
| g_type_query (type, &type_query); |
| memset (&type_info, 0, sizeof (type_info)); |
| type_info.class_size = type_query.class_size; |
| type_info.instance_size = type_query.instance_size; |
| type_name = create_type_name (type_query.type_name, codec_info->name); |
| |
| if (g_type_from_name (type_name) != G_TYPE_INVALID) { |
| GST_ERROR ("Type '%s' already exists for codec '%s'", type_name, |
| codec_info->name); |
| g_free (type_name); |
| continue; |
| } |
| |
| subtype = g_type_register_static (type, type_name, &type_info, 0); |
| g_free (type_name); |
| |
| g_type_set_qdata (subtype, gst_amc_codec_info_quark, codec_info); |
| |
| element_name = |
| create_element_name (is_video, codec_info->is_encoder, |
| codec_info->name); |
| |
| /* Give the Google software codec a secondary rank, |
| * everything else is likely a hardware codec, except |
| * OMX.SEC.*.sw.dec (as seen in Galaxy S4) */ |
| if (g_str_has_prefix (codec_info->name, "OMX.google") || |
| g_str_has_suffix (codec_info->name, ".sw.dec")) |
| rank = GST_RANK_SECONDARY; |
| else |
| rank = GST_RANK_PRIMARY; |
| |
| ret |= gst_element_register (plugin, element_name, rank, subtype); |
| g_free (element_name); |
| |
| is_video = FALSE; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| const gchar *ignore; |
| |
| GST_DEBUG_CATEGORY_INIT (gst_amc_debug, "amc", 0, "android-media-codec"); |
| |
| pthread_key_create (¤t_jni_env, gst_amc_detach_current_thread); |
| |
| if (!initialize_java_vm ()) |
| return FALSE; |
| |
| gst_plugin_add_dependency_simple (plugin, NULL, "/etc", "media_codecs.xml", |
| GST_PLUGIN_DEPENDENCY_FLAG_NONE); |
| |
| if (!get_java_classes ()) |
| return FALSE; |
| |
| /* Set this to TRUE to allow registering decoders that have |
| * any unknown color formats, or encoders that only have |
| * unknown color formats |
| */ |
| ignore = g_getenv ("GST_AMC_IGNORE_UNKNOWN_COLOR_FORMATS"); |
| if (ignore && strcmp (ignore, "yes") == 0) |
| ignore_unknown_color_formats = TRUE; |
| |
| if (!scan_codecs (plugin)) |
| return FALSE; |
| |
| gst_amc_codec_info_quark = g_quark_from_static_string ("gst-amc-codec-info"); |
| |
| if (!register_codecs (plugin)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| void |
| gst_amc_codec_info_to_caps (const GstAmcCodecInfo * codec_info, |
| GstCaps ** sink_caps, GstCaps ** src_caps) |
| { |
| GstCaps *raw_ret = NULL, *encoded_ret = NULL; |
| gint i; |
| |
| if (codec_info->is_encoder) { |
| if (sink_caps) |
| *sink_caps = raw_ret = gst_caps_new_empty (); |
| |
| if (src_caps) |
| *src_caps = encoded_ret = gst_caps_new_empty (); |
| } else { |
| if (sink_caps) |
| *sink_caps = encoded_ret = gst_caps_new_empty (); |
| |
| if (src_caps) |
| *src_caps = raw_ret = gst_caps_new_empty (); |
| } |
| |
| for (i = 0; i < codec_info->n_supported_types; i++) { |
| const GstAmcCodecType *type = &codec_info->supported_types[i]; |
| GstStructure *tmp, *tmp2, *tmp3; |
| |
| if (g_str_has_prefix (type->mime, "audio/")) { |
| if (raw_ret) { |
| tmp = gst_structure_new ("audio/x-raw", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "format", G_TYPE_STRING, GST_AUDIO_NE (S16), NULL); |
| |
| raw_ret = gst_caps_merge_structure (raw_ret, tmp); |
| } |
| |
| if (encoded_ret) { |
| if (strcmp (type->mime, "audio/mpeg") == 0) { |
| tmp = gst_structure_new ("audio/mpeg", |
| "mpegversion", G_TYPE_INT, 1, |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "parsed", G_TYPE_BOOLEAN, TRUE, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/3gpp") == 0) { |
| tmp = gst_structure_new ("audio/AMR", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/amr-wb") == 0) { |
| tmp = gst_structure_new ("audio/AMR-WB", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/mp4a-latm") == 0) { |
| gint j; |
| gboolean have_profile = FALSE; |
| GValue va = { 0, }; |
| GValue v = { 0, }; |
| |
| g_value_init (&va, GST_TYPE_LIST); |
| g_value_init (&v, G_TYPE_STRING); |
| g_value_set_string (&v, "raw"); |
| gst_value_list_append_value (&va, &v); |
| g_value_set_string (&v, "adts"); |
| gst_value_list_append_value (&va, &v); |
| g_value_unset (&v); |
| |
| tmp = gst_structure_new ("audio/mpeg", |
| "mpegversion", G_TYPE_INT, 4, |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "framed", G_TYPE_BOOLEAN, TRUE, NULL); |
| gst_structure_set_value (tmp, "stream-format", &va); |
| g_value_unset (&va); |
| |
| for (j = 0; j < type->n_profile_levels; j++) { |
| const gchar *profile; |
| |
| profile = |
| gst_amc_aac_profile_to_string (type->profile_levels[j].profile); |
| |
| if (!profile) { |
| GST_ERROR ("Unable to map AAC profile 0x%08x", |
| type->profile_levels[j].profile); |
| continue; |
| } |
| |
| tmp2 = gst_structure_copy (tmp); |
| gst_structure_set (tmp2, "profile", G_TYPE_STRING, profile, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp2); |
| |
| have_profile = TRUE; |
| } |
| |
| if (!have_profile) { |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| gst_structure_free (tmp); |
| } |
| } else if (strcmp (type->mime, "audio/g711-alaw") == 0) { |
| tmp = gst_structure_new ("audio/x-alaw", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/g711-mlaw") == 0) { |
| tmp = gst_structure_new ("audio/x-mulaw", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/vorbis") == 0) { |
| tmp = gst_structure_new ("audio/x-vorbis", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/flac") == 0) { |
| tmp = gst_structure_new ("audio/x-flac", |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "framed", G_TYPE_BOOLEAN, TRUE, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "audio/mpeg-L2") == 0) { |
| tmp = gst_structure_new ("audio/mpeg", |
| "mpegversion", G_TYPE_INT, 1, |
| "layer", G_TYPE_INT, 2, |
| "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "parsed", G_TYPE_BOOLEAN, TRUE, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| GST_WARNING ("Unsupported mimetype '%s'", type->mime); |
| } |
| } |
| } else if (g_str_has_prefix (type->mime, "video/")) { |
| if (raw_ret) { |
| gint j; |
| |
| for (j = 0; j < type->n_color_formats; j++) { |
| GstVideoFormat format; |
| |
| format = |
| gst_amc_color_format_to_video_format (codec_info, |
| type->mime, type->color_formats[j]); |
| if (format == GST_VIDEO_FORMAT_UNKNOWN) { |
| GST_WARNING ("Unknown color format 0x%08x", type->color_formats[j]); |
| continue; |
| } |
| |
| tmp = gst_structure_new ("video/x-raw", |
| "format", G_TYPE_STRING, gst_video_format_to_string (format), |
| "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, |
| "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); |
| |
| raw_ret = gst_caps_merge_structure (raw_ret, tmp); |
| } |
| } |
| |
| if (encoded_ret) { |
| if (strcmp (type->mime, "video/mp4v-es") == 0) { |
| gint j; |
| gboolean have_profile_level = FALSE; |
| |
| tmp = gst_structure_new ("video/mpeg", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, |
| 0, 1, G_MAXINT, 1, |
| "mpegversion", G_TYPE_INT, 4, |
| "systemstream", G_TYPE_BOOLEAN, FALSE, |
| "parsed", G_TYPE_BOOLEAN, TRUE, NULL); |
| |
| if (type->n_profile_levels) { |
| for (j = type->n_profile_levels - 1; j >= 0; j--) { |
| const gchar *profile; |
| |
| profile = |
| gst_amc_mpeg4_profile_to_string (type->profile_levels[j]. |
| profile); |
| if (!profile) { |
| GST_ERROR ("Unable to map MPEG4 profile 0x%08x", |
| type->profile_levels[j].profile); |
| continue; |
| } |
| |
| tmp2 = gst_structure_copy (tmp); |
| gst_structure_set (tmp2, "profile", G_TYPE_STRING, profile, NULL); |
| |
| /* Don't put the level restrictions on the sinkpad caps for decoders, |
| * see 2b94641a4 */ |
| if (codec_info->is_encoder) { |
| const gchar *level; |
| gint k; |
| GValue va = { 0, }; |
| GValue v = { 0, }; |
| |
| g_value_init (&va, GST_TYPE_LIST); |
| g_value_init (&v, G_TYPE_STRING); |
| |
| for (k = 1; k <= type->profile_levels[j].level && k != 0; |
| k <<= 1) { |
| level = gst_amc_mpeg4_level_to_string (k); |
| if (!level) |
| continue; |
| |
| g_value_set_string (&v, level); |
| gst_value_list_append_value (&va, &v); |
| g_value_reset (&v); |
| } |
| |
| gst_structure_set_value (tmp2, "level", &va); |
| g_value_unset (&va); |
| g_value_unset (&v); |
| } |
| |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp2); |
| have_profile_level = TRUE; |
| } |
| } |
| |
| if (!have_profile_level) { |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| gst_structure_free (tmp); |
| } |
| |
| tmp = gst_structure_new ("video/x-divx", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, |
| 0, 1, G_MAXINT, 1, |
| "divxversion", GST_TYPE_INT_RANGE, 3, 5, |
| "parsed", G_TYPE_BOOLEAN, TRUE, NULL); |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "video/3gpp") == 0) { |
| gint j; |
| gboolean have_profile_level = FALSE; |
| |
| tmp = gst_structure_new ("video/x-h263", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, |
| 0, 1, G_MAXINT, 1, |
| "parsed", G_TYPE_BOOLEAN, TRUE, |
| "variant", G_TYPE_STRING, "itu", NULL); |
| |
| if (type->n_profile_levels) { |
| for (j = type->n_profile_levels - 1; j >= 0; j--) { |
| gint profile; |
| |
| profile = |
| gst_amc_h263_profile_to_gst_id (type->profile_levels[j]. |
| profile); |
| |
| if (profile == -1) { |
| GST_ERROR ("Unable to map h263 profile 0x%08x", |
| type->profile_levels[j].profile); |
| continue; |
| } |
| |
| tmp2 = gst_structure_copy (tmp); |
| gst_structure_set (tmp2, "profile", G_TYPE_UINT, profile, NULL); |
| |
| if (codec_info->is_encoder) { |
| gint k; |
| gint level; |
| GValue va = { 0, }; |
| GValue v = { 0, }; |
| |
| g_value_init (&va, GST_TYPE_LIST); |
| g_value_init (&v, G_TYPE_UINT); |
| |
| for (k = 1; k <= type->profile_levels[j].level && k != 0; |
| k <<= 1) { |
| level = gst_amc_h263_level_to_gst_id (k); |
| if (level == -1) |
| continue; |
| |
| g_value_set_uint (&v, level); |
| gst_value_list_append_value (&va, &v); |
| g_value_reset (&v); |
| } |
| |
| gst_structure_set_value (tmp2, "level", &va); |
| g_value_unset (&va); |
| g_value_unset (&v); |
| } |
| |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp2); |
| have_profile_level = TRUE; |
| } |
| } |
| |
| if (!have_profile_level) { |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| gst_structure_free (tmp); |
| } |
| } else if (strcmp (type->mime, "video/avc") == 0) { |
| gint j; |
| gboolean have_profile_level = FALSE; |
| |
| tmp = gst_structure_new ("video/x-h264", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, |
| 0, 1, G_MAXINT, 1, |
| "parsed", G_TYPE_BOOLEAN, TRUE, |
| "stream-format", G_TYPE_STRING, "byte-stream", |
| "alignment", G_TYPE_STRING, "au", NULL); |
| |
| if (type->n_profile_levels) { |
| for (j = type->n_profile_levels - 1; j >= 0; j--) { |
| const gchar *profile, *alternative = NULL; |
| |
| profile = |
| gst_amc_avc_profile_to_string (type->profile_levels[j]. |
| profile, &alternative); |
| |
| if (!profile) { |
| GST_ERROR ("Unable to map H264 profile 0x%08x", |
| type->profile_levels[j].profile); |
| continue; |
| } |
| |
| tmp2 = gst_structure_copy (tmp); |
| gst_structure_set (tmp2, "profile", G_TYPE_STRING, profile, NULL); |
| |
| if (alternative) { |
| tmp3 = gst_structure_copy (tmp); |
| gst_structure_set (tmp3, "profile", G_TYPE_STRING, alternative, |
| NULL); |
| } else |
| tmp3 = NULL; |
| |
| if (codec_info->is_encoder) { |
| const gchar *level; |
| gint k; |
| GValue va = { 0, }; |
| GValue v = { 0, }; |
| |
| g_value_init (&va, GST_TYPE_LIST); |
| g_value_init (&v, G_TYPE_STRING); |
| for (k = 1; k <= type->profile_levels[j].level && k != 0; |
| k <<= 1) { |
| level = gst_amc_avc_level_to_string (k); |
| if (!level) |
| continue; |
| |
| g_value_set_string (&v, level); |
| gst_value_list_append_value (&va, &v); |
| g_value_reset (&v); |
| } |
| |
| gst_structure_set_value (tmp2, "level", &va); |
| if (tmp3) |
| gst_structure_set_value (tmp3, "level", &va); |
| |
| g_value_unset (&va); |
| g_value_unset (&v); |
| } |
| |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp2); |
| if (tmp3) |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp3); |
| have_profile_level = TRUE; |
| } |
| } |
| |
| if (!have_profile_level) { |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| gst_structure_free (tmp); |
| } |
| } else if (strcmp (type->mime, "video/x-vnd.on2.vp8") == 0) { |
| tmp = gst_structure_new ("video/x-vp8", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); |
| |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else if (strcmp (type->mime, "video/mpeg2") == 0) { |
| tmp = gst_structure_new ("video/mpeg", |
| "width", GST_TYPE_INT_RANGE, 16, 4096, |
| "height", GST_TYPE_INT_RANGE, 16, 4096, |
| "framerate", GST_TYPE_FRACTION_RANGE, |
| 0, 1, G_MAXINT, 1, |
| "mpegversion", GST_TYPE_INT_RANGE, 1, 2, |
| "systemstream", G_TYPE_BOOLEAN, FALSE, |
| "parsed", G_TYPE_BOOLEAN, TRUE, NULL); |
| |
| encoded_ret = gst_caps_merge_structure (encoded_ret, tmp); |
| } else { |
| GST_WARNING ("Unsupported mimetype '%s'", type->mime); |
| } |
| } |
| } |
| } |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| androidmedia, |
| "Android Media plugin", |
| plugin_init, |
| PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |