From 3d68879247868c1878626cf1205e2f6f5e721fd5 Mon Sep 17 00:00:00 2001 From: gambas Date: Mon, 25 Mar 2019 03:57:02 +0100 Subject: [PATCH] Support for playing speed rate and step-by-step forward. [GB.MEDIA] * NEW: MediaPipeline: Pos is a new synonymous for the Position property. * NEW: MediaPipeline: Position is a new event that is raised when the media position has changed. * NEW: MediaPipeline: Seek() is a new method that allows to move the media to a specific position while specifying GStreamer seek flags. * NEW: MediaPipeline: Speed is a new property that allows to define the media playing speed rate. A negative speed will play backward. * NEW: MediaPipeline: Forward() is a new method that allows to move forward a specific amount of frames. Moving backward does not work at the moment. * NEW: Media: Add MediaPipeline.Seek() seek constants. --- gb.media/src/c_media.c | 210 +++++++++++++++++++++++++++++++++++------ gb.media/src/c_media.h | 2 + 2 files changed, 182 insertions(+), 30 deletions(-) diff --git a/gb.media/src/c_media.c b/gb.media/src/c_media.c index ae73e6599..ee447bf5c 100644 --- a/gb.media/src/c_media.c +++ b/gb.media/src/c_media.c @@ -633,6 +633,100 @@ static GB_IMG *get_last_image(void *_object) return MEDIA_get_image_from_sample(sample, TRUE); } +static GstIteratorResult iterator_next_pad(GstIterator *iter, GstPad **pad) +{ + GstIteratorResult ret; + GValue value = G_VALUE_INIT; + + ret = gst_iterator_next(iter, &value); + if (ret == GST_ITERATOR_OK) + { + if (G_VALUE_HOLDS_BOXED(&value)) + *pad = g_value_get_boxed(&value); + else + *pad = (GstPad *)g_value_get_object(&value); + } + + return ret; +} + +static GstElement *find_sink(GstElement *pipeline) +{ + int i; + GstIterator *iter; + GstElement *element; + GstPad *pad; + bool done; + bool got_pad; + + for (i = 0; i < gst_child_proxy_get_children_count(GST_CHILD_PROXY(pipeline)); i++) + { + element = (GstElement *)gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(pipeline), i); + + iter = gst_element_iterate_src_pads(element); + + done = FALSE; + got_pad = FALSE; + while (!done) + { + switch (iterator_next_pad(iter, &pad)) + { + case GST_ITERATOR_OK: + gst_object_unref(pad); + got_pad = TRUE; + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync(iter); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + + gst_iterator_free(iter); + + if (!got_pad) + return element; + + gst_object_unref(element); + } + + GB.Error("Unable to find sink"); + return NULL; +} + +static void set_pipeline_rate(void *_object) +{ + gint64 pos; + GstElement *sink; + double rate; + + if (THIS->state != GST_STATE_PLAYING && THIS->state != GST_STATE_PAUSED) + return; + + rate = THIS_PIPELINE->next_rate; + if (rate == THIS_PIPELINE->rate) + return; + + sink = find_sink(ELEMENT); + if (!sink) + return; + + gst_element_query_position(ELEMENT, GST_FORMAT_TIME, &pos); + + if (rate > 0) + gst_element_seek(sink, rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_END, 0); + else + gst_element_seek(sink, rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, pos); + + gst_object_unref(sink); + + THIS_PIPELINE->rate = THIS_PIPELINE->next_rate; +} + #if 0 //---- MediaSignalArguments ----------------------------------------------- @@ -1179,23 +1273,6 @@ BEGIN_METHOD(MediaControl_LinkLaterTo, GB_OBJECT dest) END_METHOD -static GstIteratorResult iterator_next_pad(GstIterator *iter, GstPad **pad) -{ - GstIteratorResult ret; - GValue value = G_VALUE_INIT; - - ret = gst_iterator_next(iter, &value); - if (ret == GST_ITERATOR_OK) - { - if (G_VALUE_HOLDS_BOXED(&value)) - *pad = g_value_get_boxed(&value); - else - *pad = (GstPad *)g_value_get_object(&value); - } - - return ret; -} - static void fill_pad_list(GB_ARRAY array, GstIterator *iter) { bool done = FALSE; @@ -1553,6 +1630,7 @@ DECLARE_EVENT(EVENT_Tag); DECLARE_EVENT(EVENT_Event); DECLARE_EVENT(EVENT_Buffering); DECLARE_EVENT(EVENT_Duration); +DECLARE_EVENT(EVENT_Position); DECLARE_EVENT(EVENT_Progress); DECLARE_EVENT(EVENT_AboutToFinish); @@ -1678,7 +1756,7 @@ static int cb_message(CMEDIAPIPELINE *_object) break; } case GST_MESSAGE_BUFFERING: GB.Raise(THIS, EVENT_Buffering, 0); break; - case GST_MESSAGE_DURATION: GB.Raise(THIS, EVENT_Duration, 0); break; + case GST_MESSAGE_DURATION_CHANGED: GB.Raise(THIS, EVENT_Duration, 0); break; case GST_MESSAGE_PROGRESS: GB.Raise(THIS, EVENT_Progress, 0); break; case GST_MESSAGE_STREAM_START: @@ -1699,16 +1777,21 @@ static int cb_message(CMEDIAPIPELINE *_object) THIS_PIPELINE->in_message = FALSE; - if (THIS->state == GST_STATE_PLAYING && !THIS->error) + if ((THIS->state == GST_STATE_PLAYING || THIS->state == GST_STATE_PAUSED) && !THIS->error) { - gst_element_query_position(ELEMENT, GST_FORMAT_TIME, &THIS_PIPELINE->pos); - //fprintf(stderr, "%" PRId64 " / %" PRId64 "\n", THIS_PIPELINE->pos, THIS_PIPELINE->duration); - if (!THIS_PIPELINE->about_to_finish && THIS_PIPELINE->pos > (THIS_PIPELINE->duration - 2000000000)) + gint64 pos; + gst_element_query_position(ELEMENT, GST_FORMAT_TIME, &pos); + if (pos >= 0 && pos != THIS_PIPELINE->pos) { - THIS_PIPELINE->about_to_finish = TRUE; - GB.Raise(THIS, EVENT_AboutToFinish, 0); + THIS_PIPELINE->pos = pos; + GB.Raise(THIS, EVENT_Position, 0); + //fprintf(stderr, "%" PRId64 " / %" PRId64 "\n", THIS_PIPELINE->pos, THIS_PIPELINE->duration); + if (!THIS_PIPELINE->about_to_finish && THIS_PIPELINE->pos > (THIS_PIPELINE->duration - 2000000000)) + { + THIS_PIPELINE->about_to_finish = TRUE; + GB.Raise(THIS, EVENT_AboutToFinish, 0); + } } - } return FALSE; @@ -1757,6 +1840,8 @@ BEGIN_METHOD(MediaPipeline_new, GB_INTEGER polling) THIS_PIPELINE->polling = polling; THIS_PIPELINE->watch = GB.Every(polling, (GB_TIMER_CALLBACK)cb_message, (intptr_t)THIS); } + + THIS_PIPELINE->rate = THIS_PIPELINE->next_rate = 1.0; END_METHOD @@ -1776,6 +1861,7 @@ BEGIN_METHOD_VOID(MediaPipeline_Play) THIS->eos = FALSE; MEDIA_set_state(THIS, GST_STATE_PLAYING, TRUE); cb_message(THIS_PIPELINE); + set_pipeline_rate(THIS); END_METHOD @@ -1802,6 +1888,24 @@ BEGIN_METHOD_VOID(MediaPipeline_Pause) END_METHOD +static void pipeline_seek(void *_object, gint64 pos, GstSeekFlags flags) +{ + if (pos < 0) + pos = 0; + + gst_element_seek(ELEMENT, THIS_PIPELINE->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | flags, GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_NONE, 0); + cb_message(THIS_PIPELINE); +} + +BEGIN_METHOD(MediaPipeline_Seek, GB_FLOAT position; GB_INTEGER flag) + + gint64 pos = VARG(position) * 1E9; + GstSeekFlags flags = (GstSeekFlags)VARGOPT(flag, GST_SEEK_FLAG_NONE); + + pipeline_seek(THIS, pos, flags); + +END_METHOD + BEGIN_PROPERTY(MediaPipeline_Position) if (READ_PROPERTY) @@ -1811,11 +1915,7 @@ BEGIN_PROPERTY(MediaPipeline_Position) else { gint64 pos = VPROP(GB_FLOAT) * 1E9; - - if (pos < 0) - pos = 0; - - gst_element_seek_simple(ELEMENT, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, (guint64)pos); + pipeline_seek(THIS, pos, GST_SEEK_FLAG_ACCURATE); } END_PROPERTY @@ -1826,6 +1926,41 @@ BEGIN_PROPERTY(MediaPipeline_Duration) END_PROPERTY +BEGIN_PROPERTY(MediaPipeline_Speed) + + if (READ_PROPERTY) + GB.ReturnFloat(THIS_PIPELINE->rate); + else + { + double rate = VPROP(GB_FLOAT); + if (fabs(rate) <= 1E-6) + { + GB.Error(GB_ERR_ARG); + return; + } + + THIS_PIPELINE->next_rate = rate; + set_pipeline_rate(THIS); + } + +END_PROPERTY + +BEGIN_METHOD(MediaPipeline_Forward, GB_INTEGER count) + + GstElement *sink; + int count = VARGOPT(count, 1); + + if (count <= 0) + return; + + sink = find_sink(ELEMENT); + if (!sink) + return; + + gst_element_send_event(sink, gst_event_new_step(GST_FORMAT_BUFFERS, fabs(count), 1.0, TRUE, FALSE)); + +END_METHOD + //---- Media -------------------------------------------------------------- BEGIN_METHOD(Media_Link, GB_OBJECT controls) @@ -2035,12 +2170,17 @@ GB_DESC MediaPipelineDesc[] = GB_PROPERTY("Position", "f", MediaPipeline_Position), GB_PROPERTY_READ("Duration", "f", MediaPipeline_Duration), + GB_PROPERTY("Pos", "f", MediaPipeline_Position), GB_PROPERTY_READ("Length", "f", MediaPipeline_Duration), GB_METHOD("Play", NULL, MediaPipeline_Play, NULL), GB_METHOD("Stop", NULL, MediaPipeline_Stop, NULL), GB_METHOD("Pause", NULL, MediaPipeline_Pause, NULL), GB_METHOD("Close", NULL, MediaPipeline_Close, NULL), + + GB_METHOD("Seek", NULL, MediaPipeline_Seek, "(Position)f[(Flags)i]"), + GB_PROPERTY("Speed", "f", MediaPipeline_Speed), + GB_METHOD("Forward", NULL, MediaPipeline_Forward, "[(Frames)i]"), GB_EVENT("Start", NULL, NULL, &EVENT_Start), GB_EVENT("End", NULL, NULL, &EVENT_End), @@ -2049,6 +2189,7 @@ GB_DESC MediaPipelineDesc[] = GB_EVENT("Event", NULL, "(Message)MediaMessage;", &EVENT_Event), GB_EVENT("Buffering", NULL, NULL, &EVENT_Buffering), GB_EVENT("Duration", NULL, NULL, &EVENT_Duration), + GB_EVENT("Position", NULL, NULL, &EVENT_Position), GB_EVENT("Progress", NULL, NULL, &EVENT_Progress), GB_EVENT("AboutToFinish", NULL, NULL, &EVENT_AboutToFinish), @@ -2070,6 +2211,15 @@ GB_DESC MediaDesc[] = GB_CONSTANT("Warning", "i", 1), GB_CONSTANT("Error", "i", 2), + GB_CONSTANT("SeekAccurate", "i", GST_SEEK_FLAG_ACCURATE), + GB_CONSTANT("SeekKeyUnit", "i", GST_SEEK_FLAG_KEY_UNIT), + GB_CONSTANT("SeekTrickMode", "i", GST_SEEK_FLAG_TRICKMODE), + GB_CONSTANT("SeekSnapBefore", "i", GST_SEEK_FLAG_SNAP_BEFORE), + GB_CONSTANT("SeekSnapAfter", "i", GST_SEEK_FLAG_SNAP_AFTER), + GB_CONSTANT("SeekSnapNearest", "i", GST_SEEK_FLAG_SNAP_NEAREST), + GB_CONSTANT("SeekTrickModeKeyUnit", "i", GST_SEEK_FLAG_TRICKMODE_KEY_UNITS), + GB_CONSTANT("SeekTrickModeNoAudio", "i", GST_SEEK_FLAG_TRICKMODE_NO_AUDIO), + GB_STATIC_METHOD("Link", NULL, Media_Link, "(FirstControl)MediaControl;(SecondControl)MediaControl;."), GB_STATIC_METHOD("Time", "l", Media_Time, "(Seconds)f"), GB_STATIC_METHOD("URL", "s", Media_URL, "(Path)s"), diff --git a/gb.media/src/c_media.h b/gb.media/src/c_media.h index c5731263d..459a26e18 100644 --- a/gb.media/src/c_media.h +++ b/gb.media/src/c_media.h @@ -87,6 +87,8 @@ typedef int polling; gint64 pos; gint64 duration; + double rate; + double next_rate; unsigned in_message : 1; unsigned about_to_finish : 1; }