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