mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-06-15 20:40:39 +00:00
545 lines
13 KiB
C
545 lines
13 KiB
C
/* GStreamer
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1
|
|
*
|
|
* Copyright (C) 2019 Igalia S.L.
|
|
* Copyright (C) 2022, 2023 Collabora Ltd.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <gst/gst.h>
|
|
|
|
#include "gstmsemediatype-private.h"
|
|
|
|
static const gchar tspecials[] = {
|
|
'(', ')', '<', '>', '@', ',', ';', ':',
|
|
'\\', '"', '/', '[', ']', '?', '=',
|
|
0,
|
|
};
|
|
|
|
static gboolean
|
|
is_tspecial (const gchar c)
|
|
{
|
|
for (const gchar * tspecial = tspecials; *tspecial != 0; tspecial++) {
|
|
if (*tspecial == c) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
is_token_character (const gchar c)
|
|
{
|
|
if (g_ascii_iscntrl (c) || is_tspecial (c)) {
|
|
return FALSE;
|
|
}
|
|
return g_ascii_isgraph (c);
|
|
}
|
|
|
|
static gboolean
|
|
is_ascii (const gchar c)
|
|
{
|
|
return c > 0 && c <= G_MAXINT8;
|
|
}
|
|
|
|
static gboolean
|
|
is_eos (const gchar c)
|
|
{
|
|
return c == '\0';
|
|
}
|
|
|
|
static void
|
|
ignore_whitespace (const gchar ** input)
|
|
{
|
|
g_return_if_fail (input != NULL);
|
|
|
|
const gchar *output;
|
|
for (output = *input; g_ascii_isspace (*output); output++) {
|
|
}
|
|
*input = output;
|
|
}
|
|
|
|
static gboolean
|
|
any_of (const gchar ** input, const gchar ** choices, gchar ** value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (choices != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
for (const gchar ** choice = choices; *choice != NULL; choice++) {
|
|
gulong prefix_length = strlen (*choice);
|
|
if (g_str_has_prefix (*input, *choice)) {
|
|
*value = g_strndup (*input, prefix_length);
|
|
*input += prefix_length;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
literal (const gchar ** input, const gchar * value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
if (!g_str_has_prefix (*input, value)) {
|
|
return FALSE;
|
|
}
|
|
*input += strlen (value);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
token (const gchar ** input, gchar ** value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
const gchar *unparsed = *input;
|
|
for (; is_token_character (unparsed[0]); unparsed++) {
|
|
}
|
|
gsize length = unparsed - *input;
|
|
if (length < 1) {
|
|
return FALSE;
|
|
}
|
|
*value = g_strndup (*input, length);
|
|
*input = unparsed;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
quoted_string_char (const gchar ** input, gchar * value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
const gchar *unparsed = *input;
|
|
char c = unparsed[0];
|
|
if (!is_ascii (c)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (c == '"' || c == '\\' || c == '\r') {
|
|
return FALSE;
|
|
}
|
|
|
|
*value = c;
|
|
(*input)++;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
escaped_ascii_char (const gchar ** input, gchar * value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
if (!literal (input, "\\")) {
|
|
return FALSE;
|
|
}
|
|
|
|
gchar c = (*input)[0];
|
|
if (!is_ascii (c)) {
|
|
return FALSE;
|
|
}
|
|
|
|
*value = c;
|
|
(*input)++;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
quoted_string (const gchar ** input, gchar ** value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
const gchar *unparsed = *input;
|
|
if (!literal (&unparsed, "\"")) {
|
|
return FALSE;
|
|
}
|
|
|
|
GString *string_value = g_string_new (NULL);
|
|
for (;;) {
|
|
if (literal (&unparsed, "\"")) {
|
|
*input = unparsed;
|
|
*value = g_string_free (string_value, FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
if (is_eos (unparsed[0])) {
|
|
goto error;
|
|
}
|
|
|
|
gchar c;
|
|
|
|
if (quoted_string_char (&unparsed, &c)) {
|
|
g_string_append_c (string_value, c);
|
|
continue;
|
|
}
|
|
|
|
if (escaped_ascii_char (&unparsed, &c)) {
|
|
g_string_append_c (string_value, c);
|
|
continue;
|
|
}
|
|
|
|
goto error;
|
|
}
|
|
error:
|
|
g_string_free (string_value, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
static const gchar *discrete_media_types[] = {
|
|
"text",
|
|
"image",
|
|
"audio",
|
|
"video",
|
|
"application",
|
|
NULL,
|
|
};
|
|
|
|
static gboolean
|
|
discrete_media_type (const gchar ** input, gchar ** value)
|
|
{
|
|
return any_of (input, discrete_media_types, value);
|
|
}
|
|
|
|
static gboolean
|
|
composite_media_type (const gchar ** input, gchar ** value)
|
|
{
|
|
return token (input, value);
|
|
}
|
|
|
|
static gboolean
|
|
media_type_parameter (const gchar ** input, gchar ** param_name,
|
|
gchar ** param_value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (param_name != NULL, FALSE);
|
|
g_return_val_if_fail (param_value != NULL, FALSE);
|
|
|
|
ignore_whitespace (input);
|
|
if (!token (input, param_name)) {
|
|
return FALSE;
|
|
}
|
|
if (!literal (input, "=")) {
|
|
return FALSE;
|
|
}
|
|
if (!token (input, param_value)) {
|
|
if (!quoted_string (input, param_value)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
ignore_whitespace (input);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
media_type (const gchar ** input, gchar ** value)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (value != NULL, FALSE);
|
|
|
|
gchar *discrete = NULL;
|
|
gchar *composite = NULL;
|
|
|
|
if (!discrete_media_type (input, &discrete)) {
|
|
goto error;
|
|
}
|
|
if (!literal (input, "/")) {
|
|
goto error;
|
|
}
|
|
if (!composite_media_type (input, &composite)) {
|
|
goto error;
|
|
}
|
|
|
|
*value = g_strdup_printf ("%s/%s", discrete, composite);
|
|
g_clear_pointer (&discrete, g_free);
|
|
g_clear_pointer (&composite, g_free);
|
|
return TRUE;
|
|
|
|
error:
|
|
g_clear_pointer (&discrete, g_free);
|
|
g_clear_pointer (&composite, g_free);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
media_type_codecs (const gchar ** input, gchar *** codecs)
|
|
{
|
|
g_return_val_if_fail (input != NULL, FALSE);
|
|
g_return_val_if_fail (codecs != NULL, FALSE);
|
|
|
|
gchar *param_name = NULL;
|
|
gchar *codecs_value = NULL;
|
|
while (!is_eos (*input[0])
|
|
&& media_type_parameter (input, ¶m_name, &codecs_value)) {
|
|
gboolean is_codecs = g_strcmp0 (param_name, "codecs") == 0;
|
|
g_clear_pointer (¶m_name, g_free);
|
|
if (is_codecs) {
|
|
break;
|
|
}
|
|
g_clear_pointer (&codecs_value, g_free);
|
|
}
|
|
if (codecs_value == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
gchar **codec_names = g_strsplit (codecs_value, ",", 0);
|
|
guint n_codecs = g_strv_length (codec_names);
|
|
GPtrArray *codecs_array = g_ptr_array_new_full (n_codecs, NULL);
|
|
for (guint i = 0; i < n_codecs; i++) {
|
|
gchar *codec = g_strstrip (g_strdup (codec_names[i]));
|
|
if (g_strcmp0 (codec, "") != 0) {
|
|
g_ptr_array_add (codecs_array, g_strdup (codec));
|
|
}
|
|
g_clear_pointer (&codec, g_free);
|
|
}
|
|
g_ptr_array_add (codecs_array, NULL);
|
|
*codecs = (gchar **) g_ptr_array_free (codecs_array, FALSE);
|
|
|
|
g_clear_pointer (&codecs_value, g_free);
|
|
g_strfreev (codec_names);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_media_source_media_type_parse (GstMediaSourceMediaType * self,
|
|
const gchar * type)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (type != NULL, FALSE);
|
|
|
|
gchar *lowercase = g_ascii_strdown (type, -1);
|
|
const gchar *input = lowercase;
|
|
|
|
ignore_whitespace (&input);
|
|
if (!media_type (&input, &self->mime_type)) {
|
|
g_free (lowercase);
|
|
return FALSE;
|
|
}
|
|
self->codecs = NULL;
|
|
ignore_whitespace (&input);
|
|
if (is_eos (input[0])) {
|
|
g_free (lowercase);
|
|
return TRUE;
|
|
}
|
|
if (!literal (&input, ";")) {
|
|
g_free (lowercase);
|
|
return TRUE;
|
|
}
|
|
ignore_whitespace (&input);
|
|
media_type_codecs (&input, &self->codecs);
|
|
g_free (lowercase);
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gboolean
|
|
has_any_prefix (const gchar * str, const gchar * prefix, ...)
|
|
{
|
|
if (g_str_has_prefix (str, prefix)) {
|
|
return TRUE;
|
|
}
|
|
|
|
va_list varargs;
|
|
|
|
va_start (varargs, prefix);
|
|
for (const gchar * arg = va_arg (varargs, const gchar *); arg != NULL;
|
|
arg = va_arg (varargs, const gchar *))
|
|
{
|
|
if (g_str_has_prefix (str, arg)) {
|
|
va_end (varargs);
|
|
return TRUE;
|
|
}
|
|
}
|
|
va_end (varargs);
|
|
return FALSE;
|
|
}
|
|
|
|
static const gchar *
|
|
patch_media_type (const gchar * media_type)
|
|
{
|
|
if (g_strcmp0 (media_type, "video/mp4") == 0) {
|
|
return "video/quicktime";
|
|
}
|
|
if (g_strcmp0 (media_type, "audio/mp4") == 0 ||
|
|
g_strcmp0 (media_type, "audio/aac") == 0) {
|
|
return "audio/x-m4a";
|
|
}
|
|
if (!has_any_prefix (media_type, "audio/", "video/", NULL)) {
|
|
return NULL;
|
|
}
|
|
return media_type;
|
|
}
|
|
|
|
static GstStaticCaps h264_caps = GST_STATIC_CAPS ("video/x-h264");
|
|
static GstStaticCaps h265_caps = GST_STATIC_CAPS ("video/x-h265");
|
|
static GstStaticCaps av1_caps = GST_STATIC_CAPS ("video/x-av1");
|
|
static GstStaticCaps vp8_caps = GST_STATIC_CAPS ("video/x-vp8");
|
|
static GstStaticCaps vp9_caps = GST_STATIC_CAPS ("video/x-vp9");
|
|
|
|
static GstStaticCaps vorbis_caps = GST_STATIC_CAPS ("audio/x-vorbis");
|
|
static GstStaticCaps opus_caps = GST_STATIC_CAPS ("audio/x-opus");
|
|
static GstStaticCaps flac_caps = GST_STATIC_CAPS ("audio/x-flac");
|
|
static GstStaticCaps mp3_caps =
|
|
GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)1, layer=(int)3");
|
|
static GstStaticCaps aac_caps =
|
|
GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)4");
|
|
|
|
static inline GstStaticCaps *
|
|
mse_codec_id_to_static_caps (const gchar * codec_id)
|
|
{
|
|
if (has_any_prefix (codec_id, "avc", "x-h264", "mp4v", NULL)) {
|
|
return &h264_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "hvc1", "hev1", "x-h265", NULL)) {
|
|
return &h265_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "av01", "av1", "x-av1", NULL)) {
|
|
return &av1_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "vp8", "x-vp8", NULL)) {
|
|
return &vp8_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "vp9", "vp09", "x-vp9", NULL)) {
|
|
return &vp9_caps;
|
|
}
|
|
|
|
if (has_any_prefix (codec_id, "mpeg", "mp4a", NULL)) {
|
|
return &aac_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "vorbis", "x-vorbis", NULL)) {
|
|
return &vorbis_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "opus", "x-opus", NULL)) {
|
|
return &opus_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "flac", "x-flac", NULL)) {
|
|
return &flac_caps;
|
|
}
|
|
if (has_any_prefix (codec_id, "mp3", "audio/mp3", NULL)) {
|
|
return &mp3_caps;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
is_supported (const GstCaps * caps, GList * elements)
|
|
{
|
|
GList *supporting_elements = gst_element_factory_list_filter (elements, caps,
|
|
GST_PAD_SINK, FALSE);
|
|
gboolean supported = supporting_elements != NULL;
|
|
gst_plugin_feature_list_free (supporting_elements);
|
|
return supported;
|
|
}
|
|
|
|
static gboolean
|
|
is_codec_supported (const gchar * codec_id, GList * elements)
|
|
{
|
|
GstStaticCaps *static_caps = mse_codec_id_to_static_caps (codec_id);
|
|
if (static_caps == NULL) {
|
|
return FALSE;
|
|
}
|
|
GstCaps *codec_caps = gst_static_caps_get (static_caps);
|
|
gboolean supported = is_supported (codec_caps, elements);
|
|
gst_clear_caps (&codec_caps);
|
|
return supported;
|
|
}
|
|
|
|
static inline GList *
|
|
get_srcpad_elements (void)
|
|
{
|
|
return gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DEMUXER
|
|
| GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_DECRYPTOR |
|
|
GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_SINK |
|
|
GST_ELEMENT_FACTORY_TYPE_PARSER, GST_RANK_MARGINAL);
|
|
}
|
|
|
|
gboolean
|
|
gst_media_source_media_type_is_caps_supported (const GstCaps * caps)
|
|
{
|
|
GList *elements = get_srcpad_elements ();
|
|
gboolean supported = is_supported (caps, elements);
|
|
gst_plugin_feature_list_free (elements);
|
|
return supported;
|
|
}
|
|
|
|
gboolean
|
|
gst_media_source_media_type_is_supported (GstMediaSourceMediaType * self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (self->mime_type != NULL, FALSE);
|
|
|
|
const gchar *mime_type = patch_media_type (self->mime_type);
|
|
if (mime_type == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
GList *elements = get_srcpad_elements ();
|
|
GstCaps *caps = gst_caps_from_string (mime_type);
|
|
|
|
gboolean supported = is_supported (caps, elements);
|
|
for (gchar ** codec_id = self->codecs;
|
|
supported && codec_id && *codec_id; codec_id++) {
|
|
supported &= is_codec_supported (*codec_id, elements);
|
|
}
|
|
|
|
gst_plugin_feature_list_free (elements);
|
|
gst_clear_caps (&caps);
|
|
|
|
return supported;
|
|
}
|
|
|
|
static const gchar *generate_timestamps_formats[] = {
|
|
"audio/mpeg",
|
|
"audio/aac",
|
|
NULL,
|
|
};
|
|
|
|
gboolean
|
|
gst_media_source_media_type_generates_timestamp (GstMediaSourceMediaType * self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (self->mime_type != NULL, FALSE);
|
|
return g_strv_contains (generate_timestamps_formats, self->mime_type);
|
|
}
|
|
|
|
void
|
|
gst_media_source_media_type_reset (GstMediaSourceMediaType * self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_clear_pointer (&self->mime_type, g_free);
|
|
g_clear_pointer (&self->codecs, g_strfreev);
|
|
}
|
|
|
|
void
|
|
gst_media_source_media_type_free (GstMediaSourceMediaType * self)
|
|
{
|
|
gst_media_source_media_type_reset (self);
|
|
g_free (self);
|
|
}
|