xml: Relax AVC_MP4_MP_HD_1080i_AAC profile restrictions
[gupnp:gupnp-dlna.git] / libgupnp-dlna / gupnp-dlna-profiles.c
1 /*
2  * Copyright (C) 2010 Nokia Corporation.
3  *
4  * Authors: Arun Raghavan <arun.raghavan@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include <glib.h>
23 #include <gst/profile/gstprofile.h>
24 #include "gupnp-dlna-discoverer.h"
25 #include "gupnp-dlna-load.h"
26 #include "gupnp-dlna-profile.h"
27
28 /*
29  * This file provides the infrastructure to load DLNA profiles and the
30  * corresponding restrictions from an on-disk representation, and use them to
31  * map a given stream to its DLNA profile, if possible.
32  *
33  * Each DLNA profile is represented as a GstEncodingProfile (there might be
34  * exceptions where a single DLNA profile is represented by multiple
35  * GstEncodingProfiles - right now that's only LPCM).
36  *
37  * For a GstEncodingProfile "profile", the following fields will be populated:
38  *
39  *   profile.name = "<DLNA Profile Name>"
40  *   profile.format = Muxing format caps (with restrictions) if specified,
41  *                    else GST_CAPS_NONE
42  *   profile.encodingprofiles = GList of GstStreamEncodingProfiles
43  *
44  * For each stream of the given profile, "profile.encodingprofiles" will have
45  * a GstEncodingProfile representing the restrictions for that stream (for a
46  * video format there will be one audio and one video stream, for example).
47  *
48  *   enc_profile.type = GST_ENCODING_PROFILE_{AUDIO,VIDEO,...} (UNKNOWN for
49  *                      container restrictions)
50  *   enc_profile.format = GstCaps with all the restrictions for this format
51  *   enc_profile.restriction = GST_CAPS_ANY
52  *
53  * We assume that all DLNA profiles have exactly one audio stream, or one audio
54  * stream and one video stream.
55  *
56  * Things yet to account for:
57  *
58  *   1. Multiple audio/video streams (we need to pick the "main" one - how?
59  *      Possibly get information from the demuxer.)
60  *
61  *   2. How do we handle discovered metadata which is in tags, but not in caps?
62  *      Could potentially move it to caps in a post-discovery, pre-guessing
63  *      phase
64  */
65
66 /* New profile guessing API */
67
68 #define GUPNP_DLNA_DEBUG_ENV "GUPNP_DLNA_DEBUG"
69
70 #define gupnp_dlna_debug(args...)                               \
71 do {                                                            \
72         const gchar *_e = g_getenv (GUPNP_DLNA_DEBUG_ENV);      \
73         if (_e && !g_str_equal (_e, "0"))                       \
74                 g_debug (args);                                 \
75 } while (0)
76
77 static gboolean is_video_profile (const GstEncodingProfile *profile)
78 {
79         GList *i;
80
81         for (i = profile->encodingprofiles; i; i = i->next)
82                 if (((GstStreamEncodingProfile *) i->data)->type ==
83                                         GST_ENCODING_PROFILE_VIDEO)
84                         return TRUE;
85
86         return FALSE;
87 }
88
89 static gboolean structure_can_intersect (const GstStructure *st1,
90                                          const GstStructure *st2)
91 {
92         /* Since there is no API to intersect GstStructures, we cheat (thanks
93          * for the idea, tpm!) and make caps from the structuresa */
94
95         GstCaps *caps1, *caps2;
96         gboolean ret;
97
98         caps1 = gst_caps_new_full (gst_structure_copy (st1), NULL);
99         caps2 = gst_caps_new_full (gst_structure_copy (st2), NULL);
100
101         ret = gst_caps_can_intersect (caps1, caps2);
102
103         gst_caps_unref (caps1);
104         gst_caps_unref (caps2);
105         return ret;
106 }
107
108 static gboolean structure_is_subset (const GstStructure *st1,
109                                      const GstStructure *st2)
110 {
111         int i;
112
113         for (i = 0; i < gst_structure_n_fields (st2); i++) {
114                 const gchar *name = gst_structure_nth_field_name (st2, i);
115
116                 if (!gst_structure_has_field(st1, name)) {
117                         gupnp_dlna_debug ("    missing field %s", name);
118                         return FALSE;
119                 }
120         }
121
122         return TRUE;
123 }
124
125 /*
126  * Returns TRUE if stream_caps and profile_caps can intersect, and the
127  * intersecting structure from profile_caps is a subset of stream_caps. Put
128  * simply, the condition being met is that stream_caps intersects with
129  * profile_caps, and that intersection includes *all* fields specified by
130  * profile_caps (viz. all the fields specified by the DLNA profile's
131  * restrictions)
132  */
133 static gboolean caps_can_intersect_and_is_subset (GstCaps *stream_caps,
134                                                   GstCaps *profile_caps)
135 {
136         int i;
137         GstStructure *stream_st, *profile_st;
138
139         stream_st = gst_caps_get_structure (stream_caps, 0);
140
141         for (i = 0; i < gst_caps_get_size (profile_caps); i++) {
142                 profile_st = gst_caps_get_structure (profile_caps, i);
143
144                 if (structure_can_intersect (stream_st, profile_st) &&
145                     structure_is_subset (stream_st, profile_st))
146                         return TRUE;
147         }
148
149         return FALSE;
150 }
151
152 static gboolean
153 match_profile (const GstEncodingProfile *profile,
154                GstCaps                  *caps,
155                GstEncodingProfileType    type)
156 {
157         GList *i;
158
159         /* Profiles with an empty name are used only for inheritance and should
160          * not be matched against. */
161         if (profile->name[0] == '\0')
162           return FALSE;
163
164         for (i = profile->encodingprofiles; i; i = i->next) {
165                 GstStreamEncodingProfile *enc_profile = i->data;
166
167                 if (enc_profile->type == type &&
168                     caps_can_intersect_and_is_subset (caps,
169                                                       enc_profile->format))
170                         return TRUE;
171         }
172
173         return FALSE;
174 }
175
176 static gboolean
177 check_container (GstDiscovererInformation *info,
178                 const GstEncodingProfile  *profile)
179 {
180         /* Top-level GstStreamInformation in the topology will be
181          * the container */
182         if (info->stream_info->streamtype == GST_STREAM_CONTAINER &&
183             gst_caps_can_intersect (info->stream_info->caps, profile->format))
184                 return TRUE;
185         else if (info->stream_info->streamtype != GST_STREAM_CONTAINER &&
186                  gst_caps_is_empty (profile->format))
187                 return TRUE;
188
189         return FALSE;
190 }
191
192 static GstCaps *
193 caps_from_audio_stream_info (GstStreamAudioInformation *audio)
194 {
195         GstCaps *caps = gst_caps_copy ((GST_STREAM_INFORMATION (audio))->caps);
196
197         if (audio->sample_rate)
198                 gst_caps_set_simple (caps, "rate", G_TYPE_INT,
199                                 audio->sample_rate, NULL);
200         if (audio->channels)
201                 gst_caps_set_simple (caps, "channels", G_TYPE_INT,
202                                 audio->channels, NULL);
203         if (audio->bitrate)
204                 gst_caps_set_simple (caps, "bitrate", G_TYPE_INT,
205                                 audio->bitrate, NULL);
206         if (audio->max_bitrate)
207                 gst_caps_set_simple (caps, "maximum-bitrate", G_TYPE_INT,
208                                 audio->max_bitrate, NULL);
209         if (audio->depth)
210                 gst_caps_set_simple (caps, "depth", G_TYPE_INT,
211                                 audio->depth, NULL);
212
213         return caps;
214 }
215
216 static gboolean
217 check_audio_profile (const GstEncodingProfile *profile,
218                      GstDiscovererInformation *info)
219 {
220         GstCaps *caps;
221         GList *i;
222         gboolean found = FALSE;
223
224         /* Optimisation TODO: this can be pre-computed */
225         if (is_video_profile (profile))
226                 return FALSE;
227
228         for (i = info->stream_list; !found && i; i = i->next) {
229                 GstStreamInformation *stream = (GstStreamInformation *) i->data;
230                 GstStreamAudioInformation *audio;
231
232                 if (stream->streamtype != GST_STREAM_AUDIO)
233                         continue;
234
235                 audio = GST_STREAM_AUDIO_INFORMATION (stream);
236                 caps = caps_from_audio_stream_info (audio);
237
238                 if (match_profile (profile, caps, GST_ENCODING_PROFILE_AUDIO)) {
239                         found = TRUE;
240                         break;
241                 }
242
243                 gst_caps_unref (caps);
244         }
245
246         return found;
247 }
248
249 static void
250 guess_audio_profile (GstDiscovererInformation *info,
251                      gchar                   **name,
252                      gchar                   **mime,
253                      GList                    *profiles)
254 {
255         GList *i;
256         GUPnPDLNAProfile *profile;
257         const GstEncodingProfile *enc_profile;
258
259         for (i = profiles; i; i = i->next) {
260                 profile = (GUPnPDLNAProfile *)(i->data);
261                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
262
263                 gupnp_dlna_debug ("Checking DLNA profile %s",
264                                         gupnp_dlna_profile_get_name (profile));
265                 if (!check_audio_profile (enc_profile, info))
266                         gupnp_dlna_debug ("  Audio did not match");
267                 else if (!check_container (info, enc_profile))
268                         gupnp_dlna_debug ("  Container did not match");
269                 else {
270                         *name = g_strdup (
271                                         gupnp_dlna_profile_get_name (profile));
272                         *mime = g_strdup (
273                                         gupnp_dlna_profile_get_mime (profile));
274                         break;
275                 }
276         }
277 }
278
279 static GstCaps *
280 caps_from_video_stream_info (GstStreamVideoInformation *video)
281 {
282         GstStreamInformation *stream = GST_STREAM_INFORMATION (video);
283         GstCaps *caps = gst_caps_copy (stream->caps);
284         int n, d;
285
286         if (video->height)
287                 gst_caps_set_simple (caps, "height", G_TYPE_INT,
288                                 video->height, NULL);
289         if (video->width)
290                 gst_caps_set_simple (caps, "width", G_TYPE_INT,
291                                 video->width, NULL);
292         if (GST_VALUE_HOLDS_FRACTION (&video->frame_rate)) {
293                 n = gst_value_get_fraction_numerator (&video->frame_rate);
294                 d = gst_value_get_fraction_denominator (&video->frame_rate);
295                 gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
296                                 n, d, NULL);
297         }
298         if (GST_VALUE_HOLDS_FRACTION (&video->pixel_aspect_ratio)) {
299                 n = gst_value_get_fraction_numerator (
300                                         &video->pixel_aspect_ratio);
301                 d = gst_value_get_fraction_denominator (
302                                         &video->pixel_aspect_ratio);
303                 gst_caps_set_simple (caps, "pixel-aspect-ratio",
304                                 GST_TYPE_FRACTION, n, d, NULL);
305         }
306         if (video->interlaced)
307                 gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN,
308                                 video->interlaced, NULL);
309         if (video->format != GST_VIDEO_FORMAT_UNKNOWN)
310                 gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC,
311                                 gst_video_format_to_fourcc (video->format),
312                                 NULL);
313         if (stream->tags) {
314                 guint bitrate;
315                 if (gst_tag_list_get_uint (stream->tags, "bitrate", &bitrate))
316                         gst_caps_set_simple (caps, "bitrate", G_TYPE_INT,
317                                         (int) bitrate, NULL);
318                 if (gst_tag_list_get_uint (stream->tags, "maximum-bitrate",
319                                         &bitrate))
320                         gst_caps_set_simple (caps, "maximum-bitrate",
321                                         G_TYPE_INT, (int) bitrate, NULL);
322         }
323
324         return caps;
325 }
326
327 static gboolean
328 check_video_profile (const GstEncodingProfile *profile,
329                      GstDiscovererInformation *info)
330 {
331         GList *i;
332         gboolean found_video = FALSE, found_audio = FALSE;;
333
334         /* Check video and audio restrictions */
335         for (i = info->stream_list;
336              i && !(found_video && found_audio);
337              i = i->next) {
338                 GstStreamInformation *stream;
339                 GstCaps *caps = NULL;
340
341                 stream = (GstStreamInformation *) i->data;
342
343                 if (!found_video &&
344                     stream->streamtype == GST_STREAM_VIDEO) {
345                         caps = caps_from_video_stream_info (
346                                         GST_STREAM_VIDEO_INFORMATION (stream));
347                         if (match_profile (profile,
348                                            caps,
349                                            GST_ENCODING_PROFILE_VIDEO))
350                                 found_video = TRUE;
351                         else
352                                 gupnp_dlna_debug ("  Video did not match");
353                 } else if (!found_audio &&
354                            stream->streamtype == GST_STREAM_AUDIO) {
355                         caps = caps_from_audio_stream_info (
356                                         GST_STREAM_AUDIO_INFORMATION (stream));
357                         if (match_profile (profile,
358                                            caps,
359                                            GST_ENCODING_PROFILE_AUDIO))
360                                 found_audio = TRUE;
361                         else
362                                 gupnp_dlna_debug ("  Audio did not match");
363                 }
364
365                 if (caps)
366                         gst_caps_unref (caps);
367         }
368
369         if (!found_video || !found_audio)
370                 return FALSE;
371
372         /* Check container restrictions */
373         if (!check_container (info, profile)) {
374                 gupnp_dlna_debug ("  Container did not match");
375                 return FALSE;
376         }
377
378         return TRUE;
379 }
380
381 static void
382 guess_video_profile (GstDiscovererInformation *info,
383                      gchar                   **name,
384                      gchar                   **mime,
385                      GList                    *profiles)
386 {
387         GUPnPDLNAProfile *profile = NULL;
388         const GstEncodingProfile *enc_profile;
389         GList *i;
390
391         for (i = profiles; i; i = i->next) {
392                 profile = (GUPnPDLNAProfile *)(i->data);
393                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
394
395                 gupnp_dlna_debug ("Checking DLNA profile %s",
396                                         gupnp_dlna_profile_get_name (profile));
397                 if (check_video_profile (enc_profile, info)) {
398                         *name = g_strdup (
399                                         gupnp_dlna_profile_get_name (profile));
400                         *mime = g_strdup (
401                                         gupnp_dlna_profile_get_mime (profile));
402                         break;
403                 }
404         }
405 }
406
407 static void
408 guess_image_profile (GstStreamInformation *info,
409                      gchar               **name,
410                      gchar               **mime,
411                      GList                *profiles)
412 {
413         GstStreamVideoInformation *video = GST_STREAM_VIDEO_INFORMATION (info);
414         GstCaps *caps;
415         GList *i;
416         gboolean found = FALSE;
417         GUPnPDLNAProfile *profile;
418         const GstEncodingProfile *enc_profile;
419
420         if (!info || info->streamtype != GST_STREAM_IMAGE)
421                 return;
422
423         caps = caps_from_video_stream_info (video);
424
425         for (i = profiles; !found && i; i = i->next) {
426                 profile = (GUPnPDLNAProfile *)(i->data);
427                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
428
429                 /* Optimisation TODO: this can be pre-computed */
430                 if (is_video_profile (enc_profile))
431                         continue;
432
433                 if (match_profile (enc_profile,
434                                    caps,
435                                    GST_ENCODING_PROFILE_IMAGE)) {
436                         /* Found a match */
437                         *name = g_strdup (
438                                         gupnp_dlna_profile_get_name (profile));
439                         *mime = g_strdup (
440                                         gupnp_dlna_profile_get_mime (profile));
441                         break;
442                 }
443         }
444
445         gst_caps_unref (caps);
446 }
447
448 GUPnPDLNAInformation *
449 gupnp_dlna_information_new_from_discoverer_info (GstDiscovererInformation *info,
450                                                  GList                    *profiles)
451 {
452         GUPnPDLNAInformation *dlna;
453         GstStreamType type = GST_STREAM_UNKNOWN;
454         GList *tmp = info->stream_list;
455         gchar *name = NULL, *mime = NULL;
456
457         while (tmp) {
458                 GstStreamInformation *stream_info =
459                         (GstStreamInformation *) tmp->data;
460
461                 if (stream_info->streamtype == GST_STREAM_VIDEO)
462                         type = GST_STREAM_VIDEO;
463                 else if (stream_info->streamtype == GST_STREAM_IMAGE)
464                         type = GST_STREAM_IMAGE;
465                 else if (stream_info->streamtype == GST_STREAM_AUDIO &&
466                          type != GST_STREAM_VIDEO)
467                         type = GST_STREAM_AUDIO;
468
469                 tmp = tmp->next;
470         }
471
472         if (type == GST_STREAM_AUDIO)
473                 guess_audio_profile (info, &name, &mime, profiles);
474         else if (type == GST_STREAM_VIDEO)
475                 guess_video_profile (info, &name, &mime, profiles);
476         else if (type == GST_STREAM_IMAGE)
477                 /* There will be only one GstStreamInformation for images */
478                 guess_image_profile (info->stream_info, &name, &mime, profiles);
479
480         dlna = gupnp_dlna_information_new (name, mime, info);
481
482         g_free (name);
483         g_free (mime);
484         return dlna;
485 }