/* * GStreamer * Copyright (C) 2015 Vivia Nikolaidou * * Based on gstlevel.c: * Copyright (C) 2000,2001,2002,2003,2005 * Thomas Vander Stichele * * 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:element-videoframe-audiolevel * * This element acts like a synchronized audio/video "level". It gathers * all audio buffers sent between two video frames, and then sends a message * that contains the RMS value of all samples for these buffers. * * * Example launch line * |[ * gst-launch-1.0 -m filesrc location="file.mkv" ! decodebin name=d ! "audio/x-raw" ! videoframe-audiolevel name=l ! autoaudiosink d. ! "video/x-raw" ! l. l. ! queue ! autovideosink ]| * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* FIXME 2.0: suppress warnings for deprecated API such as GValueArray * with newer GLib versions (>= 2.31.0) */ #define GLIB_DISABLE_DEPRECATION_WARNINGS #include "gstvideoframe-audiolevel.h" #include #define GST_CAT_DEFAULT gst_videoframe_audiolevel_debug #if G_BYTE_ORDER == G_LITTLE_ENDIAN # define FORMATS "{ S8, S16LE, S32LE, F32LE, F64LE }" #else # define FORMATS "{ S8, S16BE, S32BE, F32BE, F64BE }" #endif GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); static GstStaticPadTemplate audio_sink_template = GST_STATIC_PAD_TEMPLATE ("asink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (FORMATS)) ); static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE ("asrc", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (FORMATS)) ); static GstStaticPadTemplate video_sink_template = GST_STATIC_PAD_TEMPLATE ("vsink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw") ); static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("vsrc", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw") ); #define parent_class gst_videoframe_audiolevel_parent_class G_DEFINE_TYPE (GstVideoFrameAudioLevel, gst_videoframe_audiolevel, GST_TYPE_ELEMENT); static GstFlowReturn gst_videoframe_audiolevel_asink_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf); static GstFlowReturn gst_videoframe_audiolevel_vsink_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf); static gboolean gst_videoframe_audiolevel_asink_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_videoframe_audiolevel_vsink_event (GstPad * pad, GstObject * parent, GstEvent * event); static GstIterator *gst_videoframe_audiolevel_iterate_internal_links (GstPad * pad, GstObject * parent); static void gst_videoframe_audiolevel_finalize (GObject * gobject); static GstStateChangeReturn gst_videoframe_audiolevel_change_state (GstElement * element, GstStateChange transition); static void gst_videoframe_audiolevel_class_init (GstVideoFrameAudioLevelClass * klass) { GstElementClass *gstelement_class; GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (gst_videoframe_audiolevel_debug, "videoframe-audiolevel", 0, "Synchronized audio/video level"); gstelement_class = (GstElementClass *) klass; gst_element_class_set_static_metadata (gstelement_class, "Video-frame audio level", "Filter/Analyzer/Audio", "Synchronized audio/video RMS Level messenger for audio/raw", "Vivia Nikolaidou "); gobject_class->finalize = gst_videoframe_audiolevel_finalize; gstelement_class->change_state = gst_videoframe_audiolevel_change_state; gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&audio_src_template)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&audio_sink_template)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&video_src_template)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&video_sink_template)); } static void gst_videoframe_audiolevel_init (GstVideoFrameAudioLevel * self) { self->asinkpad = gst_pad_new_from_static_template (&audio_sink_template, "asink"); gst_pad_set_chain_function (self->asinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_asink_chain)); gst_pad_set_event_function (self->asinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_asink_event)); gst_pad_set_iterate_internal_links_function (self->asinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_iterate_internal_links)); gst_element_add_pad (GST_ELEMENT (self), self->asinkpad); self->vsinkpad = gst_pad_new_from_static_template (&video_sink_template, "vsink"); gst_pad_set_chain_function (self->vsinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_vsink_chain)); gst_pad_set_event_function (self->vsinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_vsink_event)); gst_pad_set_iterate_internal_links_function (self->vsinkpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_iterate_internal_links)); gst_element_add_pad (GST_ELEMENT (self), self->vsinkpad); self->asrcpad = gst_pad_new_from_static_template (&audio_src_template, "asrc"); gst_pad_set_iterate_internal_links_function (self->asrcpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_iterate_internal_links)); gst_element_add_pad (GST_ELEMENT (self), self->asrcpad); self->vsrcpad = gst_pad_new_from_static_template (&video_src_template, "vsrc"); gst_pad_set_iterate_internal_links_function (self->vsrcpad, GST_DEBUG_FUNCPTR (gst_videoframe_audiolevel_iterate_internal_links)); gst_element_add_pad (GST_ELEMENT (self), self->vsrcpad); GST_PAD_SET_PROXY_CAPS (self->asinkpad); GST_PAD_SET_PROXY_ALLOCATION (self->asinkpad); GST_PAD_SET_PROXY_CAPS (self->asrcpad); GST_PAD_SET_PROXY_SCHEDULING (self->asrcpad); GST_PAD_SET_PROXY_CAPS (self->vsinkpad); GST_PAD_SET_PROXY_ALLOCATION (self->vsinkpad); GST_PAD_SET_PROXY_CAPS (self->vsrcpad); GST_PAD_SET_PROXY_SCHEDULING (self->vsrcpad); self->adapter = gst_adapter_new (); g_queue_init (&self->vtimeq); self->first_time = GST_CLOCK_TIME_NONE; self->total_frames = 0; /* alignment_threshold and discont_wait should become properties if needed */ self->alignment_threshold = 40 * GST_MSECOND; self->discont_time = GST_CLOCK_TIME_NONE; self->next_offset = -1; self->discont_wait = 1 * GST_SECOND; self->video_eos_flag = FALSE; self->audio_flush_flag = FALSE; self->shutdown_flag = FALSE; g_mutex_init (&self->mutex); g_cond_init (&self->cond); } static GstStateChangeReturn gst_videoframe_audiolevel_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (element); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: g_mutex_lock (&self->mutex); self->shutdown_flag = TRUE; g_cond_signal (&self->cond); g_mutex_unlock (&self->mutex); break; case GST_STATE_CHANGE_READY_TO_PAUSED: g_mutex_lock (&self->mutex); self->shutdown_flag = FALSE; self->video_eos_flag = FALSE; self->audio_flush_flag = FALSE; g_mutex_unlock (&self->mutex); default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: g_mutex_lock (&self->mutex); self->first_time = GST_CLOCK_TIME_NONE; self->total_frames = 0; gst_segment_init (&self->asegment, GST_FORMAT_UNDEFINED); gst_segment_init (&self->vsegment, GST_FORMAT_UNDEFINED); self->vsegment.position = GST_CLOCK_TIME_NONE; gst_adapter_clear (self->adapter); g_queue_foreach (&self->vtimeq, (GFunc) g_free, NULL); g_queue_clear (&self->vtimeq); if (self->CS) { g_free (self->CS); self->CS = NULL; } g_mutex_unlock (&self->mutex); break; default: break; } return ret; } static void gst_videoframe_audiolevel_finalize (GObject * object) { GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (object); if (self->adapter) { g_object_unref (self->adapter); self->adapter = NULL; } g_queue_foreach (&self->vtimeq, (GFunc) g_free, NULL); g_queue_clear (&self->vtimeq); self->first_time = GST_CLOCK_TIME_NONE; self->total_frames = 0; if (self->CS) { g_free (self->CS); self->CS = NULL; } g_mutex_clear (&self->mutex); g_cond_clear (&self->cond); G_OBJECT_CLASS (parent_class)->finalize (object); } #define DEFINE_INT_LEVEL_CALCULATOR(TYPE, RESOLUTION) \ static void inline \ gst_videoframe_audiolevel_calculate_##TYPE (gpointer data, guint num, guint channels, \ gdouble *NCS) \ { \ TYPE * in = (TYPE *)data; \ register guint j; \ gdouble squaresum = 0.0; /* square sum of the input samples */ \ register gdouble square = 0.0; /* Square */ \ gdouble normalizer; /* divisor to get a [-1.0, 1.0] range */ \ \ /* *NCS = 0.0; Normalized Cumulative Square */ \ \ for (j = 0; j < num; j += channels) { \ square = ((gdouble) in[j]) * in[j]; \ squaresum += square; \ } \ \ normalizer = (gdouble) (G_GINT64_CONSTANT(1) << (RESOLUTION * 2)); \ *NCS = squaresum / normalizer; \ } DEFINE_INT_LEVEL_CALCULATOR (gint32, 31); DEFINE_INT_LEVEL_CALCULATOR (gint16, 15); DEFINE_INT_LEVEL_CALCULATOR (gint8, 7); #define DEFINE_FLOAT_LEVEL_CALCULATOR(TYPE) \ static void inline \ gst_videoframe_audiolevel_calculate_##TYPE (gpointer data, guint num, guint channels, \ gdouble *NCS) \ { \ TYPE * in = (TYPE *)data; \ register guint j; \ gdouble squaresum = 0.0; /* square sum of the input samples */ \ register gdouble square = 0.0; /* Square */ \ \ /* *NCS = 0.0; Normalized Cumulative Square */ \ \ for (j = 0; j < num; j += channels) { \ square = ((gdouble) in[j]) * in[j]; \ squaresum += square; \ } \ \ *NCS = squaresum; \ } DEFINE_FLOAT_LEVEL_CALCULATOR (gfloat); DEFINE_FLOAT_LEVEL_CALCULATOR (gdouble); static gboolean gst_videoframe_audiolevel_vsink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (parent); GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT: g_mutex_lock (&self->mutex); g_queue_foreach (&self->vtimeq, (GFunc) g_free, NULL); g_queue_clear (&self->vtimeq); g_mutex_unlock (&self->mutex); gst_event_copy_segment (event, &self->vsegment); if (self->vsegment.format != GST_FORMAT_TIME) return FALSE; self->vsegment.position = GST_CLOCK_TIME_NONE; break; case GST_EVENT_GAP: return TRUE; case GST_EVENT_EOS: g_mutex_lock (&self->mutex); self->video_eos_flag = TRUE; g_cond_signal (&self->cond); g_mutex_unlock (&self->mutex); break; case GST_EVENT_FLUSH_STOP: g_mutex_lock (&self->mutex); g_queue_foreach (&self->vtimeq, (GFunc) g_free, NULL); g_queue_clear (&self->vtimeq); gst_segment_init (&self->vsegment, GST_FORMAT_UNDEFINED); g_cond_signal (&self->cond); g_mutex_unlock (&self->mutex); self->vsegment.position = GST_CLOCK_TIME_NONE; break; default: break; } return gst_pad_event_default (pad, parent, event); } static gboolean gst_videoframe_audiolevel_asink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (parent); GST_LOG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT: self->first_time = GST_CLOCK_TIME_NONE; self->total_frames = 0; gst_adapter_clear (self->adapter); gst_event_copy_segment (event, &self->asegment); if (self->asegment.format != GST_FORMAT_TIME) return FALSE; break; case GST_EVENT_FLUSH_START: g_mutex_lock (&self->mutex); self->audio_flush_flag = TRUE; g_cond_signal (&self->cond); g_mutex_unlock (&self->mutex); break; case GST_EVENT_FLUSH_STOP: self->audio_flush_flag = FALSE; self->total_frames = 0; self->first_time = GST_CLOCK_TIME_NONE; gst_adapter_clear (self->adapter); gst_segment_init (&self->asegment, GST_FORMAT_UNDEFINED); break; case GST_EVENT_CAPS:{ GstCaps *caps; gint channels; gst_event_parse_caps (event, &caps); GST_DEBUG_OBJECT (self, "Got caps %" GST_PTR_FORMAT, caps); if (!gst_audio_info_from_caps (&self->ainfo, caps)) return FALSE; switch (GST_AUDIO_INFO_FORMAT (&self->ainfo)) { case GST_AUDIO_FORMAT_S8: self->process = gst_videoframe_audiolevel_calculate_gint8; break; case GST_AUDIO_FORMAT_S16: self->process = gst_videoframe_audiolevel_calculate_gint16; break; case GST_AUDIO_FORMAT_S32: self->process = gst_videoframe_audiolevel_calculate_gint32; break; case GST_AUDIO_FORMAT_F32: self->process = gst_videoframe_audiolevel_calculate_gfloat; break; case GST_AUDIO_FORMAT_F64: self->process = gst_videoframe_audiolevel_calculate_gdouble; break; default: self->process = NULL; break; } gst_adapter_clear (self->adapter); channels = GST_AUDIO_INFO_CHANNELS (&self->ainfo); self->first_time = GST_CLOCK_TIME_NONE; self->total_frames = 0; if (self->CS) g_free (self->CS); self->CS = g_new0 (gdouble, channels); break; } default: break; } return gst_pad_event_default (pad, parent, event); } static GstMessage * update_rms_from_buffer (GstVideoFrameAudioLevel * self, GstBuffer * inbuf) { GstMapInfo map; guint8 *in_data; gsize in_size; gdouble CS; guint i; guint num_frames, frames; guint num_int_samples = 0; /* number of interleaved samples * ie. total count for all channels combined */ gint channels, rate, bps; GValue v = G_VALUE_INIT; GValue va = G_VALUE_INIT; GValueArray *a; GstStructure *s; GstMessage *msg; GstClockTime duration, running_time; channels = GST_AUDIO_INFO_CHANNELS (&self->ainfo); bps = GST_AUDIO_INFO_BPS (&self->ainfo); rate = GST_AUDIO_INFO_RATE (&self->ainfo); gst_buffer_map (inbuf, &map, GST_MAP_READ); in_data = map.data; in_size = map.size; num_int_samples = in_size / bps; GST_LOG_OBJECT (self, "analyzing %u sample frames at ts %" GST_TIME_FORMAT, num_int_samples, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf))); g_return_val_if_fail (num_int_samples % channels == 0, NULL); num_frames = num_int_samples / channels; frames = num_frames; duration = GST_FRAMES_TO_CLOCK_TIME (frames, rate); if (num_frames > 0) { for (i = 0; i < channels; ++i) { self->process (in_data + (bps * i), num_int_samples, channels, &CS); GST_LOG_OBJECT (self, "[%d]: cumulative squares %lf, over %d samples/%d channels", i, CS, num_int_samples, channels); self->CS[i] += CS; } in_data += num_frames * bps; self->total_frames += num_frames; } running_time = self->first_time + gst_util_uint64_scale (self->total_frames, GST_SECOND, rate); a = g_value_array_new (channels); s = gst_structure_new ("videoframe-audiolevel", "running-time", G_TYPE_UINT64, running_time, "duration", G_TYPE_UINT64, duration, NULL); g_value_init (&v, G_TYPE_DOUBLE); g_value_init (&va, G_TYPE_VALUE_ARRAY); for (i = 0; i < channels; i++) { gdouble rms = sqrt (self->CS[i] / frames); if (frames == 0 && self->CS[i] == 0) { rms = 0; /* empty buffer */ } self->CS[i] = 0.0; g_value_set_double (&v, rms); g_value_array_append (a, &v); } g_value_take_boxed (&va, a); gst_structure_take_value (s, "rms", &va); msg = gst_message_new_element (GST_OBJECT (self), s); gst_buffer_unmap (inbuf, &map); return msg; } static GstFlowReturn gst_videoframe_audiolevel_vsink_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) { GstClockTime timestamp; GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (parent); GstClockTime duration; GstClockTime *ptrtime = g_new (GstClockTime, 1); timestamp = GST_BUFFER_TIMESTAMP (inbuf); *ptrtime = gst_segment_to_running_time (&self->vsegment, GST_FORMAT_TIME, timestamp); g_mutex_lock (&self->mutex); self->vsegment.position = timestamp; duration = GST_BUFFER_DURATION (inbuf); if (duration != GST_CLOCK_TIME_NONE) self->vsegment.position += duration; g_queue_push_tail (&self->vtimeq, ptrtime); g_cond_signal (&self->cond); GST_DEBUG_OBJECT (pad, "Pushed a frame"); g_mutex_unlock (&self->mutex); return gst_pad_push (self->vsrcpad, inbuf); } static GstFlowReturn gst_videoframe_audiolevel_asink_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) { GstClockTime timestamp, cur_time; GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (parent); GstBuffer *buf; gsize inbuf_size; guint64 start_offset, end_offset; GstClockTime running_time; gint rate, bpf; gboolean discont = FALSE; timestamp = GST_BUFFER_TIMESTAMP (inbuf); running_time = gst_segment_to_running_time (&self->asegment, GST_FORMAT_TIME, timestamp); rate = GST_AUDIO_INFO_RATE (&self->ainfo); bpf = GST_AUDIO_INFO_BPF (&self->ainfo); start_offset = gst_util_uint64_scale (timestamp, rate, GST_SECOND); inbuf_size = gst_buffer_get_size (inbuf); end_offset = start_offset + inbuf_size / bpf; g_mutex_lock (&self->mutex); if (GST_BUFFER_IS_DISCONT (inbuf) || GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_RESYNC) || self->first_time == GST_CLOCK_TIME_NONE) { discont = TRUE; } else { guint64 diff, max_sample_diff; /* Check discont, based on audiobasesink */ if (start_offset <= self->next_offset) diff = self->next_offset - start_offset; else diff = start_offset - self->next_offset; max_sample_diff = gst_util_uint64_scale_int (self->alignment_threshold, rate, GST_SECOND); /* Discont! */ if (G_UNLIKELY (diff >= max_sample_diff)) { if (self->discont_wait > 0) { if (self->discont_time == GST_CLOCK_TIME_NONE) { self->discont_time = timestamp; } else if (timestamp - self->discont_time >= self->discont_wait) { discont = TRUE; self->discont_time = GST_CLOCK_TIME_NONE; } } else { discont = TRUE; } } else if (G_UNLIKELY (self->discont_time != GST_CLOCK_TIME_NONE)) { /* we have had a discont, but are now back on track! */ self->discont_time = GST_CLOCK_TIME_NONE; } } if (discont) { /* Have discont, need resync */ if (self->next_offset != -1) GST_INFO_OBJECT (pad, "Have discont. Expected %" G_GUINT64_FORMAT ", got %" G_GUINT64_FORMAT, self->next_offset, start_offset); self->total_frames = 0; self->first_time = running_time; self->next_offset = end_offset; } else { self->next_offset += inbuf_size / bpf; } gst_adapter_push (self->adapter, gst_buffer_ref (inbuf)); GST_DEBUG_OBJECT (self, "Queue length %i", g_queue_get_length (&self->vtimeq)); while (TRUE) { GstClockTime *vt0, *vt1; GstClockTime vtemp; GstMessage *msg; gsize bytes, available_bytes; vtemp = GST_CLOCK_TIME_NONE; while (!(g_queue_get_length (&self->vtimeq) >= 2 || self->video_eos_flag || self->audio_flush_flag || self->shutdown_flag)) g_cond_wait (&self->cond, &self->mutex); if (self->audio_flush_flag || self->shutdown_flag) { g_mutex_unlock (&self->mutex); gst_buffer_unref (inbuf); return GST_FLOW_FLUSHING; } else if (self->video_eos_flag) { GST_DEBUG_OBJECT (self, "Video EOS flag alert"); /* nothing to do here if queue is empty */ if (g_queue_get_length (&self->vtimeq) == 0) break; if (g_queue_get_length (&self->vtimeq) < 2) { vtemp = self->vsegment.position; } else if (self->vsegment.position == GST_CLOCK_TIME_NONE) { /* g_queue_get_length is surely >= 2 at this point * so the adapter isn't empty */ buf = gst_adapter_take_buffer (self->adapter, gst_adapter_available (self->adapter)); if (buf != NULL) { GstMessage *msg; msg = update_rms_from_buffer (self, buf); g_mutex_unlock (&self->mutex); gst_element_post_message (GST_ELEMENT (self), msg); gst_buffer_unref (buf); g_mutex_lock (&self->mutex); /* we unlock again later */ } break; } } else if (g_queue_get_length (&self->vtimeq) < 2) { continue; } vt0 = g_queue_pop_head (&self->vtimeq); if (vtemp == GST_CLOCK_TIME_NONE) vt1 = g_queue_peek_head (&self->vtimeq); else vt1 = &vtemp; cur_time = self->first_time + gst_util_uint64_scale (self->total_frames, GST_SECOND, rate); GST_DEBUG_OBJECT (self, "Processing: current time is %" GST_TIME_FORMAT, GST_TIME_ARGS (cur_time)); GST_DEBUG_OBJECT (self, "Total frames is %i with a rate of %d", self->total_frames, rate); GST_DEBUG_OBJECT (self, "Start time is %" GST_TIME_FORMAT, GST_TIME_ARGS (self->first_time)); GST_DEBUG_OBJECT (self, "Time on top is %" GST_TIME_FORMAT, GST_TIME_ARGS (*vt0)); if (cur_time < *vt0) { guint num_frames = gst_util_uint64_scale (*vt0 - cur_time, rate, GST_SECOND); bytes = num_frames * GST_AUDIO_INFO_BPF (&self->ainfo); available_bytes = gst_adapter_available (self->adapter); if (available_bytes == 0) { g_queue_push_head (&self->vtimeq, vt0); break; } if (bytes == 0) { cur_time = *vt0; } else { GST_DEBUG_OBJECT (self, "Flushed %" G_GSIZE_FORMAT " out of %" G_GSIZE_FORMAT " bytes", bytes, available_bytes); gst_adapter_flush (self->adapter, MIN (bytes, available_bytes)); self->total_frames += num_frames; if (available_bytes <= bytes) { g_queue_push_head (&self->vtimeq, vt0); break; } cur_time = self->first_time + gst_util_uint64_scale (self->total_frames, GST_SECOND, rate); } } if (*vt1 > cur_time) { bytes = GST_AUDIO_INFO_BPF (&self->ainfo) * gst_util_uint64_scale (*vt1 - cur_time, rate, GST_SECOND); } else { bytes = 0; /* We just need to discard vt0 */ } available_bytes = gst_adapter_available (self->adapter); GST_DEBUG_OBJECT (self, "Adapter contains %" G_GSIZE_FORMAT " out of %" G_GSIZE_FORMAT " bytes", available_bytes, bytes); if (available_bytes < bytes) { g_queue_push_head (&self->vtimeq, vt0); goto done; } if (bytes > 0) { buf = gst_adapter_take_buffer (self->adapter, bytes); g_assert (buf != NULL); } else { /* Just an empty buffer */ buf = gst_buffer_new (); } msg = update_rms_from_buffer (self, buf); g_mutex_unlock (&self->mutex); gst_element_post_message (GST_ELEMENT (self), msg); g_mutex_lock (&self->mutex); gst_buffer_unref (buf); g_free (vt0); if (available_bytes == bytes) break; } done: g_mutex_unlock (&self->mutex); return gst_pad_push (self->asrcpad, inbuf); } static GstIterator * gst_videoframe_audiolevel_iterate_internal_links (GstPad * pad, GstObject * parent) { GstIterator *it = NULL; GstPad *opad; GValue val = { 0, }; GstVideoFrameAudioLevel *self = GST_VIDEOFRAME_AUDIOLEVEL (parent); if (self->asinkpad == pad) opad = gst_object_ref (self->asrcpad); else if (self->asrcpad == pad) opad = gst_object_ref (self->asinkpad); else if (self->vsinkpad == pad) opad = gst_object_ref (self->vsrcpad); else if (self->vsrcpad == pad) opad = gst_object_ref (self->vsinkpad); else goto out; g_value_init (&val, GST_TYPE_PAD); g_value_set_object (&val, opad); it = gst_iterator_new_single (GST_TYPE_PAD, &val); g_value_unset (&val); gst_object_unref (opad); out: return it; } static gboolean gst_videoframe_audiolevel_plugin_init (GstPlugin * plugin) { return gst_element_register (plugin, "videoframe-audiolevel", GST_RANK_NONE, GST_TYPE_VIDEOFRAME_AUDIOLEVEL); } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, videoframe_audiolevel, "Video frame-synchronized audio level", gst_videoframe_audiolevel_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);