mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-15 12:56:33 +00:00
556 lines
14 KiB
C
556 lines
14 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#include "audio.h"
|
|
|
|
#include <gst/gststructure.h>
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
#define GST_CAT_DEFAULT ensure_debug_category()
|
|
static GstDebugCategory *
|
|
ensure_debug_category (void)
|
|
{
|
|
static gsize cat_gonce = 0;
|
|
|
|
if (g_once_init_enter (&cat_gonce)) {
|
|
gsize cat_done;
|
|
|
|
cat_done = (gsize) _gst_debug_category_new ("audio-info", 0,
|
|
"audio-info object");
|
|
|
|
g_once_init_leave (&cat_gonce, cat_done);
|
|
}
|
|
|
|
return (GstDebugCategory *) cat_gonce;
|
|
}
|
|
#else
|
|
#define ensure_debug_category() /* NOOP */
|
|
#endif /* GST_DISABLE_GST_DEBUG */
|
|
|
|
|
|
/**
|
|
* gst_audio_info_copy:
|
|
* @info: a #GstAudioInfo
|
|
*
|
|
* Copy a GstAudioInfo structure.
|
|
*
|
|
* Returns: a new #GstAudioInfo. free with gst_audio_info_free.
|
|
*/
|
|
GstAudioInfo *
|
|
gst_audio_info_copy (const GstAudioInfo * info)
|
|
{
|
|
return g_memdup2 (info, sizeof (GstAudioInfo));
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_free:
|
|
* @info: a #GstAudioInfo
|
|
*
|
|
* Free a GstAudioInfo structure previously allocated with gst_audio_info_new()
|
|
* or gst_audio_info_copy().
|
|
*/
|
|
void
|
|
gst_audio_info_free (GstAudioInfo * info)
|
|
{
|
|
g_free (info);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstAudioInfo, gst_audio_info,
|
|
(GBoxedCopyFunc) gst_audio_info_copy, (GBoxedFreeFunc) gst_audio_info_free);
|
|
|
|
/**
|
|
* gst_audio_info_new:
|
|
*
|
|
* Allocate a new #GstAudioInfo that is also initialized with
|
|
* gst_audio_info_init().
|
|
*
|
|
* Returns: a new #GstAudioInfo. free with gst_audio_info_free().
|
|
*/
|
|
GstAudioInfo *
|
|
gst_audio_info_new (void)
|
|
{
|
|
GstAudioInfo *info;
|
|
|
|
info = g_new (GstAudioInfo, 1);
|
|
gst_audio_info_init (info);
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_init:
|
|
* @info: (out caller-allocates): a #GstAudioInfo
|
|
*
|
|
* Initialize @info with default values.
|
|
*/
|
|
void
|
|
gst_audio_info_init (GstAudioInfo * info)
|
|
{
|
|
g_return_if_fail (info != NULL);
|
|
|
|
memset (info, 0, sizeof (GstAudioInfo));
|
|
|
|
info->finfo = gst_audio_format_get_info (GST_AUDIO_FORMAT_UNKNOWN);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_set_format:
|
|
* @info: a #GstAudioInfo
|
|
* @format: the format
|
|
* @rate: the samplerate
|
|
* @channels: the number of channels
|
|
* @position: (array fixed-size=64) (nullable): the channel positions
|
|
*
|
|
* Set the default info for the audio info of @format and @rate and @channels.
|
|
*
|
|
* Note: This initializes @info first, no values are preserved.
|
|
*/
|
|
void
|
|
gst_audio_info_set_format (GstAudioInfo * info, GstAudioFormat format,
|
|
gint rate, gint channels, const GstAudioChannelPosition * position)
|
|
{
|
|
const GstAudioFormatInfo *finfo;
|
|
gint i;
|
|
|
|
g_return_if_fail (info != NULL);
|
|
g_return_if_fail (format != GST_AUDIO_FORMAT_UNKNOWN);
|
|
g_return_if_fail (channels <= 64 || position == NULL);
|
|
|
|
gst_audio_info_init (info);
|
|
|
|
finfo = gst_audio_format_get_info (format);
|
|
|
|
info->flags = 0;
|
|
info->layout = GST_AUDIO_LAYOUT_INTERLEAVED;
|
|
info->finfo = finfo;
|
|
info->rate = rate;
|
|
info->channels = channels;
|
|
info->bpf = (finfo->width * channels) / 8;
|
|
|
|
memset (&info->position, 0xff, sizeof (info->position));
|
|
|
|
if (!position && channels == 1) {
|
|
info->position[0] = GST_AUDIO_CHANNEL_POSITION_MONO;
|
|
return;
|
|
} else if (!position && channels == 2) {
|
|
info->position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
|
|
info->position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
|
|
return;
|
|
} else {
|
|
if (!position
|
|
|| !gst_audio_check_valid_channel_positions (position, channels,
|
|
TRUE)) {
|
|
if (position)
|
|
g_warning ("Invalid channel positions");
|
|
} else {
|
|
memcpy (&info->position, position,
|
|
info->channels * sizeof (info->position[0]));
|
|
if (info->position[0] == GST_AUDIO_CHANNEL_POSITION_NONE)
|
|
info->flags |= GST_AUDIO_FLAG_UNPOSITIONED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Otherwise a NONE layout */
|
|
info->flags |= GST_AUDIO_FLAG_UNPOSITIONED;
|
|
for (i = 0; i < MIN (64, channels); i++)
|
|
info->position[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_from_caps:
|
|
* @info: (out caller-allocates): a #GstAudioInfo
|
|
* @caps: a #GstCaps
|
|
*
|
|
* Parse @caps and update @info.
|
|
*
|
|
* Returns: TRUE if @caps could be parsed
|
|
*/
|
|
gboolean
|
|
gst_audio_info_from_caps (GstAudioInfo * info, const GstCaps * caps)
|
|
{
|
|
GstStructure *str;
|
|
const gchar *s;
|
|
GstAudioFormat format = GST_AUDIO_FORMAT_UNKNOWN;
|
|
gint rate = 0;
|
|
gint channels = 0;
|
|
guint64 channel_mask = 0;
|
|
gint i;
|
|
GstAudioChannelPosition position[64];
|
|
GstAudioFlags flags;
|
|
GstAudioLayout layout = GST_AUDIO_LAYOUT_INTERLEAVED;
|
|
|
|
g_return_val_if_fail (info != NULL, FALSE);
|
|
g_return_val_if_fail (caps != NULL, FALSE);
|
|
g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
|
|
|
|
GST_DEBUG ("parsing caps %" GST_PTR_FORMAT, caps);
|
|
|
|
flags = 0;
|
|
|
|
str = gst_caps_get_structure (caps, 0);
|
|
|
|
if (gst_structure_has_name (str, "audio/x-raw")) {
|
|
if (!(s = gst_structure_get_string (str, "format")))
|
|
goto no_format;
|
|
|
|
format = gst_audio_format_from_string (s);
|
|
if (format == GST_AUDIO_FORMAT_UNKNOWN)
|
|
goto unknown_format;
|
|
} else if (g_str_has_prefix (gst_structure_get_name (str), "audio/")) {
|
|
format = GST_AUDIO_FORMAT_ENCODED;
|
|
} else {
|
|
goto wrong_name;
|
|
}
|
|
|
|
if (format != GST_AUDIO_FORMAT_ENCODED) {
|
|
if (!(s = gst_structure_get_string (str, "layout")))
|
|
goto no_layout;
|
|
if (g_str_equal (s, "interleaved"))
|
|
layout = GST_AUDIO_LAYOUT_INTERLEAVED;
|
|
else if (g_str_equal (s, "non-interleaved"))
|
|
layout = GST_AUDIO_LAYOUT_NON_INTERLEAVED;
|
|
else
|
|
goto unknown_layout;
|
|
}
|
|
|
|
if (!gst_structure_get_int (str, "rate", &rate) &&
|
|
format != GST_AUDIO_FORMAT_ENCODED)
|
|
goto no_rate;
|
|
if (!gst_structure_get_int (str, "channels", &channels) &&
|
|
format != GST_AUDIO_FORMAT_ENCODED)
|
|
goto no_channels;
|
|
|
|
if (!gst_structure_get (str, "channel-mask", GST_TYPE_BITMASK, &channel_mask,
|
|
NULL) || (channel_mask == 0 && channels == 1)) {
|
|
if (channels == 1) {
|
|
position[0] = GST_AUDIO_CHANNEL_POSITION_MONO;
|
|
} else if (channels == 2) {
|
|
position[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
|
|
position[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
|
|
} else if (format != GST_AUDIO_FORMAT_ENCODED) {
|
|
goto no_channel_mask;
|
|
}
|
|
} else if (channel_mask == 0) {
|
|
flags |= GST_AUDIO_FLAG_UNPOSITIONED;
|
|
for (i = 0; i < MIN (64, channels); i++)
|
|
position[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
|
} else {
|
|
if (!gst_audio_channel_positions_from_mask (channels, channel_mask,
|
|
position))
|
|
goto invalid_channel_mask;
|
|
}
|
|
|
|
gst_audio_info_set_format (info, format, rate, channels,
|
|
(channels > 64) ? NULL : position);
|
|
|
|
info->flags = flags;
|
|
info->layout = layout;
|
|
|
|
return TRUE;
|
|
|
|
/* ERROR */
|
|
wrong_name:
|
|
{
|
|
GST_ERROR ("wrong name, expected audio/x-raw");
|
|
return FALSE;
|
|
}
|
|
no_format:
|
|
{
|
|
GST_ERROR ("no format given");
|
|
return FALSE;
|
|
}
|
|
unknown_format:
|
|
{
|
|
GST_ERROR ("unknown format given");
|
|
return FALSE;
|
|
}
|
|
no_layout:
|
|
{
|
|
GST_ERROR ("no layout given");
|
|
return FALSE;
|
|
}
|
|
unknown_layout:
|
|
{
|
|
GST_ERROR ("unknown layout given");
|
|
return FALSE;
|
|
}
|
|
no_rate:
|
|
{
|
|
GST_ERROR ("no rate property given");
|
|
return FALSE;
|
|
}
|
|
no_channels:
|
|
{
|
|
GST_ERROR ("no channels property given");
|
|
return FALSE;
|
|
}
|
|
no_channel_mask:
|
|
{
|
|
GST_ERROR ("no channel-mask property given");
|
|
return FALSE;
|
|
}
|
|
invalid_channel_mask:
|
|
{
|
|
GST_ERROR ("Invalid channel mask 0x%016" G_GINT64_MODIFIER
|
|
"x for %d channels", channel_mask, channels);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_new_from_caps:
|
|
* @caps: a #GstCaps
|
|
*
|
|
* Parse @caps to generate a #GstAudioInfo.
|
|
*
|
|
* Returns: (nullable): A #GstAudioInfo, or %NULL if @caps couldn't be parsed
|
|
* Since: 1.20
|
|
*/
|
|
GstAudioInfo *
|
|
gst_audio_info_new_from_caps (const GstCaps * caps)
|
|
{
|
|
GstAudioInfo *ret = gst_audio_info_new ();
|
|
|
|
if (gst_audio_info_from_caps (ret, caps)) {
|
|
return ret;
|
|
} else {
|
|
gst_audio_info_free (ret);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_to_caps:
|
|
* @info: a #GstAudioInfo
|
|
*
|
|
* Convert the values of @info into a #GstCaps.
|
|
*
|
|
* Returns: (transfer full): the new #GstCaps containing the
|
|
* info of @info.
|
|
*/
|
|
GstCaps *
|
|
gst_audio_info_to_caps (const GstAudioInfo * info)
|
|
{
|
|
GstCaps *caps;
|
|
const gchar *format;
|
|
const gchar *layout;
|
|
GstAudioFlags flags;
|
|
|
|
g_return_val_if_fail (info != NULL, NULL);
|
|
g_return_val_if_fail (info->finfo != NULL, NULL);
|
|
g_return_val_if_fail (info->finfo->format != GST_AUDIO_FORMAT_UNKNOWN, NULL);
|
|
|
|
format = gst_audio_format_to_string (info->finfo->format);
|
|
g_return_val_if_fail (format != NULL, NULL);
|
|
|
|
if (info->layout == GST_AUDIO_LAYOUT_INTERLEAVED)
|
|
layout = "interleaved";
|
|
else if (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED)
|
|
layout = "non-interleaved";
|
|
else
|
|
g_return_val_if_reached (NULL);
|
|
|
|
flags = info->flags;
|
|
if ((flags & GST_AUDIO_FLAG_UNPOSITIONED) && info->channels > 1
|
|
&& info->position[0] != GST_AUDIO_CHANNEL_POSITION_NONE) {
|
|
flags &= ~GST_AUDIO_FLAG_UNPOSITIONED;
|
|
g_warning ("Unpositioned audio channel position flag set but "
|
|
"channel positions present");
|
|
} else if (!(flags & GST_AUDIO_FLAG_UNPOSITIONED) && info->channels > 1
|
|
&& info->position[0] == GST_AUDIO_CHANNEL_POSITION_NONE) {
|
|
flags |= GST_AUDIO_FLAG_UNPOSITIONED;
|
|
g_warning ("Unpositioned audio channel position flag not set "
|
|
"but no channel positions present");
|
|
}
|
|
|
|
caps = gst_caps_new_simple ("audio/x-raw",
|
|
"format", G_TYPE_STRING, format,
|
|
"layout", G_TYPE_STRING, layout,
|
|
"rate", G_TYPE_INT, info->rate,
|
|
"channels", G_TYPE_INT, info->channels, NULL);
|
|
|
|
if (info->channels > 1
|
|
|| info->position[0] != GST_AUDIO_CHANNEL_POSITION_MONO) {
|
|
guint64 channel_mask = 0;
|
|
|
|
if ((flags & GST_AUDIO_FLAG_UNPOSITIONED)) {
|
|
channel_mask = 0;
|
|
} else {
|
|
if (!gst_audio_channel_positions_to_mask (info->position, info->channels,
|
|
TRUE, &channel_mask))
|
|
goto invalid_channel_positions;
|
|
}
|
|
|
|
if (info->channels == 1
|
|
&& info->position[0] == GST_AUDIO_CHANNEL_POSITION_MONO) {
|
|
/* Default mono special case */
|
|
} else {
|
|
gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
return caps;
|
|
|
|
invalid_channel_positions:
|
|
{
|
|
GST_ERROR ("Invalid channel positions");
|
|
gst_caps_unref (caps);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_convert:
|
|
* @info: a #GstAudioInfo
|
|
* @src_fmt: #GstFormat of the @src_val
|
|
* @src_val: value to convert
|
|
* @dest_fmt: #GstFormat of the @dest_val
|
|
* @dest_val: (out): pointer to destination value
|
|
*
|
|
* Converts among various #GstFormat types. This function handles
|
|
* GST_FORMAT_BYTES, GST_FORMAT_TIME, and GST_FORMAT_DEFAULT. For
|
|
* raw audio, GST_FORMAT_DEFAULT corresponds to audio frames. This
|
|
* function can be used to handle pad queries of the type GST_QUERY_CONVERT.
|
|
*
|
|
* Returns: TRUE if the conversion was successful.
|
|
*/
|
|
gboolean
|
|
gst_audio_info_convert (const GstAudioInfo * info,
|
|
GstFormat src_fmt, gint64 src_val, GstFormat dest_fmt, gint64 * dest_val)
|
|
{
|
|
gboolean res = TRUE;
|
|
gint bpf, rate;
|
|
|
|
GST_DEBUG ("converting value %" G_GINT64_FORMAT " from %s (%d) to %s (%d)",
|
|
src_val, gst_format_get_name (src_fmt), src_fmt,
|
|
gst_format_get_name (dest_fmt), dest_fmt);
|
|
|
|
if (src_fmt == dest_fmt || src_val == -1) {
|
|
*dest_val = src_val;
|
|
goto done;
|
|
}
|
|
|
|
/* get important info */
|
|
bpf = GST_AUDIO_INFO_BPF (info);
|
|
rate = GST_AUDIO_INFO_RATE (info);
|
|
|
|
if (bpf == 0 || rate == 0) {
|
|
GST_DEBUG ("no rate or bpf configured");
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
switch (src_fmt) {
|
|
case GST_FORMAT_BYTES:
|
|
switch (dest_fmt) {
|
|
case GST_FORMAT_TIME:
|
|
*dest_val = GST_FRAMES_TO_CLOCK_TIME (src_val / bpf, rate);
|
|
break;
|
|
case GST_FORMAT_DEFAULT:
|
|
*dest_val = src_val / bpf;
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
case GST_FORMAT_DEFAULT:
|
|
switch (dest_fmt) {
|
|
case GST_FORMAT_TIME:
|
|
*dest_val = GST_FRAMES_TO_CLOCK_TIME (src_val, rate);
|
|
break;
|
|
case GST_FORMAT_BYTES:
|
|
*dest_val = src_val * bpf;
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
case GST_FORMAT_TIME:
|
|
switch (dest_fmt) {
|
|
case GST_FORMAT_DEFAULT:
|
|
*dest_val = GST_CLOCK_TIME_TO_FRAMES (src_val, rate);
|
|
break;
|
|
case GST_FORMAT_BYTES:
|
|
*dest_val = GST_CLOCK_TIME_TO_FRAMES (src_val, rate);
|
|
*dest_val *= bpf;
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
done:
|
|
|
|
GST_DEBUG ("ret=%d result %" G_GINT64_FORMAT, res, res ? *dest_val : -1);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_info_is_equal:
|
|
* @info: a #GstAudioInfo
|
|
* @other: a #GstAudioInfo
|
|
*
|
|
* Compares two #GstAudioInfo and returns whether they are equal or not
|
|
*
|
|
* Returns: %TRUE if @info and @other are equal, else %FALSE.
|
|
*
|
|
* Since: 1.2
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_audio_info_is_equal (const GstAudioInfo * info, const GstAudioInfo * other)
|
|
{
|
|
if (info == other)
|
|
return TRUE;
|
|
if (info->finfo == NULL || other->finfo == NULL)
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_FORMAT (info) != GST_AUDIO_INFO_FORMAT (other))
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_FLAGS (info) != GST_AUDIO_INFO_FLAGS (other))
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_LAYOUT (info) != GST_AUDIO_INFO_LAYOUT (other))
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_RATE (info) != GST_AUDIO_INFO_RATE (other))
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_CHANNELS (info) != GST_AUDIO_INFO_CHANNELS (other))
|
|
return FALSE;
|
|
if (GST_AUDIO_INFO_CHANNELS (info) > 64)
|
|
return TRUE;
|
|
if (memcmp (info->position, other->position,
|
|
GST_AUDIO_INFO_CHANNELS (info) * sizeof (GstAudioChannelPosition)) !=
|
|
0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|