Initial commit
[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 Library 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  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library 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 static GList *profiles = NULL;
69
70 static gboolean is_video_profile (GstEncodingProfile *profile)
71 {
72         GList *i;
73
74         for (i = profile->encodingprofiles; i; i = i->next)
75                 if (((GstStreamEncodingProfile *) i->data)->type ==
76                                         GST_ENCODING_PROFILE_VIDEO)
77                         return TRUE;
78
79         return FALSE;
80 }
81
82 static gboolean structure_can_intersect (const GstStructure *st1,
83                                          const GstStructure *st2)
84 {
85         /* Since there is no API to intersect GstStructures, we cheat (thanks
86          * for the idea, tpm!) and make caps from the structuresa */
87
88         GstCaps *caps1, *caps2;
89         gboolean ret;
90
91         caps1 = gst_caps_new_full (gst_structure_copy (st1), NULL);
92         caps2 = gst_caps_new_full (gst_structure_copy (st2), NULL);
93
94         ret = gst_caps_can_intersect (caps1, caps2);
95
96         gst_caps_unref (caps1);
97         gst_caps_unref (caps2);
98         return ret;
99 }
100
101 static gboolean structure_is_subset (const GstStructure *st1,
102                                      const GstStructure *st2)
103 {
104         int i;
105
106         for (i = 0; i < gst_structure_n_fields (st2); i++) {
107                 const gchar *name = gst_structure_nth_field_name (st2, i);
108
109                 if (!gst_structure_has_field(st1, name))
110                         return FALSE;
111         }
112
113         return TRUE;
114 }
115
116 /*
117  * Returns TRUE if stream_caps and profile_caps can intersect, and the
118  * intersecting structure from profile_caps is a subset of stream_caps. Put
119  * simply, the condition being met is that stream_caps intersects with
120  * profile_caps, and that intersection includes *all* fields specified by
121  * profile_caps (viz. all the fields specified by the DLNA profile's
122  * restrictions)
123  */
124 static gboolean caps_can_intersect_and_is_subset (GstCaps *stream_caps,
125                                                   GstCaps *profile_caps)
126 {
127         int i;
128         GstStructure *stream_st, *profile_st;
129
130         stream_st = gst_caps_get_structure (stream_caps, 0);
131
132         for (i = 0; i < gst_caps_get_size (profile_caps); i++) {
133                 profile_st = gst_caps_get_structure (profile_caps, i);
134
135                 if (structure_can_intersect (stream_st, profile_st) &&
136                     structure_is_subset (stream_st, profile_st))
137                         return TRUE;
138         }
139
140         return FALSE;
141 }
142
143 static gboolean
144 match_profile (GstEncodingProfile     *profile,
145                GstCaps                *caps,
146                GstEncodingProfileType type)
147 {
148         GList *i;
149
150         /* Profiles with an empty name are used only for inheritance and should
151          * not be matched against. */
152         if (profile->name[0] == '\0')
153           return FALSE;
154
155         for (i = profile->encodingprofiles; i; i = i->next) {
156                 GstStreamEncodingProfile *enc_profile = i->data;
157
158                 if (enc_profile->type == type &&
159                     caps_can_intersect_and_is_subset (caps,
160                                                       enc_profile->format))
161                         return TRUE;
162         }
163
164         return FALSE;
165 }
166
167 static gboolean
168 check_container (GstDiscovererInformation *info, GstEncodingProfile *profile)
169 {
170         /* Top-level GstStreamInformation in the topology will be
171          * the container */
172         if (info->stream_info->streamtype == GST_STREAM_CONTAINER &&
173             gst_caps_can_intersect (info->stream_info->caps, profile->format))
174                 return TRUE;
175         else if (info->stream_info->streamtype != GST_STREAM_CONTAINER &&
176                  gst_caps_is_empty (profile->format))
177                 return TRUE;
178
179         return FALSE;
180 }
181
182 static GstCaps *
183 caps_from_audio_stream_info (GstStreamAudioInformation *audio)
184 {
185         GstCaps *caps = gst_caps_copy ((GST_STREAM_INFORMATION (audio))->caps);
186
187         if (audio->sample_rate)
188                 gst_caps_set_simple (caps, "rate", G_TYPE_INT,
189                                 audio->sample_rate, NULL);
190         if (audio->channels)
191                 gst_caps_set_simple (caps, "channels", G_TYPE_INT,
192                                 audio->channels, NULL);
193         if (audio->bitrate)
194                 gst_caps_set_simple (caps, "bitrate", G_TYPE_INT,
195                                 audio->bitrate, NULL);
196         if (audio->max_bitrate)
197                 gst_caps_set_simple (caps, "maximum-bitrate", G_TYPE_INT,
198                                 audio->max_bitrate, NULL);
199         if (audio->depth)
200                 gst_caps_set_simple (caps, "depth", G_TYPE_INT,
201                                 audio->depth, NULL);
202
203         return caps;
204 }
205
206 static gboolean
207 check_audio_profile (GstEncodingProfile       *profile,
208                      GstDiscovererInformation *info)
209 {
210         GstCaps *caps;
211         GList *i;
212         gboolean found = FALSE;
213
214         /* Optimisation TODO: this can be pre-computed */
215         if (is_video_profile (profile))
216                 return FALSE;
217
218         for (i = info->stream_list; !found && i; i = i->next) {
219                 GstStreamInformation *stream = (GstStreamInformation *) i->data;
220                 GstStreamAudioInformation *audio;
221
222                 if (stream->streamtype != GST_STREAM_AUDIO)
223                         continue;
224
225                 audio = GST_STREAM_AUDIO_INFORMATION (stream);
226                 caps = caps_from_audio_stream_info (audio);
227
228                 if (match_profile (profile, caps, GST_ENCODING_PROFILE_AUDIO)) {
229                         found = TRUE;
230                         break;
231                 }
232
233                 gst_caps_unref (caps);
234         }
235
236         return found;
237 }
238
239 static void
240 guess_audio_profile (GstDiscovererInformation *info, gchar **name, gchar **mime)
241 {
242         GList *i;
243
244         for (i = profiles; i; i = i->next) {
245                 GUPnPDLNAProfile *profile = (GUPnPDLNAProfile *)(i->data);
246
247                 if (check_audio_profile (profile->enc_profile, info) &&
248                     check_container (info, profile->enc_profile)) {
249                         *name = g_strdup (profile->name);
250                         *mime = g_strdup (profile->mime);
251                         break;
252                 }
253         }
254 }
255
256 static GstCaps *
257 caps_from_video_stream_info (GstStreamVideoInformation *video)
258 {
259         GstStreamInformation *stream = GST_STREAM_INFORMATION (video);
260         GstCaps *caps = gst_caps_copy (stream->caps);
261         int n, d;
262
263         if (video->height)
264                 gst_caps_set_simple (caps, "height", G_TYPE_INT,
265                                 video->height, NULL);
266         if (video->width)
267                 gst_caps_set_simple (caps, "width", G_TYPE_INT,
268                                 video->width, NULL);
269         if (GST_VALUE_HOLDS_FRACTION (&video->frame_rate)) {
270                 n = gst_value_get_fraction_numerator (&video->frame_rate);
271                 d = gst_value_get_fraction_denominator (&video->frame_rate);
272                 gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
273                                 n, d, NULL);
274         }
275         if (GST_VALUE_HOLDS_FRACTION (&video->pixel_aspect_ratio)) {
276                 n = gst_value_get_fraction_numerator (
277                                         &video->pixel_aspect_ratio);
278                 d = gst_value_get_fraction_denominator (
279                                         &video->pixel_aspect_ratio);
280                 gst_caps_set_simple (caps, "pixel-aspect-ratio",
281                                 GST_TYPE_FRACTION, n, d, NULL);
282         }
283         if (video->format != GST_VIDEO_FORMAT_UNKNOWN)
284                 gst_caps_set_simple (caps, "format", GST_TYPE_FOURCC,
285                                 gst_video_format_to_fourcc (video->format),
286                                 NULL);
287         if (stream->tags) {
288                 guint bitrate;
289                 if (gst_tag_list_get_uint (stream->tags, "bitrate", &bitrate))
290                         gst_caps_set_simple (caps, "bitrate", G_TYPE_INT,
291                                         (int) bitrate, NULL);
292                 if (gst_tag_list_get_uint (stream->tags, "maximum-bitrate",
293                                         &bitrate))
294                         gst_caps_set_simple (caps, "maximum-bitrate",
295                                         G_TYPE_INT, (int) bitrate, NULL);
296         }
297
298         return caps;
299 }
300
301 static gboolean
302 check_video_profile (GstEncodingProfile *profile,
303                      GstDiscovererInformation *info)
304 {
305         GList *i;
306         gboolean found_video = FALSE, found_audio = FALSE;;
307
308         /* Check video and audio restrictions */
309         for (i = info->stream_list;
310              i && !(found_video && found_audio);
311              i = i->next) {
312                 GstStreamInformation *stream;
313                 GstCaps *caps = NULL;
314
315                 stream = (GstStreamInformation *) i->data;
316
317                 if (!found_video &&
318                     stream->streamtype == GST_STREAM_VIDEO) {
319                         caps = caps_from_video_stream_info (
320                                         GST_STREAM_VIDEO_INFORMATION (stream));
321                         if (match_profile (profile,
322                                            caps,
323                                            GST_ENCODING_PROFILE_VIDEO))
324                                 found_video = TRUE;
325                 } else if (!found_audio &&
326                            stream->streamtype == GST_STREAM_AUDIO) {
327                         caps = caps_from_audio_stream_info (
328                                         GST_STREAM_AUDIO_INFORMATION (stream));
329                         if (match_profile (profile,
330                                            caps,
331                                            GST_ENCODING_PROFILE_AUDIO))
332                                 found_audio = TRUE;
333                 }
334
335                 if (caps)
336                         gst_caps_unref (caps);
337         }
338
339         if (!found_video || !found_audio)
340                 return FALSE;
341
342         /* Check container restrictions */
343         if (!check_container (info, profile))
344                 return FALSE;
345
346         return TRUE;
347 }
348
349 static void
350 guess_video_profile (GstDiscovererInformation *info, gchar **name, gchar **mime)
351 {
352         GUPnPDLNAProfile *profile = NULL;
353         GList *i;
354
355         for (i = profiles; i; i = i->next) {
356                 profile = (GUPnPDLNAProfile *)(i->data);
357
358                 if (check_video_profile (profile->enc_profile, info)) {
359                         *name = g_strdup (profile->name);
360                         *mime = g_strdup (profile->mime);
361                         break;
362                 }
363         }
364 }
365
366 static void
367 guess_image_profile (GstStreamInformation *info, gchar **name, gchar **mime)
368 {
369         GstStreamVideoInformation *video = GST_STREAM_VIDEO_INFORMATION (info);
370         GstCaps *caps;
371         GList *i;
372         gboolean found = FALSE;
373
374         if (!info || info->streamtype != GST_STREAM_IMAGE)
375                 return;
376
377         caps = caps_from_video_stream_info (video);
378
379         for (i = profiles; !found && i; i = i->next) {
380                 GUPnPDLNAProfile *profile = (GUPnPDLNAProfile *)(i->data);
381
382                 /* Optimisation TODO: this can be pre-computed */
383                 if (is_video_profile (profile->enc_profile))
384                         continue;
385
386                 if (match_profile (profile->enc_profile,
387                                    caps,
388                                    GST_ENCODING_PROFILE_IMAGE)) {
389                         /* Found a match */
390                         *name = g_strdup (profile->name);
391                         *mime = g_strdup (profile->mime);
392                         break;
393                 }
394         }
395
396         gst_caps_unref (caps);
397 }
398
399 GUPnPDLNAInformation *
400 gupnp_dlna_information_new_from_discoverer_info (GstDiscovererInformation * info)
401 {
402         GUPnPDLNAInformation *dlna;
403         GstStreamType type = GST_STREAM_UNKNOWN;
404         GList *tmp = info->stream_list;
405         gchar *name = NULL, *mime = NULL;
406
407         if (!profiles) {
408                 profiles = g_list_concat (profiles,
409                                         gupnp_dlna_load_profiles_from_disk ());
410         }
411
412         while (tmp) {
413                 GstStreamInformation *stream_info =
414                         (GstStreamInformation *) tmp->data;
415
416                 if (stream_info->streamtype == GST_STREAM_VIDEO)
417                         type = GST_STREAM_VIDEO;
418                 else if (stream_info->streamtype == GST_STREAM_IMAGE)
419                         type = GST_STREAM_IMAGE;
420                 else if (stream_info->streamtype == GST_STREAM_AUDIO &&
421                          type != GST_STREAM_VIDEO)
422                         type = GST_STREAM_AUDIO;
423
424                 tmp = tmp->next;
425         }
426
427         if (type == GST_STREAM_AUDIO)
428                 guess_audio_profile (info, &name, &mime);
429         else if (type == GST_STREAM_VIDEO)
430                 guess_video_profile (info, &name, &mime);
431         else if (type == GST_STREAM_IMAGE)
432                 /* There will be only one GstStreamInformation for images */
433                 guess_image_profile (info->stream_info, &name, &mime);
434
435         dlna = gupnp_dlna_information_new (name, mime, info);
436
437         g_debug ("DLNA profile: %s -> %s, %s", info->uri, name, mime);
438
439         g_free (name);
440         g_free (mime);
441         return dlna;
442 }