doc: Update NEWS for 0.4.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/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 (video->depth)
293                 gst_caps_set_simple (caps, "depth", G_TYPE_INT,
294                                 video->depth, NULL);
295         if (GST_VALUE_HOLDS_FRACTION (&video->frame_rate)) {
296                 n = gst_value_get_fraction_numerator (&video->frame_rate);
297                 d = gst_value_get_fraction_denominator (&video->frame_rate);
298                 gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
299                                 n, d, NULL);
300         }
301         if (GST_VALUE_HOLDS_FRACTION (&video->pixel_aspect_ratio)) {
302                 n = gst_value_get_fraction_numerator (
303                                         &video->pixel_aspect_ratio);
304                 d = gst_value_get_fraction_denominator (
305                                         &video->pixel_aspect_ratio);
306                 gst_caps_set_simple (caps, "pixel-aspect-ratio",
307                                 GST_TYPE_FRACTION, n, d, NULL);
308         }
309         if (video->interlaced)
310                 gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN,
311                                 video->interlaced, NULL);
312         if (video->format != GST_VIDEO_FORMAT_UNKNOWN)
313                 gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC,
314                                 gst_video_format_to_fourcc (video->format),
315                                 NULL);
316         if (stream->tags) {
317                 guint bitrate;
318                 if (gst_tag_list_get_uint (stream->tags, "bitrate", &bitrate))
319                         gst_caps_set_simple (caps, "bitrate", G_TYPE_INT,
320                                         (int) bitrate, NULL);
321                 if (gst_tag_list_get_uint (stream->tags, "maximum-bitrate",
322                                         &bitrate))
323                         gst_caps_set_simple (caps, "maximum-bitrate",
324                                         G_TYPE_INT, (int) bitrate, NULL);
325         }
326
327         return caps;
328 }
329
330 static gboolean
331 check_video_profile (const GstEncodingProfile *profile,
332                      GstDiscovererInformation *info)
333 {
334         GList *i;
335         gboolean found_video = FALSE, found_audio = FALSE;;
336
337         /* Check video and audio restrictions */
338         for (i = info->stream_list;
339              i && !(found_video && found_audio);
340              i = i->next) {
341                 GstStreamInformation *stream;
342                 GstCaps *caps = NULL;
343
344                 stream = (GstStreamInformation *) i->data;
345
346                 if (!found_video &&
347                     stream->streamtype == GST_STREAM_VIDEO) {
348                         caps = caps_from_video_stream_info (
349                                         GST_STREAM_VIDEO_INFORMATION (stream));
350                         if (match_profile (profile,
351                                            caps,
352                                            GST_ENCODING_PROFILE_VIDEO))
353                                 found_video = TRUE;
354                         else
355                                 gupnp_dlna_debug ("  Video did not match");
356                 } else if (!found_audio &&
357                            stream->streamtype == GST_STREAM_AUDIO) {
358                         caps = caps_from_audio_stream_info (
359                                         GST_STREAM_AUDIO_INFORMATION (stream));
360                         if (match_profile (profile,
361                                            caps,
362                                            GST_ENCODING_PROFILE_AUDIO))
363                                 found_audio = TRUE;
364                         else
365                                 gupnp_dlna_debug ("  Audio did not match");
366                 }
367
368                 if (caps)
369                         gst_caps_unref (caps);
370         }
371
372         if (!found_video || !found_audio)
373                 return FALSE;
374
375         /* Check container restrictions */
376         if (!check_container (info, profile)) {
377                 gupnp_dlna_debug ("  Container did not match");
378                 return FALSE;
379         }
380
381         return TRUE;
382 }
383
384 static void
385 guess_video_profile (GstDiscovererInformation *info,
386                      gchar                   **name,
387                      gchar                   **mime,
388                      GList                    *profiles)
389 {
390         GUPnPDLNAProfile *profile = NULL;
391         const GstEncodingProfile *enc_profile;
392         GList *i;
393
394         for (i = profiles; i; i = i->next) {
395                 profile = (GUPnPDLNAProfile *)(i->data);
396                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
397
398                 gupnp_dlna_debug ("Checking DLNA profile %s",
399                                         gupnp_dlna_profile_get_name (profile));
400                 if (check_video_profile (enc_profile, info)) {
401                         *name = g_strdup (
402                                         gupnp_dlna_profile_get_name (profile));
403                         *mime = g_strdup (
404                                         gupnp_dlna_profile_get_mime (profile));
405                         break;
406                 }
407         }
408 }
409
410 static void
411 guess_image_profile (GstStreamInformation *info,
412                      gchar               **name,
413                      gchar               **mime,
414                      GList                *profiles)
415 {
416         GstStreamVideoInformation *video = GST_STREAM_VIDEO_INFORMATION (info);
417         GstCaps *caps;
418         GList *i;
419         gboolean found = FALSE;
420         GUPnPDLNAProfile *profile;
421         const GstEncodingProfile *enc_profile;
422
423         if (!info || info->streamtype != GST_STREAM_IMAGE)
424                 return;
425
426         caps = caps_from_video_stream_info (video);
427
428         for (i = profiles; !found && i; i = i->next) {
429                 profile = (GUPnPDLNAProfile *)(i->data);
430                 enc_profile = gupnp_dlna_profile_get_encoding_profile (profile);
431
432                 /* Optimisation TODO: this can be pre-computed */
433                 if (is_video_profile (enc_profile))
434                         continue;
435
436                 if (match_profile (enc_profile,
437                                    caps,
438                                    GST_ENCODING_PROFILE_IMAGE)) {
439                         /* Found a match */
440                         *name = g_strdup (
441                                         gupnp_dlna_profile_get_name (profile));
442                         *mime = g_strdup (
443                                         gupnp_dlna_profile_get_mime (profile));
444                         break;
445                 }
446         }
447
448         gst_caps_unref (caps);
449 }
450
451 GUPnPDLNAInformation *
452 gupnp_dlna_information_new_from_discoverer_info (GstDiscovererInformation *info,
453                                                  GList                    *profiles)
454 {
455         GUPnPDLNAInformation *dlna;
456         GstStreamType type = GST_STREAM_UNKNOWN;
457         GList *tmp = info->stream_list;
458         gchar *name = NULL, *mime = NULL;
459
460         while (tmp) {
461                 GstStreamInformation *stream_info =
462                         (GstStreamInformation *) tmp->data;
463
464                 if (stream_info->streamtype == GST_STREAM_VIDEO)
465                         type = GST_STREAM_VIDEO;
466                 else if (stream_info->streamtype == GST_STREAM_IMAGE)
467                         type = GST_STREAM_IMAGE;
468                 else if (stream_info->streamtype == GST_STREAM_AUDIO &&
469                          type != GST_STREAM_VIDEO)
470                         type = GST_STREAM_AUDIO;
471
472                 tmp = tmp->next;
473         }
474
475         if (type == GST_STREAM_AUDIO)
476                 guess_audio_profile (info, &name, &mime, profiles);
477         else if (type == GST_STREAM_VIDEO)
478                 guess_video_profile (info, &name, &mime, profiles);
479         else if (type == GST_STREAM_IMAGE)
480                 /* There will be only one GstStreamInformation for images */
481                 guess_image_profile (info->stream_info, &name, &mime, profiles);
482
483         dlna = gupnp_dlna_information_new (name, mime, info);
484
485         g_free (name);
486         g_free (mime);
487         return dlna;
488 }