Relase 0.6.1
[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/pbutils/pbutils.h>
24 #include "gupnp-dlna-discoverer.h"
25 #include "gupnp-dlna-profile.h"
26
27 /*
28  * This file provides the infrastructure to load DLNA profiles and the
29  * corresponding restrictions from an on-disk representation, and use them to
30  * map a given stream to its DLNA profile, if possible.
31  *
32  * Each DLNA profile is represented as a GstEncodingProfile (there might be
33  * exceptions where a single DLNA profile is represented by multiple
34  * GstEncodingProfiles - right now that's only LPCM).
35  *
36  * For a GstEncodingProfile "profile", the following fields will be populated:
37  *
38  *   profile.name = "<DLNA Profile Name>"
39  *   profile.format = Muxing format caps (with restrictions) if specified,
40  *                    else GST_CAPS_NONE
41  *   profile.encodingprofiles = GList of GstStreamEncodingProfiles
42  *
43  * For each stream of the given profile, "profile.encodingprofiles" will have
44  * a GstEncodingProfile representing the restrictions for that stream (for a
45  * video format there will be one audio and one video stream, for example).
46  *
47  *   enc_profile.type = GST_ENCODING_PROFILE_{AUDIO,VIDEO,...} (UNKNOWN for
48  *                      container restrictions)
49  *   enc_profile.format = GstCaps with all the restrictions for this format
50  *   enc_profile.restriction = GST_CAPS_ANY
51  *
52  * We assume that all DLNA profiles have exactly one audio stream, or one audio
53  * stream and one video stream.
54  *
55  * Things yet to account for:
56  *
57  *   1. Multiple audio/video streams (we need to pick the "main" one - how?
58  *      Possibly get information from the demuxer.)
59  *
60  *   2. How do we handle discovered metadata which is in tags, but not in caps?
61  *      Could potentially move it to caps in a post-discovery, pre-guessing
62  *      phase
63  */
64
65 /* New profile guessing API */
66
67 #define GUPNP_DLNA_DEBUG_ENV "GUPNP_DLNA_DEBUG"
68
69 #define gupnp_dlna_debug(args...)                               \
70 do {                                                            \
71         const gchar *_e = g_getenv (GUPNP_DLNA_DEBUG_ENV);      \
72         if (_e && !g_str_equal (_e, "0"))                       \
73                 g_debug (args);                                 \
74 } while (0)
75
76 static gboolean
77 is_video_profile (const GstEncodingProfile *profile)
78 {
79         const GList *i, *profiles_list;
80
81         if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
82                 profiles_list = gst_encoding_container_profile_get_profiles
83                                      (GST_ENCODING_CONTAINER_PROFILE (profile));
84
85                 for (i = profiles_list ; i; i = i->next)
86                         if (GST_IS_ENCODING_VIDEO_PROFILE (i->data))
87                                 return TRUE;
88         }
89
90         return FALSE;
91 }
92
93 static gboolean
94 structure_can_intersect (const GstStructure *st1, const GstStructure *st2)
95 {
96         /* Since there is no API to intersect GstStructures, we cheat (thanks
97          * for the idea, tpm!) and make caps from the structuresa */
98
99         GstCaps *caps1, *caps2;
100         gboolean ret;
101
102         caps1 = gst_caps_new_full (gst_structure_copy (st1), NULL);
103         caps2 = gst_caps_new_full (gst_structure_copy (st2), NULL);
104
105         ret = gst_caps_can_intersect (caps1, caps2);
106
107         gst_caps_unref (caps1);
108         gst_caps_unref (caps2);
109
110         return ret;
111 }
112
113 static gboolean
114 structure_is_subset (const GstStructure *st1, const GstStructure *st2)
115 {
116         int i;
117
118         for (i = 0; i < gst_structure_n_fields (st2); i++) {
119                 const gchar *name = gst_structure_nth_field_name (st2, i);
120
121                 if (!gst_structure_has_field(st1, name)) {
122                         gupnp_dlna_debug ("    missing field %s", name);
123                         return FALSE;
124                 }
125         }
126
127         return TRUE;
128 }
129
130 /*
131  * Returns TRUE if stream_caps and profile_caps can intersect, and the
132  * intersecting structure from profile_caps is a subset of stream_caps. Put
133  * simply, the condition being met is that stream_caps intersects with
134  * profile_caps, and that intersection includes *all* fields specified by
135  * profile_caps (viz. all the fields specified by the DLNA profile's
136  * restrictions)
137  */
138 static gboolean
139 caps_can_intersect_and_is_subset (GstCaps       *stream_caps,
140                                   const GstCaps *profile_caps)
141 {
142         int i;
143         GstStructure *stream_st, *profile_st;
144
145         stream_st = gst_caps_get_structure (stream_caps, 0);
146
147         for (i = 0; i < gst_caps_get_size (profile_caps); i++) {
148                 profile_st = gst_caps_get_structure (profile_caps, i);
149
150                 if (structure_can_intersect (stream_st, profile_st) &&
151                     structure_is_subset (stream_st, profile_st))
152                         return TRUE;
153         }
154
155         return FALSE;
156 }
157
158 static gboolean
159 match_profile (GstEncodingProfile *profile,
160                GstCaps            *caps,
161                GType              type)
162 {
163         const GList *i, *profiles_list;
164         const gchar *name;
165
166         /* Profiles with an empty name are used only for inheritance and should
167          * not be matched against. */
168         name = gst_encoding_profile_get_name (profile);
169         if (name[0] == '\0')
170                 return FALSE;
171
172         profiles_list = gst_encoding_container_profile_get_profiles
173                                      (GST_ENCODING_CONTAINER_PROFILE (profile));
174
175         for (i = profiles_list; i; i = i->next){
176                 GstEncodingProfile *enc_profile = GST_ENCODING_PROFILE
177                                         (i->data);
178                 const GstCaps *format = gst_encoding_profile_get_format
179                                         (enc_profile);
180
181                 if (type == G_TYPE_FROM_INSTANCE (enc_profile) &&
182                     caps_can_intersect_and_is_subset (caps, format))
183                         return TRUE;
184         }
185
186         return FALSE;
187 }
188
189 static gboolean
190 check_container (GstDiscovererInfo  *info,
191                  GstEncodingProfile *profile)
192 {
193         GstDiscovererStreamInfo *stream_info;
194         GType stream_type;
195         GstCaps *stream_caps;
196         gboolean ret = FALSE;
197
198         const GstCaps *profile_caps = gst_encoding_profile_get_format (profile);
199
200         /* Top-level GstStreamInformation in the topology will be
201          * the container */
202         stream_info = gst_discoverer_info_get_stream_info (info);
203         stream_caps = gst_discoverer_stream_info_get_caps (stream_info);
204         stream_type = G_TYPE_FROM_INSTANCE (stream_info);
205
206         if (stream_type == GST_TYPE_DISCOVERER_CONTAINER_INFO &&
207             gst_caps_can_intersect (stream_caps, profile_caps))
208                 ret = TRUE;
209         else if (stream_type != GST_TYPE_DISCOVERER_CONTAINER_INFO &&
210                  gst_caps_is_empty (profile_caps))
211                 ret = TRUE;
212
213         gst_discoverer_stream_info_unref (stream_info);
214         gst_caps_unref (stream_caps);
215
216         return ret;
217 }
218
219 static GstCaps *
220 caps_from_audio_stream_info (GstDiscovererStreamInfo *info)
221 {
222         GstCaps *temp = gst_discoverer_stream_info_get_caps (info);
223         GstCaps *caps = gst_caps_copy (temp);
224         const GstDiscovererAudioInfo *audio_info =
225                 GST_DISCOVERER_AUDIO_INFO(info);
226         guint data;
227
228         gst_caps_unref (temp);
229
230         data = gst_discoverer_audio_info_get_sample_rate (audio_info);
231         if (data)
232                 gst_caps_set_simple (caps, "rate", G_TYPE_INT, data, NULL);
233
234         data = gst_discoverer_audio_info_get_channels (audio_info);
235         if (data)
236                 gst_caps_set_simple (caps, "channels", G_TYPE_INT, data, NULL);
237
238         data = gst_discoverer_audio_info_get_bitrate (audio_info);
239         if (data)
240                 gst_caps_set_simple (caps, "bitrate", G_TYPE_INT, data, NULL);
241
242         data = gst_discoverer_audio_info_get_max_bitrate (audio_info);
243         if (data)
244                 gst_caps_set_simple
245                         (caps, "maximum-bitrate", G_TYPE_INT, data, NULL);
246
247         data = gst_discoverer_audio_info_get_depth (audio_info);
248         if (data)
249                 gst_caps_set_simple (caps, "depth", G_TYPE_INT, data, NULL);
250
251         return caps;
252 }
253
254 static gboolean
255 check_audio_profile (GstDiscovererInfo  *info,
256                      GstEncodingProfile *profile)
257 {
258         GstCaps *caps;
259         GList *i, *stream_list;
260         gboolean found = FALSE;
261
262         /* Optimisation TODO: this can be pre-computed */
263         if (is_video_profile (profile))
264                 return FALSE;
265
266         stream_list = gst_discoverer_info_get_stream_list (info);
267
268         for (i = stream_list; !found && i; i = i->next) {
269                 GstDiscovererStreamInfo *stream =
270                         GST_DISCOVERER_STREAM_INFO(i->data);
271                 GType stream_type = G_TYPE_FROM_INSTANCE (stream);
272
273                 if (stream_type != GST_TYPE_DISCOVERER_AUDIO_INFO)
274                         continue;
275
276                 caps = caps_from_audio_stream_info (stream);
277
278                 if (match_profile (profile,
279                                    caps,
280                                    GST_TYPE_ENCODING_AUDIO_PROFILE)) {
281                         found = TRUE;
282                         break;
283                 }
284
285                 gst_caps_unref (caps);
286         }
287
288         gst_discoverer_stream_info_list_free (stream_list);
289
290         return found;
291 }
292
293 static void
294 guess_audio_profile (GstDiscovererInfo *info,
295                      gchar             **name,
296                      gchar             **mime,
297                      GList             *profiles)
298 {
299         GList *i;
300         GUPnPDLNAProfile *profile;
301         GstEncodingProfile *enc_profile;
302
303         for (i = profiles; i; i = i->next) {
304                 profile = (GUPnPDLNAProfile *)(i->data);
305                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
306
307                 gupnp_dlna_debug ("Checking DLNA profile %s",
308                                   gupnp_dlna_profile_get_name (profile));
309
310                 if (!check_audio_profile (info, enc_profile))
311                         gupnp_dlna_debug ("  Audio did not match");
312                 else if (!check_container (info, enc_profile))
313                         gupnp_dlna_debug ("  Container did not match");
314                 else {
315                         *name = g_strdup
316                                 (gupnp_dlna_profile_get_name (profile));
317                         *mime = g_strdup
318                                 (gupnp_dlna_profile_get_mime (profile));
319                         break;
320                 }
321         }
322 }
323
324 static GstCaps *
325 caps_from_video_stream_info (GstDiscovererStreamInfo *info)
326 {
327         GstCaps *temp = gst_discoverer_stream_info_get_caps (info);
328         GstCaps *caps = gst_caps_copy (temp);
329         const GstDiscovererVideoInfo *video_info =
330                 GST_DISCOVERER_VIDEO_INFO (info);
331         const GstTagList *stream_tag_list;
332         guint n, d, data;
333         gboolean value;
334
335         gst_caps_unref (temp);
336
337         data = gst_discoverer_video_info_get_height (video_info);
338         if (data)
339                 gst_caps_set_simple (caps, "height", G_TYPE_INT, data, NULL);
340
341         data = gst_discoverer_video_info_get_width (video_info);
342         if (data)
343                 gst_caps_set_simple (caps, "width", G_TYPE_INT, data, NULL);
344
345         data = gst_discoverer_video_info_get_depth (video_info);
346         if (data)
347                 gst_caps_set_simple (caps, "depth", G_TYPE_INT, data, NULL);
348
349         n = gst_discoverer_video_info_get_framerate_num (video_info);
350         d = gst_discoverer_video_info_get_framerate_denom (video_info);
351         if (n && d)
352                 gst_caps_set_simple (caps,
353                                      "framerate",
354                                      GST_TYPE_FRACTION, n, d,
355                                      NULL);
356
357         n = gst_discoverer_video_info_get_par_num (video_info);
358         d = gst_discoverer_video_info_get_par_denom (video_info);
359         if (n && d)
360                 gst_caps_set_simple (caps,
361                                      "pixel-aspect-ratio",
362                                      GST_TYPE_FRACTION, n, d,
363                                      NULL);
364
365         value = gst_discoverer_video_info_is_interlaced (video_info);
366         if (value)
367                 gst_caps_set_simple
368                         (caps, "interlaced", G_TYPE_BOOLEAN, value, NULL);
369
370         stream_tag_list = gst_discoverer_stream_info_get_tags (info);
371         if (stream_tag_list) {
372                 guint bitrate;
373                 if (gst_tag_list_get_uint (stream_tag_list, "bitrate", &bitrate))
374                         gst_caps_set_simple
375                              (caps, "bitrate", G_TYPE_INT, (int) bitrate, NULL);
376
377                 if (gst_tag_list_get_uint (stream_tag_list,
378                                            "maximum-bitrate",
379                                            &bitrate))
380                         gst_caps_set_simple (caps,
381                                              "maximum-bitrate",
382                                              G_TYPE_INT,
383                                              (int) bitrate,
384                                              NULL);
385         }
386
387         return caps;
388 }
389
390 static gboolean
391 check_video_profile (GstDiscovererInfo  *info,
392                      GstEncodingProfile *profile)
393 {
394         GList *i, *stream_list;
395         gboolean found_video = FALSE, found_audio = FALSE;;
396
397         stream_list = gst_discoverer_info_get_stream_list (info);
398
399         /* Check video and audio restrictions */
400         for (i = stream_list;
401              i && !(found_video && found_audio);
402              i = i->next) {
403                 GstDiscovererStreamInfo *stream;
404                 GType stream_type;
405                 GstCaps *caps = NULL;
406
407                 stream = GST_DISCOVERER_STREAM_INFO(i->data);
408                 stream_type = G_TYPE_FROM_INSTANCE (stream);
409
410                 if (!found_video &&
411                     stream_type == GST_TYPE_DISCOVERER_VIDEO_INFO) {
412                         caps = caps_from_video_stream_info (stream);
413                         if (match_profile (profile,
414                                            caps,
415                                            GST_TYPE_ENCODING_VIDEO_PROFILE))
416                                 found_video = TRUE;
417                         else
418                                 gupnp_dlna_debug ("  Video did not match");
419                 } else if (!found_audio &&
420                            stream_type == GST_TYPE_DISCOVERER_AUDIO_INFO) {
421                         caps = caps_from_audio_stream_info (stream);
422                         if (match_profile (profile,
423                                            caps,
424                                            GST_TYPE_ENCODING_AUDIO_PROFILE))
425                                 found_audio = TRUE;
426                         else
427                                 gupnp_dlna_debug ("  Audio did not match");
428                 }
429
430                 if (caps)
431                         gst_caps_unref (caps);
432         }
433
434         gst_discoverer_stream_info_list_free (stream_list);
435
436         if (!found_video || !found_audio)
437                 return FALSE;
438
439         /* Check container restrictions */
440         if (!check_container (info, profile)) {
441                 gupnp_dlna_debug ("  Container did not match");
442                 return FALSE;
443         }
444
445         return TRUE;
446 }
447
448 static void
449 guess_video_profile (GstDiscovererInfo *info,
450                      gchar             **name,
451                      gchar             **mime,
452                      GList             *profiles)
453 {
454         GUPnPDLNAProfile *profile = NULL;
455         GstEncodingProfile *enc_profile;
456         GList *i;
457
458         for (i = profiles; i; i = i->next) {
459                 profile = (GUPnPDLNAProfile *)(i->data);
460                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
461
462                 gupnp_dlna_debug ("Checking DLNA profile %s",
463                                   gupnp_dlna_profile_get_name (profile));
464                 if (check_video_profile (info, enc_profile)) {
465                         *name = g_strdup (gupnp_dlna_profile_get_name (profile));
466                         *mime = g_strdup (gupnp_dlna_profile_get_mime (profile));
467                         break;
468                 }
469         }
470 }
471
472 static void
473 guess_image_profile (GstDiscovererStreamInfo *info,
474                      gchar                   **name,
475                      gchar                   **mime,
476                      GList                   *profiles)
477 {
478         GstCaps *caps;
479         GList *i;
480         gboolean found = FALSE;
481         GUPnPDLNAProfile *profile;
482         GstEncodingProfile *enc_profile;
483         const GstDiscovererVideoInfo *video_info =
484                 GST_DISCOVERER_VIDEO_INFO (info);
485
486         if (!info || !gst_discoverer_video_info_is_image (video_info))
487                 return;
488
489         caps = caps_from_video_stream_info (info);
490
491         for (i = profiles; !found && i; i = i->next) {
492                 profile = (GUPnPDLNAProfile *)(i->data);
493                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
494
495                 /* Optimisation TODO: this can be pre-computed */
496                 if (!is_video_profile (enc_profile))
497                         continue;
498
499                 if (match_profile (enc_profile,
500                                    caps,
501                                    GST_TYPE_ENCODING_VIDEO_PROFILE)) {
502                         /* Found a match */
503                         *name = g_strdup (gupnp_dlna_profile_get_name (profile));
504                         *mime = g_strdup (gupnp_dlna_profile_get_mime (profile));
505                         break;
506                 }
507         }
508
509         gst_caps_unref (caps);
510 }
511
512 GUPnPDLNAInformation *
513 gupnp_dlna_information_new_from_discoverer_info (GstDiscovererInfo *info,
514                                                  GList             *profiles)
515 {
516         GUPnPDLNAInformation *dlna;
517         GList *video_list, *audio_list;
518         gchar *name = NULL, *mime = NULL;
519
520         video_list = gst_discoverer_info_get_video_streams (info);
521         audio_list = gst_discoverer_info_get_audio_streams (info);
522         if (video_list) {
523                 if ((g_list_length (video_list) ==1 ) &&
524                     gst_discoverer_video_info_is_image
525                                         (GST_DISCOVERER_VIDEO_INFO
526                                                   (video_list->data))) {
527                         GstDiscovererStreamInfo *stream;
528                         stream = (GstDiscovererStreamInfo *) video_list->data;
529                         guess_image_profile (stream, &name, &mime, profiles);
530                 } else
531                         guess_video_profile (info, &name, &mime, profiles);
532         } else if (audio_list)
533                 guess_audio_profile (info, &name, &mime, profiles);
534
535         gst_discoverer_stream_info_list_free (audio_list);
536         gst_discoverer_stream_info_list_free (video_list);
537
538         dlna = gupnp_dlna_information_new (name, mime, info);
539
540
541         g_free (name);
542         g_free (mime);
543
544         return dlna;
545 }