1 /* GStreamer
2  * Copyright (C) <2011> Wim Taymans <wim.taymans@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:gstaudiometa
22  * @title: GstAudio meta
23  * @short_description: Buffer metadata for audio downmix matrix handling
24  *
25  * #GstAudioDownmixMeta defines an audio downmix matrix to be send along with
26  * audio buffers. These functions in this module help to create and attach the
27  * meta as well as extracting it.
28  */
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #include <string.h>
34 
35 #include "gstaudiometa.h"
36 
37 static gboolean
gst_audio_downmix_meta_init(GstMeta * meta,gpointer params,GstBuffer * buffer)38 gst_audio_downmix_meta_init (GstMeta * meta, gpointer params,
39     GstBuffer * buffer)
40 {
41   GstAudioDownmixMeta *dmeta = (GstAudioDownmixMeta *) meta;
42 
43   dmeta->from_position = dmeta->to_position = NULL;
44   dmeta->from_channels = dmeta->to_channels = 0;
45   dmeta->matrix = NULL;
46 
47   return TRUE;
48 }
49 
50 static void
gst_audio_downmix_meta_free(GstMeta * meta,GstBuffer * buffer)51 gst_audio_downmix_meta_free (GstMeta * meta, GstBuffer * buffer)
52 {
53   GstAudioDownmixMeta *dmeta = (GstAudioDownmixMeta *) meta;
54 
55   g_free (dmeta->from_position);
56   if (dmeta->matrix) {
57     g_free (*dmeta->matrix);
58     g_free (dmeta->matrix);
59   }
60 }
61 
62 static gboolean
gst_audio_downmix_meta_transform(GstBuffer * dest,GstMeta * meta,GstBuffer * buffer,GQuark type,gpointer data)63 gst_audio_downmix_meta_transform (GstBuffer * dest, GstMeta * meta,
64     GstBuffer * buffer, GQuark type, gpointer data)
65 {
66   GstAudioDownmixMeta *smeta, *dmeta;
67 
68   smeta = (GstAudioDownmixMeta *) meta;
69 
70   if (GST_META_TRANSFORM_IS_COPY (type)) {
71     dmeta = gst_buffer_add_audio_downmix_meta (dest, smeta->from_position,
72         smeta->from_channels, smeta->to_position, smeta->to_channels,
73         (const gfloat **) smeta->matrix);
74     if (!dmeta)
75       return FALSE;
76   } else {
77     /* return FALSE, if transform type is not supported */
78     return FALSE;
79   }
80 
81   return TRUE;
82 }
83 
84 /**
85  * gst_buffer_get_audio_downmix_meta_for_channels:
86  * @buffer: a #GstBuffer
87  * @to_position: (array length=to_channels): the channel positions of
88  *   the destination
89  * @to_channels: The number of channels of the destination
90  *
91  * Find the #GstAudioDownmixMeta on @buffer for the given destination
92  * channel positions.
93  *
94  * Returns: (transfer none): the #GstAudioDownmixMeta on @buffer.
95  */
96 GstAudioDownmixMeta *
gst_buffer_get_audio_downmix_meta_for_channels(GstBuffer * buffer,const GstAudioChannelPosition * to_position,gint to_channels)97 gst_buffer_get_audio_downmix_meta_for_channels (GstBuffer * buffer,
98     const GstAudioChannelPosition * to_position, gint to_channels)
99 {
100   gpointer state = NULL;
101   GstMeta *meta;
102   const GstMetaInfo *info = GST_AUDIO_DOWNMIX_META_INFO;
103 
104   while ((meta = gst_buffer_iterate_meta (buffer, &state))) {
105     if (meta->info->api == info->api) {
106       GstAudioDownmixMeta *ameta = (GstAudioDownmixMeta *) meta;
107       if (ameta->to_channels == to_channels &&
108           memcmp (ameta->to_position, to_position,
109               sizeof (GstAudioChannelPosition) * to_channels) == 0)
110         return ameta;
111     }
112   }
113   return NULL;
114 }
115 
116 /**
117  * gst_buffer_add_audio_downmix_meta:
118  * @buffer: a #GstBuffer
119  * @from_position: (array length=from_channels): the channel positions
120  *   of the source
121  * @from_channels: The number of channels of the source
122  * @to_position: (array length=to_channels): the channel positions of
123  *   the destination
124  * @to_channels: The number of channels of the destination
125  * @matrix: The matrix coefficients.
126  *
127  * Attaches #GstAudioDownmixMeta metadata to @buffer with the given parameters.
128  *
129  * @matrix is an two-dimensional array of @to_channels times @from_channels
130  * coefficients, i.e. the i-th output channels is constructed by multiplicating
131  * the input channels with the coefficients in @matrix[i] and taking the sum
132  * of the results.
133  *
134  * Returns: (transfer none): the #GstAudioDownmixMeta on @buffer.
135  */
136 GstAudioDownmixMeta *
gst_buffer_add_audio_downmix_meta(GstBuffer * buffer,const GstAudioChannelPosition * from_position,gint from_channels,const GstAudioChannelPosition * to_position,gint to_channels,const gfloat ** matrix)137 gst_buffer_add_audio_downmix_meta (GstBuffer * buffer,
138     const GstAudioChannelPosition * from_position, gint from_channels,
139     const GstAudioChannelPosition * to_position, gint to_channels,
140     const gfloat ** matrix)
141 {
142   GstAudioDownmixMeta *meta;
143   gint i;
144 
145   g_return_val_if_fail (from_position != NULL, NULL);
146   g_return_val_if_fail (from_channels > 0, NULL);
147   g_return_val_if_fail (to_position != NULL, NULL);
148   g_return_val_if_fail (to_channels > 0, NULL);
149   g_return_val_if_fail (matrix != NULL, NULL);
150 
151   meta =
152       (GstAudioDownmixMeta *) gst_buffer_add_meta (buffer,
153       GST_AUDIO_DOWNMIX_META_INFO, NULL);
154 
155   meta->from_channels = from_channels;
156   meta->to_channels = to_channels;
157 
158   meta->from_position =
159       g_new (GstAudioChannelPosition, meta->from_channels + meta->to_channels);
160   meta->to_position = meta->from_position + meta->from_channels;
161   memcpy (meta->from_position, from_position,
162       sizeof (GstAudioChannelPosition) * meta->from_channels);
163   memcpy (meta->to_position, to_position,
164       sizeof (GstAudioChannelPosition) * meta->to_channels);
165 
166   meta->matrix = g_new (gfloat *, meta->to_channels);
167   meta->matrix[0] = g_new (gfloat, meta->from_channels * meta->to_channels);
168   memcpy (meta->matrix[0], matrix[0], sizeof (gfloat) * meta->from_channels);
169   for (i = 1; i < meta->to_channels; i++) {
170     meta->matrix[i] = meta->matrix[0] + i * meta->from_channels;
171     memcpy (meta->matrix[i], matrix[i], sizeof (gfloat) * meta->from_channels);
172   }
173 
174   return meta;
175 }
176 
177 GType
gst_audio_downmix_meta_api_get_type(void)178 gst_audio_downmix_meta_api_get_type (void)
179 {
180   static volatile GType type;
181   static const gchar *tags[] =
182       { GST_META_TAG_AUDIO_STR, GST_META_TAG_AUDIO_CHANNELS_STR, NULL };
183 
184   if (g_once_init_enter (&type)) {
185     GType _type = gst_meta_api_type_register ("GstAudioDownmixMetaAPI", tags);
186     g_once_init_leave (&type, _type);
187   }
188   return type;
189 }
190 
191 const GstMetaInfo *
gst_audio_downmix_meta_get_info(void)192 gst_audio_downmix_meta_get_info (void)
193 {
194   static const GstMetaInfo *audio_downmix_meta_info = NULL;
195 
196   if (g_once_init_enter ((GstMetaInfo **) & audio_downmix_meta_info)) {
197     const GstMetaInfo *meta =
198         gst_meta_register (GST_AUDIO_DOWNMIX_META_API_TYPE,
199         "GstAudioDownmixMeta", sizeof (GstAudioDownmixMeta),
200         gst_audio_downmix_meta_init, gst_audio_downmix_meta_free,
201         gst_audio_downmix_meta_transform);
202     g_once_init_leave ((GstMetaInfo **) & audio_downmix_meta_info,
203         (GstMetaInfo *) meta);
204   }
205   return audio_downmix_meta_info;
206 }
207 
208 static gboolean
gst_audio_clipping_meta_init(GstMeta * meta,gpointer params,GstBuffer * buffer)209 gst_audio_clipping_meta_init (GstMeta * meta, gpointer params,
210     GstBuffer * buffer)
211 {
212   GstAudioClippingMeta *cmeta = (GstAudioClippingMeta *) meta;
213 
214   cmeta->format = GST_FORMAT_UNDEFINED;
215   cmeta->start = cmeta->end = 0;
216 
217   return TRUE;
218 }
219 
220 static gboolean
gst_audio_clipping_meta_transform(GstBuffer * dest,GstMeta * meta,GstBuffer * buffer,GQuark type,gpointer data)221 gst_audio_clipping_meta_transform (GstBuffer * dest, GstMeta * meta,
222     GstBuffer * buffer, GQuark type, gpointer data)
223 {
224   GstAudioClippingMeta *smeta, *dmeta;
225 
226   smeta = (GstAudioClippingMeta *) meta;
227 
228   if (GST_META_TRANSFORM_IS_COPY (type)) {
229     GstMetaTransformCopy *copy = data;
230 
231     if (copy->region)
232       return FALSE;
233 
234     dmeta =
235         gst_buffer_add_audio_clipping_meta (dest, smeta->format, smeta->start,
236         smeta->end);
237     if (!dmeta)
238       return FALSE;
239   } else {
240     /* TODO: Could implement an automatic transform for resampling */
241     /* return FALSE, if transform type is not supported */
242     return FALSE;
243   }
244 
245   return TRUE;
246 }
247 
248 /**
249  * gst_buffer_add_audio_clipping_meta:
250  * @buffer: a #GstBuffer
251  * @format: GstFormat of @start and @stop, GST_FORMAT_DEFAULT is samples
252  * @start: Amount of audio to clip from start of buffer
253  * @end: Amount of  to clip from end of buffer
254  *
255  * Attaches #GstAudioClippingMeta metadata to @buffer with the given parameters.
256  *
257  * Returns: (transfer none): the #GstAudioClippingMeta on @buffer.
258  *
259  * Since: 1.8
260  */
261 GstAudioClippingMeta *
gst_buffer_add_audio_clipping_meta(GstBuffer * buffer,GstFormat format,guint64 start,guint64 end)262 gst_buffer_add_audio_clipping_meta (GstBuffer * buffer,
263     GstFormat format, guint64 start, guint64 end)
264 {
265   GstAudioClippingMeta *meta;
266 
267   g_return_val_if_fail (format != GST_FORMAT_UNDEFINED, NULL);
268 
269   meta =
270       (GstAudioClippingMeta *) gst_buffer_add_meta (buffer,
271       GST_AUDIO_CLIPPING_META_INFO, NULL);
272 
273   meta->format = format;
274   meta->start = start;
275   meta->end = end;
276 
277   return meta;
278 }
279 
280 GType
gst_audio_clipping_meta_api_get_type(void)281 gst_audio_clipping_meta_api_get_type (void)
282 {
283   static volatile GType type;
284   static const gchar *tags[] =
285       { GST_META_TAG_AUDIO_STR, GST_META_TAG_AUDIO_RATE_STR, NULL };
286 
287   if (g_once_init_enter (&type)) {
288     GType _type = gst_meta_api_type_register ("GstAudioClippingMetaAPI", tags);
289     g_once_init_leave (&type, _type);
290   }
291   return type;
292 }
293 
294 const GstMetaInfo *
gst_audio_clipping_meta_get_info(void)295 gst_audio_clipping_meta_get_info (void)
296 {
297   static const GstMetaInfo *audio_clipping_meta_info = NULL;
298 
299   if (g_once_init_enter ((GstMetaInfo **) & audio_clipping_meta_info)) {
300     const GstMetaInfo *meta =
301         gst_meta_register (GST_AUDIO_CLIPPING_META_API_TYPE,
302         "GstAudioClippingMeta", sizeof (GstAudioClippingMeta),
303         gst_audio_clipping_meta_init, NULL,
304         gst_audio_clipping_meta_transform);
305     g_once_init_leave ((GstMetaInfo **) & audio_clipping_meta_info,
306         (GstMetaInfo *) meta);
307   }
308   return audio_clipping_meta_info;
309 }
310 
311 
312 static gboolean
gst_audio_meta_init(GstMeta * meta,gpointer params,GstBuffer * buffer)313 gst_audio_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer)
314 {
315   GstAudioMeta *ameta = (GstAudioMeta *) meta;
316 
317   gst_audio_info_init (&ameta->info);
318   ameta->samples = 0;
319   ameta->offsets = NULL;
320 
321   return TRUE;
322 }
323 
324 static void
gst_audio_meta_free(GstMeta * meta,GstBuffer * buffer)325 gst_audio_meta_free (GstMeta * meta, GstBuffer * buffer)
326 {
327   GstAudioMeta *ameta = (GstAudioMeta *) meta;
328 
329   if (ameta->offsets && ameta->offsets != ameta->priv_offsets_arr)
330     g_slice_free1 (ameta->info.channels * sizeof (gsize), ameta->offsets);
331 }
332 
333 static gboolean
gst_audio_meta_transform(GstBuffer * dest,GstMeta * meta,GstBuffer * buffer,GQuark type,gpointer data)334 gst_audio_meta_transform (GstBuffer * dest, GstMeta * meta,
335     GstBuffer * buffer, GQuark type, gpointer data)
336 {
337   GstAudioMeta *smeta, *dmeta;
338 
339   smeta = (GstAudioMeta *) meta;
340 
341   if (GST_META_TRANSFORM_IS_COPY (type)) {
342     dmeta = gst_buffer_add_audio_meta (dest, &smeta->info, smeta->samples,
343         smeta->offsets);
344     if (!dmeta)
345       return FALSE;
346   } else {
347     /* return FALSE, if transform type is not supported */
348     return FALSE;
349   }
350 
351   return TRUE;
352 }
353 
354 /**
355  * gst_buffer_add_audio_meta:
356  * @buffer: a #GstBuffer
357  * @info: the audio properties of the buffer
358  * @samples: the number of valid samples in the buffer
359  * @offsets: (nullable): the offsets (in bytes) where each channel plane starts
360  *   in the buffer or %NULL to calculate it (see below); must be %NULL also
361  *   when @info->layout is %GST_AUDIO_LAYOUT_INTERLEAVED
362  *
363  * Allocates and attaches a #GstAudioMeta on @buffer, which must be writable
364  * for that purpose. The fields of the #GstAudioMeta are directly populated
365  * from the arguments of this function.
366  *
367  * When @info->layout is %GST_AUDIO_LAYOUT_NON_INTERLEAVED and @offsets is
368  * %NULL, the offsets are calculated with a formula that assumes the planes are
369  * tightly packed and in sequence:
370  * offsets[channel] = channel * @samples * sample_stride
371  *
372  * It is not allowed for channels to overlap in memory,
373  * i.e. for each i in [0, channels), the range
374  * [@offsets[i], @offsets[i] + @samples * sample_stride) must not overlap
375  * with any other such range. This function will assert if the parameters
376  * specified cause this restriction to be violated.
377  *
378  * It is, obviously, also not allowed to specify parameters that would cause
379  * out-of-bounds memory access on @buffer. This is also checked, which means
380  * that you must add enough memory on the @buffer before adding this meta.
381  *
382  * Returns: (transfer none): the #GstAudioMeta that was attached on the @buffer
383  *
384  * Since: 1.16
385  */
386 GstAudioMeta *
gst_buffer_add_audio_meta(GstBuffer * buffer,const GstAudioInfo * info,gsize samples,gsize offsets[])387 gst_buffer_add_audio_meta (GstBuffer * buffer, const GstAudioInfo * info,
388     gsize samples, gsize offsets[])
389 {
390   GstAudioMeta *meta;
391   gint i;
392   gsize plane_size;
393 
394   g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
395   g_return_val_if_fail (info != NULL, NULL);
396   g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (info), NULL);
397   g_return_val_if_fail (GST_AUDIO_INFO_FORMAT (info) !=
398       GST_AUDIO_FORMAT_UNKNOWN, NULL);
399   g_return_val_if_fail (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED
400       || !offsets, NULL);
401 
402   meta =
403       (GstAudioMeta *) gst_buffer_add_meta (buffer, GST_AUDIO_META_INFO, NULL);
404 
405   meta->info = *info;
406   meta->samples = samples;
407   plane_size = samples * info->finfo->width / 8;
408 
409   if (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED) {
410 #ifndef G_DISABLE_CHECKS
411     gsize max_offset = 0;
412     gint j;
413 #endif
414 
415     if (G_UNLIKELY (info->channels > 8))
416       meta->offsets = g_slice_alloc (info->channels * sizeof (gsize));
417     else
418       meta->offsets = meta->priv_offsets_arr;
419 
420     if (offsets) {
421       for (i = 0; i < info->channels; i++) {
422         meta->offsets[i] = offsets[i];
423 #ifndef G_DISABLE_CHECKS
424         max_offset = MAX (max_offset, offsets[i]);
425         for (j = 0; j < info->channels; j++) {
426           if (i != j && !(offsets[j] + plane_size <= offsets[i]
427                   || offsets[i] + plane_size <= offsets[j])) {
428             g_critical ("GstAudioMeta properties would cause channel memory "
429                 "areas to overlap! offsets: %" G_GSIZE_FORMAT " (%d), %"
430                 G_GSIZE_FORMAT " (%d) with plane size %" G_GSIZE_FORMAT,
431                 offsets[i], i, offsets[j], j, plane_size);
432             gst_buffer_remove_meta (buffer, (GstMeta *) meta);
433             return NULL;
434           }
435         }
436 #endif
437       }
438     } else {
439       /* default offsets assume channels are laid out sequentially in memory */
440       for (i = 0; i < info->channels; i++)
441         meta->offsets[i] = i * plane_size;
442 #ifndef G_DISABLE_CHECKS
443       max_offset = meta->offsets[info->channels - 1];
444 #endif
445     }
446 
447 #ifndef G_DISABLE_CHECKS
448     if (max_offset + plane_size > gst_buffer_get_size (buffer)) {
449       g_critical ("GstAudioMeta properties would cause "
450           "out-of-bounds memory access on the buffer: max_offset %"
451           G_GSIZE_FORMAT ", samples %" G_GSIZE_FORMAT ", bps %u, buffer size %"
452           G_GSIZE_FORMAT, max_offset, samples, info->finfo->width / 8,
453           gst_buffer_get_size (buffer));
454       gst_buffer_remove_meta (buffer, (GstMeta *) meta);
455       return NULL;
456     }
457 #endif
458   }
459 
460   return meta;
461 }
462 
463 GType
gst_audio_meta_api_get_type(void)464 gst_audio_meta_api_get_type (void)
465 {
466   static volatile GType type;
467   static const gchar *tags[] = {
468     GST_META_TAG_AUDIO_STR, GST_META_TAG_AUDIO_CHANNELS_STR,
469     GST_META_TAG_AUDIO_RATE_STR, NULL
470   };
471 
472   if (g_once_init_enter (&type)) {
473     GType _type = gst_meta_api_type_register ("GstAudioMetaAPI", tags);
474     g_once_init_leave (&type, _type);
475   }
476   return type;
477 }
478 
479 const GstMetaInfo *
gst_audio_meta_get_info(void)480 gst_audio_meta_get_info (void)
481 {
482   static const GstMetaInfo *audio_meta_info = NULL;
483 
484   if (g_once_init_enter ((GstMetaInfo **) & audio_meta_info)) {
485     const GstMetaInfo *meta = gst_meta_register (GST_AUDIO_META_API_TYPE,
486         "GstAudioMeta", sizeof (GstAudioMeta),
487         gst_audio_meta_init,
488         gst_audio_meta_free,
489         gst_audio_meta_transform);
490     g_once_init_leave ((GstMetaInfo **) & audio_meta_info,
491         (GstMetaInfo *) meta);
492   }
493   return audio_meta_info;
494 }
495