diff options
Diffstat (limited to 'subprojects/gst-plugins-bad/sys/applemedia/avfassetsrc.m')
-rw-r--r-- | subprojects/gst-plugins-bad/sys/applemedia/avfassetsrc.m | 1109 |
1 files changed, 1109 insertions, 0 deletions
diff --git a/subprojects/gst-plugins-bad/sys/applemedia/avfassetsrc.m b/subprojects/gst-plugins-bad/sys/applemedia/avfassetsrc.m new file mode 100644 index 0000000000..20478bda5a --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/applemedia/avfassetsrc.m @@ -0,0 +1,1109 @@ +/* + * GStreamer + * Copyright (C) 2013 Fluendo S.L. <support@fluendo.com> + * Authors: Andoni Morales Alastruey <amorales@fluendo.com> + * Copyright (C) 2014 Collabora Ltd. + * Authors: Matthieu Bouron <matthieu.bouron@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-plugin + * + * Read and decode samples from AVFoundation assets using the AVFAssetReader API + * + * ## Example launch line + * + * |[ + * gst-launch-1.0 -v -m avfassetsrc uri="file://movie.mp4" ! autovideosink + * ]| + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "avfassetsrc.h" +#include "coremediabuffer.h" + +GST_DEBUG_CATEGORY_STATIC (gst_avf_asset_src_debug); +#define GST_CAT_DEFAULT gst_avf_asset_src_debug + +#define CMTIME_TO_GST_TIME(x) \ + (x.value == 0 ? 0 : (guint64)(x.value * GST_SECOND / x.timescale)); +#define GST_AVF_ASSET_SRC_LOCK(x) (g_mutex_lock (&x->lock)); +#define GST_AVF_ASSET_SRC_UNLOCK(x) (g_mutex_unlock (&x->lock)); +#define MEDIA_TYPE_TO_STR(x) \ + (x == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO ? "audio" : "video") +#define AVF_ASSET_READER_HAS_AUDIO(x) \ + ([GST_AVF_ASSET_SRC_READER(self) hasMediaType:GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO]) +#define AVF_ASSET_READER_HAS_VIDEO(x) \ + ([GST_AVF_ASSET_SRC_READER(self) hasMediaType:GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO]) + +enum +{ + PROP_0, + PROP_URI +}; + +static GstStaticPadTemplate audio_factory = GST_STATIC_PAD_TEMPLATE ("audio", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) F32LE, " + "rate = " GST_AUDIO_RATE_RANGE ", " + "channels = (int) [1, 2], " + "layout = (string) interleaved" + ) +); + +static GstStaticPadTemplate video_factory = GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-raw, " + "format = (string) NV12, " + "framerate = " GST_VIDEO_FPS_RANGE ", " + "width = " GST_VIDEO_SIZE_RANGE ", " + "height = " GST_VIDEO_SIZE_RANGE + ) +); + +static void gst_avf_asset_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_avf_asset_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_avf_asset_src_dispose (GObject *object); + +static GstStateChangeReturn gst_avf_asset_src_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gst_avf_asset_src_query (GstPad *pad, GstObject * parent, GstQuery *query); +static gboolean gst_avf_asset_src_event (GstPad *pad, GstObject * parent, GstEvent *event); +static gboolean gst_avf_asset_src_send_event (GstAVFAssetSrc *self, + GstEvent *event); + +static void gst_avf_asset_src_read_audio (GstAVFAssetSrc *self); +static void gst_avf_asset_src_read_video (GstAVFAssetSrc *self); +static void gst_avf_asset_src_start (GstAVFAssetSrc *self); +static void gst_avf_asset_src_stop (GstAVFAssetSrc *self); +static gboolean gst_avf_asset_src_start_reading (GstAVFAssetSrc *self); +static void gst_avf_asset_src_stop_reading (GstAVFAssetSrc *self); +static void gst_avf_asset_src_stop_all (GstAVFAssetSrc *self); +static void gst_avf_asset_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +static void +_do_init (GType avf_assetsrc_type) +{ + static const GInterfaceInfo urihandler_info = { + gst_avf_asset_src_uri_handler_init, + NULL, + NULL + }; + + g_type_add_interface_static (avf_assetsrc_type, GST_TYPE_URI_HANDLER, + &urihandler_info); + GST_DEBUG_CATEGORY_INIT (gst_avf_asset_src_debug, "avfassetsrc", + 0, "avfassetsrc element"); +} + +G_DEFINE_TYPE_WITH_CODE (GstAVFAssetSrc, gst_avf_asset_src, GST_TYPE_ELEMENT, + _do_init (g_define_type_id)); + + +/* GObject vmethod implementations */ + +static void +gst_avf_asset_src_class_init (GstAVFAssetSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, + "Source and decoder for AVFoundation assets", + "Source/Codec", + "Read and decode samples from AVFoundation assets using the AVFAssetReader API", + "Andoni Morales Alastruey amorales@fluendo.com"); + + gst_element_class_add_static_pad_template (gstelement_class, &audio_factory); + gst_element_class_add_static_pad_template (gstelement_class, &video_factory); + + gobject_class->set_property = gst_avf_asset_src_set_property; + gobject_class->get_property = gst_avf_asset_src_get_property; + gobject_class->dispose = gst_avf_asset_src_dispose; + + /** + * GstAVFAssetSrc:uri + * + * URI of the asset to read + * + **/ + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "Asset URI", + "URI of the asset to read", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + + gstelement_class->change_state = gst_avf_asset_src_change_state; + +} + +static void +gst_avf_asset_src_init (GstAVFAssetSrc * self) +{ + self->selected_audio_track = 0; + self->selected_video_track = 0; + self->last_audio_pad_ret = GST_FLOW_OK; + self->last_video_pad_ret = GST_FLOW_OK; + g_mutex_init (&self->lock); +} + +static void +gst_avf_asset_src_dispose (GObject *object) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (object); + + if (self->uri != NULL) { + g_free (self->uri); + self->uri = NULL; + } + + if (self->seek_event) { + gst_event_unref (self->seek_event); + self->seek_event = NULL; + } +} + +static void +gst_avf_asset_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (object); + + switch (prop_id) { + case PROP_URI: + g_free (self->uri); + self->uri = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_avf_asset_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (object); + + switch (prop_id) { + case PROP_URI: + g_value_set_string (value, self->uri); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_avf_asset_src_change_state (GstElement * element, GstStateChange transition) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (element); + GstStateChangeReturn ret; + GError *error; + + GST_DEBUG ("%s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: { + self->state = GST_AVF_ASSET_SRC_STATE_STOPPED; + if (!self->uri) { + GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND, + ("\"uri\" property not set"), (NULL)); + gst_avf_asset_src_stop_all (self); + return GST_STATE_CHANGE_FAILURE; + } + self->reader = (__bridge_retained gpointer)([[GstAVFAssetReader alloc] initWithURI:self->uri:&error]); + if (error) { + GST_ELEMENT_ERROR (element, RESOURCE, FAILED, ("AVFAssetReader error"), + ("%s", error->message)); + g_error_free (error); + gst_avf_asset_src_stop_all (self); + return GST_STATE_CHANGE_FAILURE; + } + break; + } + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_avf_asset_src_start (self); + gst_avf_asset_src_start_reading (self); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (gst_avf_asset_src_parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_avf_asset_src_stop_reading (self); + gst_avf_asset_src_stop (self); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + CFBridgingRelease(self->reader); + break; + default: + break; + } + return ret; +} + +static GstCaps * +gst_avf_asset_src_get_caps(GstAVFAssetSrc * self, GstPad * pad, GstCaps * filter) +{ + GstCaps * caps; + + caps = gst_pad_get_current_caps (pad); + if (!caps) { + caps = gst_pad_get_pad_template_caps (pad); + } + + if (filter) { + GstCaps *intersection = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = intersection; + } + + return caps; +} + +static gboolean +gst_avf_asset_src_query (GstPad *pad, GstObject * parent, GstQuery *query) +{ + gboolean ret = FALSE; + GstCaps *caps; + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_URI: + gst_query_set_uri (query, self->uri); + ret = TRUE; + break; + case GST_QUERY_DURATION: + gst_query_set_duration (query, GST_FORMAT_TIME, GST_AVF_ASSET_SRC_READER(self).duration); + ret = TRUE; + break; + case GST_QUERY_POSITION: + gst_query_set_position (query, GST_FORMAT_TIME, GST_AVF_ASSET_SRC_READER(self).position); + ret = TRUE; + break; + case GST_QUERY_SEEKING: { + GstFormat fmt; + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + if (fmt == GST_FORMAT_TIME) { + gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, GST_AVF_ASSET_SRC_READER(self).duration); + ret = TRUE; + } + break; + } + case GST_QUERY_CAPS: { + GstCaps *filter = NULL; + gst_query_parse_caps (query, &filter); + caps = gst_avf_asset_src_get_caps (self, pad, filter); + gst_query_set_caps_result (query, caps); + ret = TRUE; + break; + } + default: + ret = FALSE; + break; + } + + return ret; +} + +static gboolean +gst_avf_asset_src_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstAVFAssetSrc *self; + gboolean res = TRUE; + GError *error = NULL; + + self = GST_AVF_ASSET_SRC (gst_pad_get_parent_element (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: { + GstFormat format; + GstSeekFlags flags; + gdouble rate; + GstSeekType start_type, stop_type; + gint64 start, stop; + GstSegment segment; + + GST_DEBUG ("Processing SEEK event"); + + GST_AVF_ASSET_SRC_LOCK (self); + if (self->seek_event && gst_event_get_seqnum (event) == + gst_event_get_seqnum (self->seek_event)) { + GST_AVF_ASSET_SRC_UNLOCK (self); + break; + } + self->seek_event = gst_event_ref (event); + GST_AVF_ASSET_SRC_UNLOCK (self); + + /* pause tasks before re-acquiring the object's lock */ + gst_avf_asset_src_stop_reading (self); + GST_AVF_ASSET_SRC_LOCK (self); + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, + &start, &stop_type, &stop); + + if (rate < 0) { + GST_WARNING ("Negative rates are not supported yet"); + GST_AVF_ASSET_SRC_UNLOCK (self); + res = FALSE; + break; + } + + if (format != GST_FORMAT_TIME || start_type == GST_SEEK_TYPE_NONE) { + GST_AVF_ASSET_SRC_UNLOCK(self); + gst_avf_asset_src_start_reading (self); + res = FALSE; + break; + } + if (stop_type == GST_SEEK_TYPE_NONE) { + stop = GST_CLOCK_TIME_NONE; + } + gst_avf_asset_src_send_event (self, gst_event_new_flush_start ()); + [GST_AVF_ASSET_SRC_READER(self) seekTo: start: stop: &error]; + + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.rate = rate; + segment.start = start; + segment.stop = stop; + segment.position = start; + + gst_avf_asset_src_send_event (self, gst_event_new_flush_stop (TRUE)); + gst_avf_asset_src_send_event (self, gst_event_new_segment (&segment)); + + if (error != NULL) { + GST_ELEMENT_ERROR (self, RESOURCE, SEEK, + ("AVFAssetReader seek failed"), ("%s", error->message)); + g_error_free(error); + res = FALSE; + } + GST_AVF_ASSET_SRC_UNLOCK (self); + gst_event_unref (event); + + /* start tasks after releasing the object's lock */ + gst_avf_asset_src_start_reading (self); + break; + } + default: + res = gst_pad_event_default (pad, parent, event); + break; + } + + gst_object_unref (self); + return res; +} + +static GstFlowReturn +gst_avf_asset_src_send_start_stream (GstAVFAssetSrc * self, GstPad * pad) +{ + GstEvent *event; + gchar *stream_id; + GstFlowReturn ret; + + stream_id = gst_pad_create_stream_id (pad, GST_ELEMENT_CAST (self), NULL); + GST_DEBUG_OBJECT (self, "Pushing STREAM START"); + event = gst_event_new_stream_start (stream_id); + gst_event_set_group_id (event, gst_util_group_id_next ()); + + ret = gst_pad_push_event (pad, event); + g_free (stream_id); + + return ret; +} + +static GstFlowReturn +gst_avf_asset_src_combine_flows (GstAVFAssetSrc * self, GstAVFAssetReaderMediaType type, + GstFlowReturn ret) +{ + gboolean has_other_pad; + GstFlowReturn last_other_pad_ret; + + GST_AVF_ASSET_SRC_LOCK (self); + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO) { + self->last_audio_pad_ret = ret; + has_other_pad = AVF_ASSET_READER_HAS_VIDEO (ret); + last_other_pad_ret = self->last_video_pad_ret; + } else if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO) { + self->last_video_pad_ret = ret; + has_other_pad = AVF_ASSET_READER_HAS_AUDIO (ret); + last_other_pad_ret = self->last_audio_pad_ret; + } else { + GST_ERROR ("Unsupported media type"); + ret = GST_FLOW_ERROR; + goto exit; + } + + if (!has_other_pad || ret != GST_FLOW_NOT_LINKED) + goto exit; + + ret = last_other_pad_ret; + +exit: + GST_AVF_ASSET_SRC_UNLOCK (self); + return ret; +} + +static void +gst_avf_asset_src_read_data (GstAVFAssetSrc *self, GstPad *pad, + GstAVFAssetReaderMediaType type) +{ + GstBuffer *buf; + GstFlowReturn ret, combined_ret; + GError *error; + + + GST_AVF_ASSET_SRC_LOCK (self); + if (self->state != GST_AVF_ASSET_SRC_STATE_READING) { + GST_AVF_ASSET_SRC_UNLOCK (self); + return; + } + + buf = [GST_AVF_ASSET_SRC_READER(self) nextBuffer:type:&error]; + GST_AVF_ASSET_SRC_UNLOCK (self); + + if (buf == NULL) { + if (error != NULL) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Error reading next buffer"), + ("%s", error->message)); + g_error_free (error); + + gst_avf_asset_src_combine_flows (self, type, GST_FLOW_ERROR); + gst_pad_pause_task (pad); + return; + } + + gst_pad_push_event (pad, gst_event_new_eos ()); + gst_avf_asset_src_combine_flows (self, type, GST_FLOW_EOS); + gst_pad_pause_task (pad); + return; + } + + ret = gst_pad_push (pad, buf); + combined_ret = gst_avf_asset_src_combine_flows (self, type, ret); + + if (ret != GST_FLOW_OK) { + GST_WARNING ("Error pushing %s buffer on pad %" GST_PTR_FORMAT + ", reason %s", MEDIA_TYPE_TO_STR (type), pad, gst_flow_get_name (ret)); + + if (ret == GST_FLOW_EOS) { + gst_pad_push_event (pad, gst_event_new_eos ()); + } + + if (combined_ret != GST_FLOW_OK) { + GST_ELEMENT_FLOW_ERROR (self, ret); + } + + gst_pad_pause_task (pad); + } + +} + +static void +gst_avf_asset_src_read_audio (GstAVFAssetSrc *self) +{ + gst_avf_asset_src_read_data (self, self->audiopad, + GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO); +} + +static void +gst_avf_asset_src_read_video (GstAVFAssetSrc *self) +{ + gst_avf_asset_src_read_data (self, self->videopad, + GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO); +} + +static gboolean +gst_avf_asset_src_start_reader (GstAVFAssetSrc * self) +{ + GError *error = NULL; + gboolean ret = TRUE; + + + [GST_AVF_ASSET_SRC_READER(self) start: &error]; + if (error != NULL) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, + ("AVFAssetReader could not start reading"), ("%s", error->message)); + g_error_free (error); + ret = FALSE; + goto exit; + } + +exit: + return ret; +} + +static gboolean +gst_avf_asset_src_send_event (GstAVFAssetSrc *self, GstEvent *event) +{ + gboolean ret = TRUE; + + + if (AVF_ASSET_READER_HAS_VIDEO (self)) { + ret |= gst_pad_push_event (self->videopad, gst_event_ref (event)); + } + if (AVF_ASSET_READER_HAS_AUDIO (self)) { + ret |= gst_pad_push_event (self->audiopad, gst_event_ref (event)); + } + + gst_event_unref (event); + return ret; +} + +static void +gst_avf_asset_src_start (GstAVFAssetSrc *self) +{ + GstSegment segment; + + if (self->state == GST_AVF_ASSET_SRC_STATE_STARTED) { + return; + } + + GST_DEBUG_OBJECT (self, "Creating pads and starting reader"); + + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.duration = GST_AVF_ASSET_SRC_READER(self).duration; + + /* We call AVFAssetReader's startReading when the pads are linked + * and no outputs can be added afterwards, so the tracks must be + * selected before adding any of the new pads */ + if (AVF_ASSET_READER_HAS_AUDIO (self)) { + [GST_AVF_ASSET_SRC_READER(self) selectTrack: GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO: + self->selected_audio_track]; + } + if (AVF_ASSET_READER_HAS_VIDEO (self)) { + [GST_AVF_ASSET_SRC_READER(self) selectTrack: GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO: + self->selected_video_track]; + } + + if (AVF_ASSET_READER_HAS_AUDIO (self)) { + self->audiopad = gst_pad_new_from_static_template (&audio_factory, "audio"); + gst_pad_set_query_function (self->audiopad, + gst_avf_asset_src_query); + gst_pad_set_event_function(self->audiopad, + gst_avf_asset_src_event); + gst_pad_use_fixed_caps (self->audiopad); + gst_pad_set_active (self->audiopad, TRUE); + gst_avf_asset_src_send_start_stream (self, self->audiopad); + gst_pad_set_caps (self->audiopad, + [GST_AVF_ASSET_SRC_READER(self) getCaps: GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO]); + gst_pad_push_event (self->audiopad, gst_event_new_caps ( + [GST_AVF_ASSET_SRC_READER(self) getCaps: GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO])); + gst_pad_push_event (self->audiopad, gst_event_new_segment (&segment)); + gst_element_add_pad (GST_ELEMENT (self), self->audiopad); + } + if (AVF_ASSET_READER_HAS_VIDEO (self)) { + self->videopad = gst_pad_new_from_static_template (&video_factory, "video"); + gst_pad_set_query_function (self->videopad, + gst_avf_asset_src_query); + gst_pad_set_event_function(self->videopad, + gst_avf_asset_src_event); + gst_pad_use_fixed_caps (self->videopad); + gst_pad_set_active (self->videopad, TRUE); + gst_avf_asset_src_send_start_stream (self, self->videopad); + gst_pad_set_caps (self->videopad, + [GST_AVF_ASSET_SRC_READER(self) getCaps: GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO]); + gst_pad_push_event (self->videopad, gst_event_new_caps ( + [GST_AVF_ASSET_SRC_READER(self) getCaps: GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO])); + gst_pad_push_event (self->videopad, gst_event_new_segment (&segment)); + gst_element_add_pad (GST_ELEMENT (self), self->videopad); + } + gst_element_no_more_pads (GST_ELEMENT (self)); + + self->state = GST_AVF_ASSET_SRC_STATE_STARTED; +} + +static void +gst_avf_asset_src_stop (GstAVFAssetSrc *self) +{ + gboolean has_audio, has_video; + + if (self->state == GST_AVF_ASSET_SRC_STATE_STOPPED) { + return; + } + + GST_DEBUG ("Stopping tasks and removing pads"); + + has_audio = AVF_ASSET_READER_HAS_AUDIO (self); + has_video = AVF_ASSET_READER_HAS_VIDEO (self); + [GST_AVF_ASSET_SRC_READER(self) stop]; + + if (has_audio) { + gst_pad_stop_task (self->audiopad); + gst_element_remove_pad (GST_ELEMENT (self), self->audiopad); + } + if (has_video) { + gst_pad_stop_task (self->videopad); + gst_element_remove_pad (GST_ELEMENT (self), self->videopad); + } + + self->state = GST_AVF_ASSET_SRC_STATE_STOPPED; +} + +static gboolean +gst_avf_asset_src_start_reading (GstAVFAssetSrc *self) +{ + gboolean ret = TRUE; + + if (self->state != GST_AVF_ASSET_SRC_STATE_STARTED) { + goto exit; + } + + GST_DEBUG_OBJECT (self, "Start reading"); + + if ((ret = gst_avf_asset_src_start_reader (self)) != TRUE) { + goto exit; + } + + if (AVF_ASSET_READER_HAS_AUDIO (self)) { + ret = gst_pad_start_task (self->audiopad, (GstTaskFunction)gst_avf_asset_src_read_audio, self, NULL); + if (!ret) { + GST_ERROR ("Failed to start audio task"); + goto exit; + } + } + + if (AVF_ASSET_READER_HAS_VIDEO (self)) { + ret = gst_pad_start_task (self->videopad, (GstTaskFunction)gst_avf_asset_src_read_video, self, NULL); + if (!ret) { + GST_ERROR ("Failed to start video task"); + goto exit; + } + } + + self->state = GST_AVF_ASSET_SRC_STATE_READING; + +exit: + return ret; +} + +static void +gst_avf_asset_src_stop_reading (GstAVFAssetSrc * self) +{ + if (self->state != GST_AVF_ASSET_SRC_STATE_READING) { + return; + } + + GST_DEBUG_OBJECT (self, "Stop reading"); + + if (AVF_ASSET_READER_HAS_AUDIO (self)) { + gst_pad_pause_task (self->audiopad); + } + if (AVF_ASSET_READER_HAS_VIDEO (self)) { + gst_pad_pause_task (self->videopad); + } + + self->state = GST_AVF_ASSET_SRC_STATE_STARTED; +} + +static void +gst_avf_asset_src_stop_all (GstAVFAssetSrc *self) +{ + GST_AVF_ASSET_SRC_LOCK (self); + gst_avf_asset_src_stop_reading (self); + gst_avf_asset_src_stop (self); + GST_AVF_ASSET_SRC_UNLOCK (self); +} + +static GQuark +gst_avf_asset_src_error_quark (void) +{ + static GQuark q; /* 0 */ + + if (G_UNLIKELY (q == 0)) { + q = g_quark_from_static_string ("avfasset-src-error-quark"); + } + return q; +} + +static GstURIType +gst_avf_asset_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar * const * +gst_avf_asset_src_uri_get_protocols (GType type) +{ + static const gchar * const protocols[] = { "file", "ipod-library", NULL }; + + return protocols; +} + +static gchar * +gst_avf_asset_src_uri_get_uri (GstURIHandler * handler) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (handler); + + return g_strdup (self->uri); +} + +static gboolean +gst_avf_asset_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, GError **error) +{ + GstAVFAssetSrc *self = GST_AVF_ASSET_SRC (handler); + NSString *str; + NSURL *url; + AVAsset *asset; + gboolean ret = FALSE; + + str = [NSString stringWithUTF8String: uri]; + url = [[NSURL alloc] initWithString: str]; + asset = [AVAsset assetWithURL: url]; + + if (asset.playable) { + ret = TRUE; + g_free (self->uri); + self->uri = g_strdup (uri); + } else { + g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "Invalid URI '%s' for avfassetsrc", uri); + } + return ret; +} + +static void +gst_avf_asset_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_avf_asset_src_uri_get_type; + iface->get_protocols = gst_avf_asset_src_uri_get_protocols; + iface->get_uri = gst_avf_asset_src_uri_get_uri; + iface->set_uri = gst_avf_asset_src_uri_set_uri; +} + +@implementation GstAVFAssetReader + +@synthesize duration; +@synthesize position; + +- (NSDictionary *) capsToAudioSettings +{ + gint depth; + gboolean isFloat; + GstAudioInfo info; + + if (!gst_caps_is_fixed (audio_caps)) + return NULL; + + gst_audio_info_from_caps (&info, audio_caps); + isFloat = GST_AUDIO_INFO_IS_FLOAT(&info); + depth = GST_AUDIO_INFO_DEPTH(&info); + + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, + [NSNumber numberWithFloat:info.rate], AVSampleRateKey, + [NSNumber numberWithInt:info.channels], AVNumberOfChannelsKey, + //[NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], + //AVChannelLayoutKey, + [NSNumber numberWithInt:depth], AVLinearPCMBitDepthKey, + [NSNumber numberWithBool:isFloat],AVLinearPCMIsFloatKey, + [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, + [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, + nil]; +} + +- (void) releaseReader +{ + video_track = nil; + audio_track = nil; + video_tracks = nil; + audio_tracks = nil; + reader = nil; +} + +- (void) initReader: (GError **) error +{ + NSError *nserror; + + reader = [[AVAssetReader alloc] initWithAsset:asset error:&nserror]; + if (nserror != NULL) { + GST_ERROR ("Error initializing reader: %s", + [nserror.description UTF8String]); + *error = g_error_new (GST_AVF_ASSET_SRC_ERROR, GST_AVF_ASSET_ERROR_INIT, "%s", + [nserror.description UTF8String]); + + return; + } + + audio_tracks = [asset tracksWithMediaType:AVMediaTypeAudio]; + video_tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + reader.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration); + GST_INFO ("Found %lu video tracks and %lu audio tracks", + (unsigned long)[video_tracks count], (unsigned long)[audio_tracks count]); +} + +- (id) initWithURI:(gchar*)uri : (GError **)error; +{ + NSString *str; + NSURL *url; + + GST_INFO ("Initializing AVFAssetReader with uri: %s", uri); + *error = NULL; + + str = [NSString stringWithUTF8String: uri]; + url = [[NSURL alloc] initWithString: str]; + asset = [AVAsset assetWithURL: url]; + + if (!asset.playable) { + *error = g_error_new (GST_AVF_ASSET_SRC_ERROR, GST_AVF_ASSET_ERROR_NOT_PLAYABLE, + "Media is not playable"); + asset = nil; + return nil; + } + + selected_audio_track = -1; + selected_video_track = -1; + reading = FALSE; + position = 0; + duration = CMTIME_TO_GST_TIME (asset.duration); + + /* FIXME: use fixed caps here until we found a way to determine + * the native audio format */ + audio_caps = gst_caps_from_string ("audio/x-raw, " + "format=F32LE, rate=44100, channels=2, layout=interleaved"); + + [self initReader: error]; + if (*error) { + return nil; + } + + self = [super init]; + return self; +} + +- (BOOL) selectTrack: (GstAVFAssetReaderMediaType) type : (gint) index +{ + NSArray *tracks; + AVAssetTrack *track; + AVAssetReaderOutput * __strong *output; + NSDictionary *settings; + NSString *mediaType; + gint *selected_track; + + GST_INFO ("Selecting %s track %d", MEDIA_TYPE_TO_STR(type), index); + + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO) { + mediaType = AVMediaTypeAudio; + selected_track = &selected_audio_track; + output = &audio_track; + settings = [self capsToAudioSettings]; + } else if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO) { + mediaType = AVMediaTypeVideo; + selected_track = &selected_video_track; + output = &video_track; + settings = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: + kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], + kCVPixelBufferPixelFormatTypeKey, nil]; + } else { + return FALSE; + } + + tracks = [asset tracksWithMediaType:mediaType]; + if ([tracks count] == 0 || [tracks count] < index + 1) { + return FALSE; + } + + track = [tracks objectAtIndex:index]; + *selected_track = index; + *output = [AVAssetReaderTrackOutput + assetReaderTrackOutputWithTrack:track + outputSettings:settings]; + [reader addOutput:*output]; + return TRUE; +} + +- (void) start: (GError **)error +{ + if (reading) + return; + + if (![reader startReading]) { + *error = g_error_new (GST_AVF_ASSET_SRC_ERROR, GST_AVF_ASSET_ERROR_START, + "%s", [reader.error.description UTF8String]); + reading = FALSE; + return; + } + reading = TRUE; +} + +- (void) stop +{ + [reader cancelReading]; + reading = FALSE; +} + +- (BOOL) hasMediaType: (GstAVFAssetReaderMediaType) type +{ + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO) { + return [audio_tracks count] != 0; + } + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO) { + return [video_tracks count] != 0; + } + return FALSE; +} + +- (void) seekTo: (guint64) startTS : (guint64) stopTS : (GError **) error +{ + CMTime startTime = kCMTimeZero, stopTime = kCMTimePositiveInfinity; + + if (startTS != GST_CLOCK_TIME_NONE) { + startTime = CMTimeMake (startTS, GST_SECOND); + } + if (stopTS != GST_CLOCK_TIME_NONE) { + stopTime = CMTimeMake (stopTS, GST_SECOND); + } + + /* AVFAssetReader needs to be recreated before changing the + * timerange property */ + [self stop]; + [self releaseReader]; + [self initReader: error]; + if (*error) { + return; + } + + GST_DEBUG ("Seeking to start:%" GST_TIME_FORMAT " stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS(startTS), GST_TIME_ARGS(stopTS)); + + reader.timeRange = CMTimeRangeMake(startTime, stopTime); + [self selectTrack: GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO:selected_audio_track]; + [self selectTrack: GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO:selected_video_track]; + [self start: error]; +} + +- (GstBuffer *) nextBuffer: (GstAVFAssetReaderMediaType) type : (GError **) error +{ + CMSampleBufferRef cmbuf; + AVAssetReaderTrackOutput *areader = NULL; + GstCaps *caps; + GstBuffer *buf; + CMTime dur, ts; + + GST_LOG ("Reading %s next buffer", MEDIA_TYPE_TO_STR (type)); + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO && audio_track != NULL) { + areader = audio_track; + caps = audio_caps; + } else if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO && + video_track != NULL) { + areader = video_track; + caps = video_caps; + } + + if (areader == NULL) { + return NULL; + } + + *error = NULL; + cmbuf = [areader copyNextSampleBuffer]; + if (cmbuf == NULL) { + if (reader.error != NULL) { + *error = g_error_new (GST_AVF_ASSET_SRC_ERROR, GST_AVF_ASSET_ERROR_READ, + "%s", [reader.error.description UTF8String]); + } + /* EOS */ + return NULL; + } + + buf = gst_core_media_buffer_new (cmbuf, FALSE, NULL); + CFRelease (cmbuf); + if (buf == NULL) + return NULL; + /* cmbuf is now retained by buf (in meta) */ + dur = CMSampleBufferGetDuration (cmbuf); + ts = CMSampleBufferGetPresentationTimeStamp (cmbuf); + if (dur.value != 0) { + GST_BUFFER_DURATION (buf) = CMTIME_TO_GST_TIME (dur); + } + GST_BUFFER_TIMESTAMP (buf) = CMTIME_TO_GST_TIME (ts); + GST_LOG ("Copying next %s buffer ts:%" GST_TIME_FORMAT " dur:%" + GST_TIME_FORMAT, MEDIA_TYPE_TO_STR (type), + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS(GST_BUFFER_DURATION (buf))); + if (GST_BUFFER_TIMESTAMP (buf) > position) { + position = GST_BUFFER_TIMESTAMP (buf); + } + return buf; +} + +- (GstCaps *) getCaps: (GstAVFAssetReaderMediaType) type +{ + GstCaps *caps = NULL; + AVAssetTrack *track; + + if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_AUDIO) { + caps = gst_caps_ref (audio_caps); + GST_INFO ("Using audio caps: %" GST_PTR_FORMAT, caps); + } else if (type == GST_AVF_ASSET_READER_MEDIA_TYPE_VIDEO) { + gint fr_n, fr_d; + + track = [video_tracks objectAtIndex: selected_video_track]; + gst_util_double_to_fraction(track.nominalFrameRate, &fr_n, &fr_d); + caps = gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "NV12", + "width", G_TYPE_INT, (int) track.naturalSize.width, + "height", G_TYPE_INT, (int) track.naturalSize.height, + "framerate", GST_TYPE_FRACTION, fr_n, fr_d, NULL); + GST_INFO ("Using video caps: %" GST_PTR_FORMAT, caps); + video_caps = gst_caps_ref (caps); + } + + return caps; +} + +- (void) dealloc +{ + asset = nil; + [self releaseReader]; + + if (audio_caps != NULL) { + gst_caps_unref (audio_caps); + } + + if (video_caps != NULL) { + gst_caps_unref (audio_caps); + } +} + +@end |