1/* GStreamer
2 * Copyright (C) 2013 Fluendo S.L. <support@fluendo.com>
3 *   Authors:    2013 Andoni Morales Alastruey <amorales@fluendo.com>
4 * Copyright (C) 2013 Sebastian Dröge <slomo@circular-chaos.org>
5 *
6 * gstios_assetsrc.c:
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 */
23/**
24 * SECTION:element-ios_assetsrc
25 * @see_also: #GstIOSAssetSrc
26 *
27 * Read data from an iOS asset from the media library.
28 *
29 * <refsect2>
30 * <title>Example launch line</title>
31 * |[
32 * gst-launch-1.0 iosassetsrc uri=assets-library://asset/asset.M4V?id=11&ext=M4V ! decodebin ! autoaudiosink
33 * ]| Plays asset with id a song.ogg from local dir.
34 * </refsect2>
35 */
36
37#ifdef HAVE_CONFIG_H
38#  include "config.h"
39#endif
40
41#include <gst/gst.h>
42#include <gst/base/base.h>
43#include "iosassetsrc.h"
44
45static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
46    GST_PAD_SRC,
47    GST_PAD_ALWAYS,
48    GST_STATIC_CAPS_ANY);
49
50GST_DEBUG_CATEGORY_STATIC (gst_ios_asset_src_debug);
51#define GST_CAT_DEFAULT gst_ios_asset_src_debug
52
53
54#define DEFAULT_BLOCKSIZE       4*1024
55
56enum
57{
58  PROP_0,
59  PROP_URI,
60};
61
62static void gst_ios_asset_src_finalize (GObject * object);
63
64static void gst_ios_asset_src_set_property (GObject * object, guint prop_id,
65    const GValue * value, GParamSpec * pspec);
66static void gst_ios_asset_src_get_property (GObject * object, guint prop_id,
67    GValue * value, GParamSpec * pspec);
68
69static gboolean gst_ios_asset_src_start (GstBaseSrc * basesrc);
70static gboolean gst_ios_asset_src_stop (GstBaseSrc * basesrc);
71
72static gboolean gst_ios_asset_src_is_seekable (GstBaseSrc * src);
73static gboolean gst_ios_asset_src_get_size (GstBaseSrc * src, guint64 * size);
74static GstFlowReturn gst_ios_asset_src_create (GstBaseSrc * src, guint64 offset,
75    guint length, GstBuffer ** buffer);
76static gboolean gst_ios_asset_src_query (GstBaseSrc * src, GstQuery * query);
77
78static void gst_ios_asset_src_uri_handler_init (gpointer g_iface,
79    gpointer iface_data);
80
81static void
82_do_init (GType ios_assetsrc_type)
83{
84  static const GInterfaceInfo urihandler_info = {
85    gst_ios_asset_src_uri_handler_init,
86    NULL,
87    NULL
88  };
89
90  g_type_add_interface_static (ios_assetsrc_type, GST_TYPE_URI_HANDLER,
91      &urihandler_info);
92  GST_DEBUG_CATEGORY_INIT (gst_ios_asset_src_debug, "iosassetsrc", 0, "iosassetsrc element");
93}
94
95G_DEFINE_TYPE_WITH_CODE (GstIOSAssetSrc, gst_ios_asset_src, GST_TYPE_BASE_SRC,
96    _do_init (g_define_type_id));
97
98static void
99gst_ios_asset_src_class_init (GstIOSAssetSrcClass * klass)
100{
101  GObjectClass *gobject_class;
102  GstElementClass *gstelement_class;
103  GstBaseSrcClass *gstbasesrc_class;
104
105  gobject_class = G_OBJECT_CLASS (klass);
106  gstelement_class = GST_ELEMENT_CLASS (klass);
107  gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
108
109  gobject_class->set_property = gst_ios_asset_src_set_property;
110  gobject_class->get_property = gst_ios_asset_src_get_property;
111
112  g_object_class_install_property (gobject_class, PROP_URI,
113      g_param_spec_string ("uri", "Asset URI",
114          "URI of the asset to read", NULL,
115          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
116          GST_PARAM_MUTABLE_READY));
117
118  gobject_class->finalize = gst_ios_asset_src_finalize;
119
120  gst_element_class_set_static_metadata (gstelement_class,
121      "IOSAsset Source",
122      "Source/File",
123      "Read from arbitrary point in a iOS asset",
124      "Andoni Morales Alastruey <amorales@fluendo.com>");
125
126  gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
127
128  gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_ios_asset_src_start);
129  gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_ios_asset_src_stop);
130  gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_ios_asset_src_is_seekable);
131  gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_ios_asset_src_get_size);
132  gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_ios_asset_src_create);
133  gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_ios_asset_src_query);
134}
135
136static void
137gst_ios_asset_src_init (GstIOSAssetSrc * src)
138{
139  src->uri = NULL;
140  src->asset = NULL;
141  src->library = (__bridge_retained gpointer)[[GstAssetsLibrary alloc] init];
142  gst_base_src_set_blocksize (GST_BASE_SRC (src), DEFAULT_BLOCKSIZE);
143}
144
145static void
146gst_ios_asset_src_free_resources (GstIOSAssetSrc *src)
147{
148  if (src->asset != NULL) {
149    CFBridgingRelease(src->asset);
150    src->asset = NULL;
151  }
152
153  if (src->url != NULL) {
154    CFBridgingRelease(src->url);
155    src->url = NULL;
156  }
157
158  if (src->uri != NULL) {
159    g_free (src->uri);
160    src->uri = NULL;
161  }
162}
163
164static void
165gst_ios_asset_src_finalize (GObject * object)
166{
167  GstIOSAssetSrc *src;
168
169  src = GST_IOS_ASSET_SRC (object);
170  gst_ios_asset_src_free_resources (src);
171  CFBridgingRelease(src->library);
172
173  G_OBJECT_CLASS (gst_ios_asset_src_parent_class)->finalize (object);
174}
175
176static gboolean
177gst_ios_asset_src_set_uri (GstIOSAssetSrc * src, const gchar * uri, GError **err)
178{
179  GstState state;
180  NSString *nsuristr;
181  NSURL *url;
182
183  /* the element must be stopped in order to do this */
184  GST_OBJECT_LOCK (src);
185  state = GST_STATE (src);
186  if (state != GST_STATE_READY && state != GST_STATE_NULL)
187    goto wrong_state;
188  GST_OBJECT_UNLOCK (src);
189
190  gst_ios_asset_src_free_resources (src);
191
192  nsuristr = [[NSString alloc] initWithUTF8String:uri];
193  url = [[NSURL alloc] initWithString:nsuristr];
194
195  if (url == NULL) {
196    GST_ERROR_OBJECT (src, "Invalid URI: %s", uri);
197    g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
198        "Invalid URI: %s", uri);
199    return FALSE;
200  }
201
202  GST_INFO_OBJECT (src, "URI      : %s", src->uri);
203  src->url = (__bridge_retained gpointer)url;
204  src->uri = g_strdup (uri);
205  g_object_notify (G_OBJECT (src), "uri");
206
207  return TRUE;
208
209  /* ERROR */
210wrong_state:
211  {
212    g_warning ("Changing the 'uri' property on iosassetsrc when an asset is "
213        "open is not supported.");
214    g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
215        "Changing the 'uri' property on iosassetsrc when an asset is "
216        "open is not supported.");
217    GST_OBJECT_UNLOCK (src);
218    return FALSE;
219  }
220}
221
222static void
223gst_ios_asset_src_set_property (GObject * object, guint prop_id,
224    const GValue * value, GParamSpec * pspec)
225{
226  GstIOSAssetSrc *src;
227
228  g_return_if_fail (GST_IS_IOS_ASSET_SRC (object));
229
230  src = GST_IOS_ASSET_SRC (object);
231
232  switch (prop_id) {
233    case PROP_URI:
234      gst_ios_asset_src_set_uri (src, g_value_get_string (value), NULL);
235      break;
236    default:
237      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
238      break;
239  }
240}
241
242static void
243gst_ios_asset_src_get_property (GObject * object, guint prop_id, GValue * value,
244    GParamSpec * pspec)
245{
246  GstIOSAssetSrc *src;
247
248  g_return_if_fail (GST_IS_IOS_ASSET_SRC (object));
249
250  src = GST_IOS_ASSET_SRC (object);
251
252  switch (prop_id) {
253    case PROP_URI:
254      g_value_set_string (value, src->uri);
255      break;
256    default:
257      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
258      break;
259  }
260}
261
262static GstFlowReturn
263gst_ios_asset_src_create (GstBaseSrc * basesrc, guint64 offset, guint length,
264    GstBuffer ** buffer)
265{
266  GstBuffer *buf = NULL;
267  GstMapInfo info;
268  NSError *err = nil;
269  guint bytes_read;
270  GstFlowReturn ret;
271  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (basesrc);
272
273  buf = gst_buffer_new_and_alloc (length);
274  if (G_UNLIKELY (buf == NULL && length > 0)) {
275    GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", length);
276    ret = GST_FLOW_ERROR;
277    goto exit;
278  }
279
280  gst_buffer_map (buf, &info, GST_MAP_READWRITE);
281
282  /* No need to read anything if length is 0 */
283  bytes_read = [GST_IOS_ASSET_SRC_ASSET(src) getBytes: info.data
284      fromOffset:offset
285          length:length
286           error:&err];
287  if (G_UNLIKELY (err != NULL)) {
288    goto could_not_read;
289  }
290
291  /* we should eos if we read less than what was requested */
292  if (G_UNLIKELY (bytes_read < length)) {
293    GST_DEBUG ("EOS");
294    ret = GST_FLOW_EOS;
295  } else {
296    ret = GST_FLOW_OK;
297  }
298
299  gst_buffer_unmap (buf, &info);
300  gst_buffer_set_size (buf, bytes_read);
301
302  GST_BUFFER_OFFSET (buf) = offset;
303  GST_BUFFER_OFFSET_END (buf) = offset + bytes_read;
304
305  *buffer = buf;
306
307  goto exit;
308
309  /* ERROR */
310could_not_read:
311  {
312    GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM);
313    gst_buffer_unmap (buf, &info);
314    gst_buffer_unref (buf);
315    ret = GST_FLOW_ERROR;
316    goto exit;
317  }
318exit:
319  {
320    return ret;
321  }
322
323}
324
325static gboolean
326gst_ios_asset_src_query (GstBaseSrc * basesrc, GstQuery * query)
327{
328  gboolean ret = FALSE;
329  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (basesrc);
330
331  switch (GST_QUERY_TYPE (query)) {
332    case GST_QUERY_URI:
333      gst_query_set_uri (query, src->uri);
334      ret = TRUE;
335      break;
336    default:
337      ret = FALSE;
338      break;
339  }
340
341  if (!ret)
342    ret = GST_BASE_SRC_CLASS (gst_ios_asset_src_parent_class)->query (basesrc, query);
343
344  return ret;
345}
346
347static gboolean
348gst_ios_asset_src_is_seekable (GstBaseSrc * basesrc)
349{
350  return TRUE;
351}
352
353static gboolean
354gst_ios_asset_src_get_size (GstBaseSrc * basesrc, guint64 * size)
355{
356  GstIOSAssetSrc *src;
357
358  src = GST_IOS_ASSET_SRC (basesrc);
359
360  *size = (guint64) [GST_IOS_ASSET_SRC_ASSET(src) size];
361  return TRUE;
362}
363
364static gboolean
365gst_ios_asset_src_start (GstBaseSrc * basesrc)
366{
367  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (basesrc);
368  gboolean ret = TRUE;
369
370  src->asset = (__bridge_retained gpointer)[GST_IOS_ASSET_SRC_LIBRARY(src) assetForURLSync: GST_IOS_ASSET_SRC_URL(src)];
371
372  if (src->asset == NULL) {
373    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
374        ("Could not open asset \"%s\" for reading.", src->uri),
375        GST_ERROR_SYSTEM);
376    ret = FALSE;
377  };
378
379  return ret;
380}
381
382/* unmap and close the ios_asset */
383static gboolean
384gst_ios_asset_src_stop (GstBaseSrc * basesrc)
385{
386  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (basesrc);
387
388  CFBridgingRelease(src->asset);
389  return TRUE;
390}
391
392static GstURIType
393gst_ios_asset_src_uri_get_type (GType type)
394{
395  return GST_URI_SRC;
396}
397
398static const gchar * const *
399gst_ios_asset_src_uri_get_protocols (GType type)
400{
401  static const gchar * const protocols[] = { "assets-library", NULL };
402
403  return protocols;
404}
405
406static gchar *
407gst_ios_asset_src_uri_get_uri (GstURIHandler * handler)
408{
409  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (handler);
410
411  return g_strdup (src->uri);
412}
413
414static gboolean
415gst_ios_asset_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, GError **err)
416{
417  GstIOSAssetSrc *src = GST_IOS_ASSET_SRC (handler);
418
419  if (! g_str_has_prefix (uri, "assets-library://")) {
420    GST_WARNING_OBJECT (src, "Invalid URI '%s' for ios_assetsrc", uri);
421    g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
422        "Invalid URI '%s' for ios_assetsrc", uri);
423    return FALSE;
424  }
425
426  return gst_ios_asset_src_set_uri (src, uri, err);
427}
428
429static void
430gst_ios_asset_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
431{
432  GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
433
434  iface->get_type = gst_ios_asset_src_uri_get_type;
435  iface->get_protocols = gst_ios_asset_src_uri_get_protocols;
436  iface->get_uri = gst_ios_asset_src_uri_get_uri;
437  iface->set_uri = gst_ios_asset_src_uri_set_uri;
438}
439
440
441@implementation GstAssetsLibrary
442
443@synthesize asset;
444@synthesize result;
445
446- (id) init
447{
448  self = [super init];
449
450  return self;
451}
452
453- (ALAssetRepresentation *) assetForURLSync:(NSURL*) uri
454{
455  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
456  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
457
458  dispatch_async(queue, ^{
459    [self assetForURL:uri resultBlock:
460         ^(ALAsset *myasset)
461         {
462           self.asset = myasset;
463           self.result = [myasset defaultRepresentation];
464
465           dispatch_semaphore_signal(sema);
466         }
467             failureBlock:
468         ^(NSError *myerror)
469         {
470           self.result = nil;
471           dispatch_semaphore_signal(sema);
472         }
473    ];
474  });
475
476  dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
477
478  return self.result;
479}
480@end
481