mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
timecodestamper: Add support for linear timecode (LTC) from an audio stream
Based on a patch by Georg Lippitsch <glippitsch@toolsonair.com> Vivia Nikolaidou <vivia@toolsonair.com> Using libltc from https://github.com/x42/libltc
This commit is contained in:
parent
678064d603
commit
6ea4557271
4 changed files with 792 additions and 1 deletions
|
@ -2170,6 +2170,13 @@ AG_GST_CHECK_FEATURE(SCTP, [sctp plug-in], sctp, [
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
dnl *** libltc ***
|
||||||
|
PKG_CHECK_MODULES(LIBLTC, ltc >= 1.1.4, HAVE_LTC=yes, HAVE_LTC=no)
|
||||||
|
if test "x$HAVE_LTC" = "xyes"; then
|
||||||
|
LIBS="$LIBS -lltc"
|
||||||
|
AC_DEFINE(HAVE_LTC, 1, [Use libltc])
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
dnl not building plugins with external dependencies,
|
dnl not building plugins with external dependencies,
|
||||||
|
|
|
@ -63,6 +63,8 @@ enum
|
||||||
PROP_DROP_FRAME,
|
PROP_DROP_FRAME,
|
||||||
PROP_POST_MESSAGES,
|
PROP_POST_MESSAGES,
|
||||||
PROP_SET_INTERNAL_TIMECODE,
|
PROP_SET_INTERNAL_TIMECODE,
|
||||||
|
PROP_LTC_DAILY_JAM,
|
||||||
|
PROP_LTC_AUTO_RESYNC,
|
||||||
PROP_RTC_MAX_DRIFT,
|
PROP_RTC_MAX_DRIFT,
|
||||||
PROP_RTC_AUTO_RESYNC,
|
PROP_RTC_AUTO_RESYNC,
|
||||||
PROP_TIMECODE_OFFSET
|
PROP_TIMECODE_OFFSET
|
||||||
|
@ -73,10 +75,14 @@ enum
|
||||||
#define DEFAULT_DROP_FRAME FALSE
|
#define DEFAULT_DROP_FRAME FALSE
|
||||||
#define DEFAULT_POST_MESSAGES FALSE
|
#define DEFAULT_POST_MESSAGES FALSE
|
||||||
#define DEFAULT_SET_INTERNAL_TIMECODE NULL
|
#define DEFAULT_SET_INTERNAL_TIMECODE NULL
|
||||||
|
#define DEFAULT_LTC_DAILY_JAM NULL
|
||||||
|
#define DEFAULT_LTC_AUTO_RESYNC TRUE
|
||||||
#define DEFAULT_RTC_MAX_DRIFT 250000000
|
#define DEFAULT_RTC_MAX_DRIFT 250000000
|
||||||
#define DEFAULT_RTC_AUTO_RESYNC TRUE
|
#define DEFAULT_RTC_AUTO_RESYNC TRUE
|
||||||
#define DEFAULT_TIMECODE_OFFSET 0
|
#define DEFAULT_TIMECODE_OFFSET 0
|
||||||
|
|
||||||
|
#define DEFAULT_LTC_QUEUE 100
|
||||||
|
|
||||||
static GstStaticPadTemplate gst_timecodestamper_src_template =
|
static GstStaticPadTemplate gst_timecodestamper_src_template =
|
||||||
GST_STATIC_PAD_TEMPLATE ("src",
|
GST_STATIC_PAD_TEMPLATE ("src",
|
||||||
GST_PAD_SRC,
|
GST_PAD_SRC,
|
||||||
|
@ -108,6 +114,28 @@ static gboolean gst_timecodestamper_sink_event (GstBaseTransform * trans,
|
||||||
static GstFlowReturn gst_timecodestamper_transform_ip (GstBaseTransform *
|
static GstFlowReturn gst_timecodestamper_transform_ip (GstBaseTransform *
|
||||||
vfilter, GstBuffer * buffer);
|
vfilter, GstBuffer * buffer);
|
||||||
static gboolean gst_timecodestamper_stop (GstBaseTransform * trans);
|
static gboolean gst_timecodestamper_stop (GstBaseTransform * trans);
|
||||||
|
static gboolean gst_timecodestamper_start (GstBaseTransform * trans);
|
||||||
|
static GstPad *gst_timecodestamper_request_new_pad (GstElement * element,
|
||||||
|
GstPadTemplate * temp, const gchar * unused, const GstCaps * caps);
|
||||||
|
static void gst_timecodestamper_release_pad (GstElement * element,
|
||||||
|
GstPad * pad);
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
static GstFlowReturn gst_timecodestamper_ltcpad_chain (GstPad * pad,
|
||||||
|
GstObject * parent, GstBuffer * buffer);
|
||||||
|
static gboolean gst_timecodestamper_ltcpad_event (GstPad * pad,
|
||||||
|
GstObject * parent, GstEvent * event);
|
||||||
|
static gboolean gst_timecodestamper_ltcpad_query (GstPad * pad,
|
||||||
|
GstObject * parent, GstQuery * query);
|
||||||
|
static gboolean gst_timecodestamper_ltcpad_activatemode (GstPad * pad,
|
||||||
|
GstObject * parent, GstPadMode mode, gboolean active);
|
||||||
|
|
||||||
|
static gboolean gst_timecodestamper_videopad_activatemode (GstPad * pad,
|
||||||
|
GstObject * parent, GstPadMode mode, gboolean active);
|
||||||
|
|
||||||
|
static GstIterator *gst_timecodestamper_src_iterate_internal_link (GstPad * pad,
|
||||||
|
GstObject * parent);
|
||||||
|
#endif
|
||||||
|
|
||||||
static void gst_timecodestamper_update_drop_frame (GstTimeCodeStamper *
|
static void gst_timecodestamper_update_drop_frame (GstTimeCodeStamper *
|
||||||
timecodestamper);
|
timecodestamper);
|
||||||
|
@ -128,6 +156,8 @@ gst_timecodestamper_source_get_type (void)
|
||||||
{GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN,
|
{GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN,
|
||||||
"Count up from the last known upstream timecode or internal if unknown",
|
"Count up from the last known upstream timecode or internal if unknown",
|
||||||
"last-known"},
|
"last-known"},
|
||||||
|
{GST_TIME_CODE_STAMPER_SOURCE_LTC,
|
||||||
|
"Linear timecode from an audio device", "ltc"},
|
||||||
{GST_TIME_CODE_STAMPER_SOURCE_RTC,
|
{GST_TIME_CODE_STAMPER_SOURCE_RTC,
|
||||||
"Timecode from real time clock", "rtc"},
|
"Timecode from real time clock", "rtc"},
|
||||||
{0, NULL, NULL},
|
{0, NULL, NULL},
|
||||||
|
@ -204,6 +234,17 @@ gst_timecodestamper_class_init (GstTimeCodeStamperClass * klass)
|
||||||
"start at 0 with the daily jam being the current real-time clock time",
|
"start at 0 with the daily jam being the current real-time clock time",
|
||||||
GST_TYPE_VIDEO_TIME_CODE,
|
GST_TYPE_VIDEO_TIME_CODE,
|
||||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property (gobject_class, PROP_LTC_DAILY_JAM,
|
||||||
|
g_param_spec_boxed ("ltc-daily-jam",
|
||||||
|
"LTC Daily jam",
|
||||||
|
"The daily jam of the LTC timecode",
|
||||||
|
G_TYPE_DATE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property (gobject_class, PROP_LTC_AUTO_RESYNC,
|
||||||
|
g_param_spec_boolean ("ltc-auto-resync",
|
||||||
|
"LTC Auto Resync",
|
||||||
|
"If true and LTC timecode is used, it will be automatically "
|
||||||
|
"resynced if it drifts, otherwise it will only be initialised once",
|
||||||
|
DEFAULT_LTC_AUTO_RESYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
g_object_class_install_property (gobject_class, PROP_RTC_MAX_DRIFT,
|
g_object_class_install_property (gobject_class, PROP_RTC_MAX_DRIFT,
|
||||||
g_param_spec_uint64 ("rtc-max-drift",
|
g_param_spec_uint64 ("rtc-max-drift",
|
||||||
"RTC Maximum Offset",
|
"RTC Maximum Offset",
|
||||||
|
@ -220,7 +261,7 @@ gst_timecodestamper_class_init (GstTimeCodeStamperClass * klass)
|
||||||
g_object_class_install_property (gobject_class, PROP_TIMECODE_OFFSET,
|
g_object_class_install_property (gobject_class, PROP_TIMECODE_OFFSET,
|
||||||
g_param_spec_int ("timecode-offset",
|
g_param_spec_int ("timecode-offset",
|
||||||
"Timecode Offset",
|
"Timecode Offset",
|
||||||
"Add this offset in frames to internal or RTC timecode, "
|
"Add this offset in frames to internal, LTC or RTC timecode, "
|
||||||
"useful if there is an offset between the timecode source and video",
|
"useful if there is an offset between the timecode source and video",
|
||||||
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
@ -231,8 +272,14 @@ gst_timecodestamper_class_init (GstTimeCodeStamperClass * klass)
|
||||||
gst_element_class_add_pad_template (element_class,
|
gst_element_class_add_pad_template (element_class,
|
||||||
gst_static_pad_template_get (&gst_timecodestamper_ltc_template));
|
gst_static_pad_template_get (&gst_timecodestamper_ltc_template));
|
||||||
|
|
||||||
|
element_class->request_new_pad =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_request_new_pad);
|
||||||
|
element_class->release_pad =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_release_pad);
|
||||||
|
|
||||||
trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_timecodestamper_sink_event);
|
trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_timecodestamper_sink_event);
|
||||||
trans_class->stop = GST_DEBUG_FUNCPTR (gst_timecodestamper_stop);
|
trans_class->stop = GST_DEBUG_FUNCPTR (gst_timecodestamper_stop);
|
||||||
|
trans_class->start = GST_DEBUG_FUNCPTR (gst_timecodestamper_start);
|
||||||
|
|
||||||
trans_class->transform_ip =
|
trans_class->transform_ip =
|
||||||
GST_DEBUG_FUNCPTR (gst_timecodestamper_transform_ip);
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_transform_ip);
|
||||||
|
@ -248,6 +295,8 @@ gst_timecodestamper_init (GstTimeCodeStamper * timecodestamper)
|
||||||
timecodestamper->drop_frame = DEFAULT_DROP_FRAME;
|
timecodestamper->drop_frame = DEFAULT_DROP_FRAME;
|
||||||
timecodestamper->post_messages = DEFAULT_POST_MESSAGES;
|
timecodestamper->post_messages = DEFAULT_POST_MESSAGES;
|
||||||
timecodestamper->set_internal_tc = NULL;
|
timecodestamper->set_internal_tc = NULL;
|
||||||
|
timecodestamper->ltc_daily_jam = DEFAULT_LTC_DAILY_JAM;
|
||||||
|
timecodestamper->ltc_auto_resync = DEFAULT_LTC_AUTO_RESYNC;
|
||||||
timecodestamper->rtc_max_drift = DEFAULT_RTC_MAX_DRIFT;
|
timecodestamper->rtc_max_drift = DEFAULT_RTC_MAX_DRIFT;
|
||||||
timecodestamper->rtc_auto_resync = DEFAULT_RTC_AUTO_RESYNC;
|
timecodestamper->rtc_auto_resync = DEFAULT_RTC_AUTO_RESYNC;
|
||||||
timecodestamper->timecode_offset = 0;
|
timecodestamper->timecode_offset = 0;
|
||||||
|
@ -255,6 +304,32 @@ gst_timecodestamper_init (GstTimeCodeStamper * timecodestamper)
|
||||||
timecodestamper->internal_tc = NULL;
|
timecodestamper->internal_tc = NULL;
|
||||||
timecodestamper->last_tc = NULL;
|
timecodestamper->last_tc = NULL;
|
||||||
timecodestamper->rtc_tc = NULL;
|
timecodestamper->rtc_tc = NULL;
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
g_mutex_init (&timecodestamper->mutex);
|
||||||
|
g_cond_init (&timecodestamper->ltc_cond_video);
|
||||||
|
g_cond_init (&timecodestamper->ltc_cond_audio);
|
||||||
|
|
||||||
|
gst_segment_init (&timecodestamper->ltc_segment, GST_FORMAT_UNDEFINED);
|
||||||
|
timecodestamper->ltc_first_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
timecodestamper->ltc_current_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
|
timecodestamper->ltc_current_tc = NULL;
|
||||||
|
timecodestamper->ltc_current_tc_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
timecodestamper->ltc_internal_tc = NULL;
|
||||||
|
timecodestamper->ltc_dec = NULL;
|
||||||
|
timecodestamper->ltc_total = 0;
|
||||||
|
|
||||||
|
timecodestamper->ltc_eos = TRUE;
|
||||||
|
timecodestamper->ltc_flushing = TRUE;
|
||||||
|
|
||||||
|
timecodestamper->video_activatemode_default =
|
||||||
|
GST_PAD_ACTIVATEMODEFUNC (GST_BASE_TRANSFORM_SINK_PAD (timecodestamper));
|
||||||
|
GST_PAD_ACTIVATEMODEFUNC (GST_BASE_TRANSFORM_SINK_PAD (timecodestamper)) =
|
||||||
|
gst_timecodestamper_videopad_activatemode;
|
||||||
|
gst_pad_set_iterate_internal_links_function (GST_BASE_TRANSFORM_SRC_PAD
|
||||||
|
(timecodestamper), gst_timecodestamper_src_iterate_internal_link);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -286,6 +361,29 @@ gst_timecodestamper_dispose (GObject * object)
|
||||||
gst_video_time_code_free (timecodestamper->rtc_tc);
|
gst_video_time_code_free (timecodestamper->rtc_tc);
|
||||||
timecodestamper->rtc_tc = NULL;
|
timecodestamper->rtc_tc = NULL;
|
||||||
}
|
}
|
||||||
|
#if HAVE_LTC
|
||||||
|
g_cond_clear (&timecodestamper->ltc_cond_video);
|
||||||
|
g_cond_clear (&timecodestamper->ltc_cond_audio);
|
||||||
|
g_mutex_clear (&timecodestamper->mutex);
|
||||||
|
if (timecodestamper->ltc_current_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_current_tc);
|
||||||
|
timecodestamper->ltc_current_tc = NULL;
|
||||||
|
}
|
||||||
|
if (timecodestamper->ltc_internal_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_internal_tc);
|
||||||
|
timecodestamper->ltc_internal_tc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_dec) {
|
||||||
|
ltc_decoder_free (timecodestamper->ltc_dec);
|
||||||
|
timecodestamper->ltc_dec = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->stream_align) {
|
||||||
|
gst_audio_stream_align_free (timecodestamper->stream_align);
|
||||||
|
timecodestamper->stream_align = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
G_OBJECT_CLASS (gst_timecodestamper_parent_class)->dispose (object);
|
G_OBJECT_CLASS (gst_timecodestamper_parent_class)->dispose (object);
|
||||||
}
|
}
|
||||||
|
@ -310,6 +408,31 @@ gst_timecodestamper_set_property (GObject * object, guint prop_id,
|
||||||
timecodestamper->drop_frame = g_value_get_boolean (value);
|
timecodestamper->drop_frame = g_value_get_boolean (value);
|
||||||
gst_timecodestamper_update_drop_frame (timecodestamper);
|
gst_timecodestamper_update_drop_frame (timecodestamper);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LTC_DAILY_JAM:
|
||||||
|
if (timecodestamper->ltc_daily_jam)
|
||||||
|
g_date_time_unref (timecodestamper->ltc_daily_jam);
|
||||||
|
timecodestamper->ltc_daily_jam = g_value_dup_boxed (value);
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
if (timecodestamper->ltc_current_tc) {
|
||||||
|
if (timecodestamper->ltc_current_tc->config.latest_daily_jam) {
|
||||||
|
g_date_time_unref (timecodestamper->ltc_current_tc->config.
|
||||||
|
latest_daily_jam);
|
||||||
|
}
|
||||||
|
timecodestamper->ltc_current_tc->config.latest_daily_jam =
|
||||||
|
g_date_time_ref (timecodestamper->ltc_daily_jam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_internal_tc) {
|
||||||
|
if (timecodestamper->ltc_internal_tc->config.latest_daily_jam) {
|
||||||
|
g_date_time_unref (timecodestamper->ltc_internal_tc->config.
|
||||||
|
latest_daily_jam);
|
||||||
|
}
|
||||||
|
timecodestamper->ltc_internal_tc->config.latest_daily_jam =
|
||||||
|
g_date_time_ref (timecodestamper->ltc_daily_jam);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
case PROP_POST_MESSAGES:
|
case PROP_POST_MESSAGES:
|
||||||
timecodestamper->post_messages = g_value_get_boolean (value);
|
timecodestamper->post_messages = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
|
@ -327,6 +450,9 @@ gst_timecodestamper_set_property (GObject * object, guint prop_id,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case PROP_LTC_AUTO_RESYNC:
|
||||||
|
timecodestamper->ltc_auto_resync = g_value_get_boolean (value);
|
||||||
|
break;
|
||||||
case PROP_RTC_MAX_DRIFT:
|
case PROP_RTC_MAX_DRIFT:
|
||||||
timecodestamper->rtc_max_drift = g_value_get_uint64 (value);
|
timecodestamper->rtc_max_drift = g_value_get_uint64 (value);
|
||||||
break;
|
break;
|
||||||
|
@ -361,12 +487,18 @@ gst_timecodestamper_get_property (GObject * object, guint prop_id,
|
||||||
case PROP_DROP_FRAME:
|
case PROP_DROP_FRAME:
|
||||||
g_value_set_boolean (value, timecodestamper->drop_frame);
|
g_value_set_boolean (value, timecodestamper->drop_frame);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LTC_DAILY_JAM:
|
||||||
|
g_value_set_boxed (value, timecodestamper->ltc_daily_jam);
|
||||||
|
break;
|
||||||
case PROP_POST_MESSAGES:
|
case PROP_POST_MESSAGES:
|
||||||
g_value_set_boolean (value, timecodestamper->post_messages);
|
g_value_set_boolean (value, timecodestamper->post_messages);
|
||||||
break;
|
break;
|
||||||
case PROP_SET_INTERNAL_TIMECODE:
|
case PROP_SET_INTERNAL_TIMECODE:
|
||||||
g_value_set_boxed (value, timecodestamper->set_internal_tc);
|
g_value_set_boxed (value, timecodestamper->set_internal_tc);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LTC_AUTO_RESYNC:
|
||||||
|
g_value_set_boolean (value, timecodestamper->ltc_auto_resync);
|
||||||
|
break;
|
||||||
case PROP_RTC_MAX_DRIFT:
|
case PROP_RTC_MAX_DRIFT:
|
||||||
g_value_set_uint64 (value, timecodestamper->rtc_max_drift);
|
g_value_set_uint64 (value, timecodestamper->rtc_max_drift);
|
||||||
break;
|
break;
|
||||||
|
@ -388,6 +520,15 @@ gst_timecodestamper_stop (GstBaseTransform * trans)
|
||||||
{
|
{
|
||||||
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (trans);
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (trans);
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = TRUE;
|
||||||
|
timecodestamper->ltc_flushing = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
gst_video_info_init (&timecodestamper->vinfo);
|
gst_video_info_init (&timecodestamper->vinfo);
|
||||||
|
|
||||||
if (timecodestamper->internal_tc != NULL) {
|
if (timecodestamper->internal_tc != NULL) {
|
||||||
|
@ -404,6 +545,53 @@ gst_timecodestamper_stop (GstBaseTransform * trans)
|
||||||
gst_video_time_code_free (timecodestamper->last_tc);
|
gst_video_time_code_free (timecodestamper->last_tc);
|
||||||
timecodestamper->last_tc = NULL;
|
timecodestamper->last_tc = NULL;
|
||||||
}
|
}
|
||||||
|
#if HAVE_LTC
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
gst_audio_info_init (&timecodestamper->ainfo);
|
||||||
|
gst_segment_init (&timecodestamper->ltc_segment, GST_FORMAT_UNDEFINED);
|
||||||
|
|
||||||
|
timecodestamper->ltc_first_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
timecodestamper->ltc_current_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_internal_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_internal_tc);
|
||||||
|
timecodestamper->ltc_internal_tc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_current_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_current_tc);
|
||||||
|
timecodestamper->ltc_current_tc = NULL;
|
||||||
|
}
|
||||||
|
timecodestamper->ltc_current_tc_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_dec) {
|
||||||
|
ltc_decoder_free (timecodestamper->ltc_dec);
|
||||||
|
timecodestamper->ltc_dec = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->stream_align) {
|
||||||
|
gst_audio_stream_align_free (timecodestamper->stream_align);
|
||||||
|
timecodestamper->stream_align = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
timecodestamper->ltc_total = 0;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_timecodestamper_start (GstBaseTransform * trans)
|
||||||
|
{
|
||||||
|
#if HAVE_LTC
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (trans);
|
||||||
|
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = FALSE;
|
||||||
|
timecodestamper->video_eos = FALSE;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -421,6 +609,14 @@ gst_timecodestamper_update_drop_frame (GstTimeCodeStamper * timecodestamper)
|
||||||
if (timecodestamper->rtc_tc)
|
if (timecodestamper->rtc_tc)
|
||||||
timecodestamper->rtc_tc->config.flags |=
|
timecodestamper->rtc_tc->config.flags |=
|
||||||
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
#if HAVE_LTC
|
||||||
|
if (timecodestamper->ltc_current_tc)
|
||||||
|
timecodestamper->ltc_current_tc->config.flags |=
|
||||||
|
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
if (timecodestamper->ltc_internal_tc)
|
||||||
|
timecodestamper->ltc_internal_tc->config.flags |=
|
||||||
|
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
if (timecodestamper->internal_tc)
|
if (timecodestamper->internal_tc)
|
||||||
timecodestamper->internal_tc->config.flags &=
|
timecodestamper->internal_tc->config.flags &=
|
||||||
|
@ -428,6 +624,14 @@ gst_timecodestamper_update_drop_frame (GstTimeCodeStamper * timecodestamper)
|
||||||
if (timecodestamper->rtc_tc)
|
if (timecodestamper->rtc_tc)
|
||||||
timecodestamper->rtc_tc->config.flags &=
|
timecodestamper->rtc_tc->config.flags &=
|
||||||
~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
#if HAVE_LTC
|
||||||
|
if (timecodestamper->ltc_current_tc)
|
||||||
|
timecodestamper->ltc_current_tc->config.flags &=
|
||||||
|
~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
if (timecodestamper->ltc_internal_tc)
|
||||||
|
timecodestamper->ltc_internal_tc->config.flags &=
|
||||||
|
~GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,6 +690,13 @@ gst_timecodestamper_update_framerate (GstTimeCodeStamper * timecodestamper,
|
||||||
timecodestamper->last_tc);
|
timecodestamper->last_tc);
|
||||||
gst_timecodestamper_update_timecode_framerate (timecodestamper, vinfo,
|
gst_timecodestamper_update_timecode_framerate (timecodestamper, vinfo,
|
||||||
timecodestamper->rtc_tc);
|
timecodestamper->rtc_tc);
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
gst_timecodestamper_update_timecode_framerate (timecodestamper, vinfo,
|
||||||
|
timecodestamper->ltc_current_tc);
|
||||||
|
gst_timecodestamper_update_timecode_framerate (timecodestamper, vinfo,
|
||||||
|
timecodestamper->ltc_internal_tc);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
@ -533,6 +744,26 @@ gst_timecodestamper_sink_event (GstBaseTransform * trans, GstEvent * event)
|
||||||
GST_OBJECT_UNLOCK (timecodestamper);
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#if HAVE_LTC
|
||||||
|
case GST_EVENT_FLUSH_START:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
case GST_EVENT_FLUSH_STOP:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = FALSE;
|
||||||
|
timecodestamper->video_eos = FALSE;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
case GST_EVENT_EOS:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_eos = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -757,6 +988,163 @@ gst_timecodestamper_transform_ip (GstBaseTransform * vfilter,
|
||||||
}
|
}
|
||||||
GST_OBJECT_UNLOCK (timecodestamper);
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
|
||||||
|
/* Update LTC-based timecode as needed */
|
||||||
|
#if HAVE_LTC
|
||||||
|
{
|
||||||
|
GstClockTime frame_duration;
|
||||||
|
gchar *tc_str;
|
||||||
|
LTCFrameExt ltc_frame;
|
||||||
|
gboolean updated_internal = FALSE;
|
||||||
|
|
||||||
|
frame_duration = gst_util_uint64_scale_int_ceil (GST_SECOND,
|
||||||
|
timecodestamper->vinfo.fps_d, timecodestamper->vinfo.fps_n);
|
||||||
|
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
|
||||||
|
/* Wait until the the audio is at least 2 frame durations ahead of the
|
||||||
|
* video to allow for some slack, or the video pad is flushing or the
|
||||||
|
* LTC pad is EOS. */
|
||||||
|
while ((timecodestamper->ltc_current_running_time == GST_CLOCK_TIME_NONE
|
||||||
|
|| timecodestamper->ltc_current_running_time <
|
||||||
|
running_time + 2 * frame_duration)
|
||||||
|
&& !timecodestamper->video_flushing && !timecodestamper->ltc_eos) {
|
||||||
|
g_cond_wait (&timecodestamper->ltc_cond_video, &timecodestamper->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->video_flushing) {
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
flow_ret = GST_FLOW_FLUSHING;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (timecodestamper);
|
||||||
|
/* Take timecodes out of the queue until we're at the current video
|
||||||
|
* position, but first check the last timecode we took out of the queue
|
||||||
|
* if any. */
|
||||||
|
while (timecodestamper->ltc_dec && (timecodestamper->ltc_current_tc
|
||||||
|
|| ltc_decoder_read (timecodestamper->ltc_dec, <c_frame) == 1)) {
|
||||||
|
GstVideoTimeCode ltc_read_tc, *ltc_read_tc_ptr = NULL;
|
||||||
|
GstClockTime ltc_running_time;
|
||||||
|
|
||||||
|
memset (<c_read_tc, 0, sizeof (ltc_read_tc));
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_current_tc) {
|
||||||
|
ltc_running_time = timecodestamper->ltc_current_tc_running_time;
|
||||||
|
ltc_read_tc_ptr = timecodestamper->ltc_current_tc;
|
||||||
|
|
||||||
|
tc_str =
|
||||||
|
gst_video_time_code_to_string (timecodestamper->ltc_current_tc);
|
||||||
|
GST_INFO_OBJECT (timecodestamper,
|
||||||
|
"Using previous LTC timecode %s at %" GST_TIME_FORMAT, tc_str,
|
||||||
|
GST_TIME_ARGS (ltc_running_time));
|
||||||
|
g_free (tc_str);
|
||||||
|
} else {
|
||||||
|
SMPTETimecode stc;
|
||||||
|
gint fps_n_div;
|
||||||
|
|
||||||
|
if (ltc_frame.off_start < 0) {
|
||||||
|
GstClockTime offset =
|
||||||
|
gst_util_uint64_scale (GST_SECOND, -ltc_frame.off_start,
|
||||||
|
timecodestamper->ainfo.rate);
|
||||||
|
|
||||||
|
if (offset > timecodestamper->ltc_first_running_time)
|
||||||
|
ltc_running_time = 0;
|
||||||
|
else
|
||||||
|
ltc_running_time = timecodestamper->ltc_first_running_time - offset;
|
||||||
|
} else {
|
||||||
|
ltc_running_time = timecodestamper->ltc_first_running_time +
|
||||||
|
gst_util_uint64_scale (GST_SECOND, ltc_frame.off_start,
|
||||||
|
timecodestamper->ainfo.rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
ltc_frame_to_time (&stc, <c_frame.ltc, 0);
|
||||||
|
GST_INFO_OBJECT (timecodestamper,
|
||||||
|
"Got LTC timecode %02d:%02d:%02d:%02d at %" GST_TIME_FORMAT,
|
||||||
|
stc.hours, stc.mins, stc.secs, stc.frame,
|
||||||
|
GST_TIME_ARGS (ltc_running_time));
|
||||||
|
fps_n_div =
|
||||||
|
((gdouble) timecodestamper->vinfo.fps_n) /
|
||||||
|
timecodestamper->vinfo.fps_d > 30 ? 2 : 1;
|
||||||
|
|
||||||
|
gst_video_time_code_clear (<c_read_tc);
|
||||||
|
gst_video_time_code_init (<c_read_tc,
|
||||||
|
timecodestamper->vinfo.fps_n / fps_n_div,
|
||||||
|
timecodestamper->vinfo.fps_d,
|
||||||
|
timecodestamper->ltc_daily_jam,
|
||||||
|
tc_flags, stc.hours, stc.mins, stc.secs, stc.frame, 0);
|
||||||
|
|
||||||
|
ltc_read_tc_ptr = <c_read_tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A timecode frame that starts +/- half a frame to the
|
||||||
|
* video frame is considered belonging to that video frame.
|
||||||
|
*
|
||||||
|
* If it's further ahead than half a frame duration, break out of
|
||||||
|
* the loop here and reconsider on the next frame. */
|
||||||
|
if (ABSDIFF (running_time, ltc_running_time) <= frame_duration / 2) {
|
||||||
|
/* If we're resyncing LTC in general, directly replace the current
|
||||||
|
* LTC timecode with the new one we read. Otherwise we'll continue
|
||||||
|
* counting based on the previous timecode we had
|
||||||
|
*/
|
||||||
|
if (timecodestamper->ltc_auto_resync) {
|
||||||
|
if (timecodestamper->ltc_internal_tc)
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_internal_tc);
|
||||||
|
timecodestamper->ltc_internal_tc =
|
||||||
|
gst_video_time_code_copy (ltc_read_tc_ptr);
|
||||||
|
updated_internal = TRUE;
|
||||||
|
GST_INFO_OBJECT (timecodestamper, "Resynced internal LTC counter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* And store it for the next frame in case it has more or less the
|
||||||
|
* same running time */
|
||||||
|
timecodestamper->ltc_current_tc =
|
||||||
|
gst_video_time_code_copy (ltc_read_tc_ptr);
|
||||||
|
timecodestamper->ltc_current_tc_running_time = ltc_running_time;
|
||||||
|
gst_video_time_code_clear (<c_read_tc);
|
||||||
|
break;
|
||||||
|
} else if (ltc_running_time > running_time
|
||||||
|
&& ltc_running_time - running_time > frame_duration / 2) {
|
||||||
|
/* Store it for the next frame */
|
||||||
|
timecodestamper->ltc_current_tc =
|
||||||
|
gst_video_time_code_copy (ltc_read_tc_ptr);
|
||||||
|
timecodestamper->ltc_current_tc_running_time = ltc_running_time;
|
||||||
|
gst_video_time_code_clear (<c_read_tc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* otherwise it's in the past and we need to consider the next
|
||||||
|
* timecode. Forget the current timecode and read a new one */
|
||||||
|
if (timecodestamper->ltc_current_tc) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_current_tc);
|
||||||
|
timecodestamper->ltc_current_tc = NULL;
|
||||||
|
timecodestamper->ltc_current_tc_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_video_time_code_clear (<c_read_tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we didn't update from LTC above, increment our internal timecode
|
||||||
|
* for this frame */
|
||||||
|
if (!updated_internal && timecodestamper->ltc_internal_tc) {
|
||||||
|
gst_video_time_code_increment_frame (timecodestamper->ltc_internal_tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_internal_tc) {
|
||||||
|
tc_str = gst_video_time_code_to_string (timecodestamper->ltc_internal_tc);
|
||||||
|
GST_DEBUG_OBJECT (timecodestamper, "Updated LTC timecode to %s", tc_str);
|
||||||
|
g_free (tc_str);
|
||||||
|
} else {
|
||||||
|
GST_DEBUG_OBJECT (timecodestamper, "Have no LTC timecode yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
GST_OBJECT_LOCK (timecodestamper);
|
GST_OBJECT_LOCK (timecodestamper);
|
||||||
switch (timecodestamper->tc_source) {
|
switch (timecodestamper->tc_source) {
|
||||||
case GST_TIME_CODE_STAMPER_SOURCE_INTERNAL:
|
case GST_TIME_CODE_STAMPER_SOURCE_INTERNAL:
|
||||||
|
@ -770,6 +1158,17 @@ gst_timecodestamper_transform_ip (GstBaseTransform * vfilter,
|
||||||
case GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN:
|
case GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN:
|
||||||
tc = timecodestamper->last_tc;
|
tc = timecodestamper->last_tc;
|
||||||
break;
|
break;
|
||||||
|
case GST_TIME_CODE_STAMPER_SOURCE_LTC:
|
||||||
|
#if HAVE_LTC
|
||||||
|
if (timecodestamper->ltc_current_tc)
|
||||||
|
tc = timecodestamper->ltc_current_tc;
|
||||||
|
#endif
|
||||||
|
if (!tc) {
|
||||||
|
tc = gst_video_time_code_new (timecodestamper->vinfo.fps_n,
|
||||||
|
timecodestamper->vinfo.fps_d, NULL, tc_flags, 0, 0, 0, 0, 0);
|
||||||
|
free_tc = TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case GST_TIME_CODE_STAMPER_SOURCE_RTC:
|
case GST_TIME_CODE_STAMPER_SOURCE_RTC:
|
||||||
tc = timecodestamper->rtc_tc;
|
tc = timecodestamper->rtc_tc;
|
||||||
break;
|
break;
|
||||||
|
@ -843,6 +1242,9 @@ gst_timecodestamper_transform_ip (GstBaseTransform * vfilter,
|
||||||
msg = gst_message_new_element (GST_OBJECT (timecodestamper), s);
|
msg = gst_message_new_element (GST_OBJECT (timecodestamper), s);
|
||||||
gst_element_post_message (GST_ELEMENT (timecodestamper), msg);
|
gst_element_post_message (GST_ELEMENT (timecodestamper), msg);
|
||||||
}
|
}
|
||||||
|
#if HAVE_LTC
|
||||||
|
out:
|
||||||
|
#endif
|
||||||
|
|
||||||
if (dt_now)
|
if (dt_now)
|
||||||
g_date_time_unref (dt_now);
|
g_date_time_unref (dt_now);
|
||||||
|
@ -853,3 +1255,334 @@ gst_timecodestamper_transform_ip (GstBaseTransform * vfilter,
|
||||||
|
|
||||||
return flow_ret;
|
return flow_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GstPad *
|
||||||
|
gst_timecodestamper_request_new_pad (GstElement * element,
|
||||||
|
GstPadTemplate * templ, const gchar * name_templ, const GstCaps * caps)
|
||||||
|
{
|
||||||
|
#if HAVE_LTC
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (element);
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (timecodestamper);
|
||||||
|
if (timecodestamper->ltcpad) {
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
timecodestamper->ltcpad = gst_pad_new_from_static_template
|
||||||
|
(&gst_timecodestamper_ltc_template, "ltc");
|
||||||
|
|
||||||
|
gst_pad_set_chain_function (timecodestamper->ltcpad,
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_ltcpad_chain));
|
||||||
|
gst_pad_set_event_function (timecodestamper->ltcpad,
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_ltcpad_event));
|
||||||
|
gst_pad_set_query_function (timecodestamper->ltcpad,
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_ltcpad_query));
|
||||||
|
gst_pad_set_activatemode_function (timecodestamper->ltcpad,
|
||||||
|
GST_DEBUG_FUNCPTR (gst_timecodestamper_ltcpad_activatemode));
|
||||||
|
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
|
||||||
|
gst_element_add_pad (element, timecodestamper->ltcpad);
|
||||||
|
|
||||||
|
return timecodestamper->ltcpad;
|
||||||
|
#else
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_timecodestamper_release_pad (GstElement * element, GstPad * pad)
|
||||||
|
{
|
||||||
|
#if HAVE_LTC
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (element);
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (timecodestamper);
|
||||||
|
if (timecodestamper->ltcpad != pad) {
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timecodestamper->ltcpad = NULL;
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_internal_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_internal_tc);
|
||||||
|
timecodestamper->ltc_internal_tc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_current_tc != NULL) {
|
||||||
|
gst_video_time_code_free (timecodestamper->ltc_current_tc);
|
||||||
|
timecodestamper->ltc_current_tc = NULL;
|
||||||
|
}
|
||||||
|
timecodestamper->ltc_current_tc_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
|
||||||
|
gst_pad_set_active (pad, FALSE);
|
||||||
|
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_flushing = TRUE;
|
||||||
|
timecodestamper->ltc_eos = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
|
||||||
|
gst_audio_info_init (&timecodestamper->ainfo);
|
||||||
|
gst_segment_init (&timecodestamper->ltc_segment, GST_FORMAT_UNDEFINED);
|
||||||
|
|
||||||
|
timecodestamper->ltc_first_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
timecodestamper->ltc_current_running_time = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_dec) {
|
||||||
|
ltc_decoder_free (timecodestamper->ltc_dec);
|
||||||
|
timecodestamper->ltc_dec = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->stream_align) {
|
||||||
|
gst_audio_stream_align_free (timecodestamper->stream_align);
|
||||||
|
timecodestamper->stream_align = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
timecodestamper->ltc_total = 0;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
|
||||||
|
gst_element_remove_pad (element, pad);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
static GstFlowReturn
|
||||||
|
gst_timecodestamper_ltcpad_chain (GstPad * pad,
|
||||||
|
GstObject * parent, GstBuffer * buffer)
|
||||||
|
{
|
||||||
|
GstFlowReturn fr = GST_FLOW_OK;
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (parent);
|
||||||
|
GstMapInfo map;
|
||||||
|
GstClockTime timestamp, running_time, duration;
|
||||||
|
guint nsamples;
|
||||||
|
gboolean discont;
|
||||||
|
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
if (timecodestamper->ltc_flushing) {
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
gst_buffer_unref (buffer);
|
||||||
|
return GST_FLOW_FLUSHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsamples = gst_buffer_get_size (buffer) /
|
||||||
|
GST_AUDIO_INFO_BPF (&timecodestamper->ainfo);
|
||||||
|
|
||||||
|
if (!timecodestamper->stream_align) {
|
||||||
|
timecodestamper->stream_align =
|
||||||
|
gst_audio_stream_align_new (timecodestamper->ainfo.rate, GST_SECOND,
|
||||||
|
40 * GST_MSECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
discont =
|
||||||
|
gst_audio_stream_align_process (timecodestamper->stream_align,
|
||||||
|
GST_BUFFER_IS_DISCONT (buffer), GST_BUFFER_PTS (buffer), nsamples,
|
||||||
|
×tamp, &duration, NULL);
|
||||||
|
|
||||||
|
if (discont) {
|
||||||
|
if (timecodestamper->ltc_dec)
|
||||||
|
ltc_decoder_queue_flush (timecodestamper->ltc_dec);
|
||||||
|
timecodestamper->ltc_total = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timecodestamper->ltc_dec) {
|
||||||
|
gint samples_per_frame = 1920;
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (timecodestamper);
|
||||||
|
/* This is only for initialization and needs to be somewhat close to the
|
||||||
|
* real value. It will be tracked automatically afterwards */
|
||||||
|
if (timecodestamper->vinfo.fps_n) {
|
||||||
|
samples_per_frame = timecodestamper->ainfo.rate *
|
||||||
|
timecodestamper->vinfo.fps_d / timecodestamper->vinfo.fps_n;
|
||||||
|
}
|
||||||
|
GST_OBJECT_UNLOCK (timecodestamper);
|
||||||
|
|
||||||
|
timecodestamper->ltc_dec =
|
||||||
|
ltc_decoder_create (samples_per_frame, DEFAULT_LTC_QUEUE);
|
||||||
|
timecodestamper->ltc_total = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
running_time = gst_segment_to_running_time (&timecodestamper->ltc_segment,
|
||||||
|
GST_FORMAT_TIME, timestamp);
|
||||||
|
timecodestamper->ltc_current_running_time = running_time + duration;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (timecodestamper,
|
||||||
|
"Handling LTC audio buffer at %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT
|
||||||
|
" (offset %" G_GUINT64_FORMAT ")",
|
||||||
|
GST_TIME_ARGS (running_time),
|
||||||
|
GST_TIME_ARGS (timecodestamper->ltc_current_running_time),
|
||||||
|
(guint64) timecodestamper->ltc_total);
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_total == 0) {
|
||||||
|
timecodestamper->ltc_first_running_time = running_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
||||||
|
ltc_decoder_write (timecodestamper->ltc_dec, map.data, map.size,
|
||||||
|
timecodestamper->ltc_total);
|
||||||
|
timecodestamper->ltc_total += map.size;
|
||||||
|
gst_buffer_unmap (buffer, &map);
|
||||||
|
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
|
||||||
|
/* Wait until the video caught up if we already queued up a lot of pending
|
||||||
|
* timecodes, or until video is EOS or the LTC pad is flushing. */
|
||||||
|
while (ltc_decoder_queue_length (timecodestamper->ltc_dec) >
|
||||||
|
DEFAULT_LTC_QUEUE / 2 && !timecodestamper->video_eos
|
||||||
|
&& !timecodestamper->ltc_flushing) {
|
||||||
|
g_cond_wait (&timecodestamper->ltc_cond_audio, &timecodestamper->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->ltc_flushing)
|
||||||
|
fr = GST_FLOW_FLUSHING;
|
||||||
|
else
|
||||||
|
fr = GST_FLOW_OK;
|
||||||
|
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
|
||||||
|
|
||||||
|
gst_buffer_unref (buffer);
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_timecodestamper_ltcpad_event (GstPad * pad,
|
||||||
|
GstObject * parent, GstEvent * event)
|
||||||
|
{
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (parent);
|
||||||
|
|
||||||
|
GstCaps *caps;
|
||||||
|
gboolean ret = TRUE;
|
||||||
|
|
||||||
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
|
case GST_EVENT_CAPS:
|
||||||
|
gst_event_parse_caps (event, &caps);
|
||||||
|
|
||||||
|
if (!gst_audio_info_from_caps (&timecodestamper->ainfo, caps)) {
|
||||||
|
gst_event_unref (event);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timecodestamper->stream_align) {
|
||||||
|
gst_audio_stream_align_set_rate (timecodestamper->stream_align,
|
||||||
|
timecodestamper->ainfo.rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case GST_EVENT_SEGMENT:
|
||||||
|
gst_event_copy_segment (event, &timecodestamper->ltc_segment);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GST_EVENT_FLUSH_START:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_flushing = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
case GST_EVENT_FLUSH_STOP:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_flushing = FALSE;
|
||||||
|
timecodestamper->ltc_eos = FALSE;
|
||||||
|
gst_segment_init (&timecodestamper->ltc_segment, GST_FORMAT_UNDEFINED);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
case GST_EVENT_EOS:
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_eos = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_event_unref (event);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_timecodestamper_ltcpad_query (GstPad * pad,
|
||||||
|
GstObject * parent, GstQuery * query)
|
||||||
|
{
|
||||||
|
GstCaps *caps, *filter, *tcaps;
|
||||||
|
|
||||||
|
switch (GST_QUERY_TYPE (query)) {
|
||||||
|
case GST_QUERY_CAPS:
|
||||||
|
gst_query_parse_caps (query, &filter);
|
||||||
|
tcaps = gst_pad_get_pad_template_caps (pad);
|
||||||
|
if (filter)
|
||||||
|
caps = gst_caps_intersect_full (tcaps, filter,
|
||||||
|
GST_CAPS_INTERSECT_FIRST);
|
||||||
|
else
|
||||||
|
caps = gst_caps_ref (tcaps);
|
||||||
|
gst_query_set_caps_result (query, caps);
|
||||||
|
gst_caps_unref (tcaps);
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
return TRUE;
|
||||||
|
default:
|
||||||
|
return gst_pad_query_default (pad, parent, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_timecodestamper_ltcpad_activatemode (GstPad * pad,
|
||||||
|
GstObject * parent, GstPadMode mode, gboolean active)
|
||||||
|
{
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (parent);
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_flushing = FALSE;
|
||||||
|
timecodestamper->ltc_eos = FALSE;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
} else {
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->ltc_flushing = TRUE;
|
||||||
|
timecodestamper->ltc_eos = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_audio);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_timecodestamper_videopad_activatemode (GstPad * pad,
|
||||||
|
GstObject * parent, GstPadMode mode, gboolean active)
|
||||||
|
{
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (parent);
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = FALSE;
|
||||||
|
timecodestamper->video_eos = FALSE;
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
} else {
|
||||||
|
g_mutex_lock (&timecodestamper->mutex);
|
||||||
|
timecodestamper->video_flushing = TRUE;
|
||||||
|
g_cond_signal (&timecodestamper->ltc_cond_video);
|
||||||
|
g_mutex_unlock (&timecodestamper->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return timecodestamper->video_activatemode_default (pad, parent, mode,
|
||||||
|
active);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstIterator *
|
||||||
|
gst_timecodestamper_src_iterate_internal_link (GstPad * pad, GstObject * parent)
|
||||||
|
{
|
||||||
|
GstTimeCodeStamper *timecodestamper = GST_TIME_CODE_STAMPER (parent);
|
||||||
|
GValue value = G_VALUE_INIT;
|
||||||
|
GstIterator *it;
|
||||||
|
|
||||||
|
g_value_init (&value, GST_TYPE_PAD);
|
||||||
|
g_value_set_object (&value, GST_BASE_TRANSFORM_SINK_PAD (timecodestamper));
|
||||||
|
it = gst_iterator_new_single (GST_TYPE_PAD, &value);
|
||||||
|
g_value_unset (&value);
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
#include <gst/video/video.h>
|
#include <gst/video/video.h>
|
||||||
#include <gst/audio/audio.h>
|
#include <gst/audio/audio.h>
|
||||||
|
|
||||||
|
#if HAVE_LTC
|
||||||
|
#include <ltc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define GST_TYPE_TIME_CODE_STAMPER (gst_timecodestamper_get_type())
|
#define GST_TYPE_TIME_CODE_STAMPER (gst_timecodestamper_get_type())
|
||||||
#define GST_TIME_CODE_STAMPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TIME_CODE_STAMPER,GstTimeCodeStamper))
|
#define GST_TIME_CODE_STAMPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TIME_CODE_STAMPER,GstTimeCodeStamper))
|
||||||
#define GST_TIME_CODE_STAMPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_TIME_CODE_STAMPER,GstTimeCodeStamperClass))
|
#define GST_TIME_CODE_STAMPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_TIME_CODE_STAMPER,GstTimeCodeStamperClass))
|
||||||
|
@ -45,6 +49,7 @@ typedef enum GstTimeCodeStamperSource
|
||||||
GST_TIME_CODE_STAMPER_SOURCE_INTERNAL,
|
GST_TIME_CODE_STAMPER_SOURCE_INTERNAL,
|
||||||
GST_TIME_CODE_STAMPER_SOURCE_ZERO,
|
GST_TIME_CODE_STAMPER_SOURCE_ZERO,
|
||||||
GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN,
|
GST_TIME_CODE_STAMPER_SOURCE_LAST_KNOWN,
|
||||||
|
GST_TIME_CODE_STAMPER_SOURCE_LTC,
|
||||||
GST_TIME_CODE_STAMPER_SOURCE_RTC,
|
GST_TIME_CODE_STAMPER_SOURCE_RTC,
|
||||||
} GstTimeCodeStamperSource;
|
} GstTimeCodeStamperSource;
|
||||||
|
|
||||||
|
@ -87,6 +92,46 @@ struct _GstTimeCodeStamper
|
||||||
|
|
||||||
/* Internal state */
|
/* Internal state */
|
||||||
GstVideoInfo vinfo; /* protected by object lock, changed only from video streaming thread */
|
GstVideoInfo vinfo; /* protected by object lock, changed only from video streaming thread */
|
||||||
|
|
||||||
|
/* LTC specific fields */
|
||||||
|
#if HAVE_LTC
|
||||||
|
GMutex mutex;
|
||||||
|
GCond ltc_cond_video;
|
||||||
|
GCond ltc_cond_audio;
|
||||||
|
|
||||||
|
/* Only accessed from audio streaming thread */
|
||||||
|
GstAudioInfo ainfo;
|
||||||
|
GstAudioStreamAlign *stream_align;
|
||||||
|
GstSegment ltc_segment;
|
||||||
|
/* Running time of the first audio buffer passed to the LTC decoder */
|
||||||
|
GstClockTime ltc_first_running_time;
|
||||||
|
/* Running time of the last sample we passed to the LTC decoder so far */
|
||||||
|
GstClockTime ltc_current_running_time;
|
||||||
|
|
||||||
|
/* Protected by object lock */
|
||||||
|
/* Current LTC timecode that we last read close
|
||||||
|
* to our video running time */
|
||||||
|
GstVideoTimeCode *ltc_current_tc;
|
||||||
|
GstClockTime ltc_current_tc_running_time;
|
||||||
|
|
||||||
|
/* LTC timecode we last synced to and potentially incremented manually since
|
||||||
|
* then */
|
||||||
|
GstVideoTimeCode *ltc_internal_tc;
|
||||||
|
|
||||||
|
/* Protected by mutex above */
|
||||||
|
LTCDecoder *ltc_dec;
|
||||||
|
ltc_off_t ltc_total;
|
||||||
|
|
||||||
|
/* Protected by mutex above */
|
||||||
|
gboolean video_flushing;
|
||||||
|
gboolean video_eos;
|
||||||
|
|
||||||
|
/* Protected by mutex above */
|
||||||
|
gboolean ltc_flushing;
|
||||||
|
gboolean ltc_eos;
|
||||||
|
|
||||||
|
GstPadActivateModeFunction video_activatemode_default;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GstTimeCodeStamperClass
|
struct _GstTimeCodeStamperClass
|
||||||
|
|
|
@ -6,6 +6,12 @@ timecode_sources = [
|
||||||
|
|
||||||
tc_deps = [gstbase_dep, gstaudio_dep, gstvideo_dep]
|
tc_deps = [gstbase_dep, gstaudio_dep, gstvideo_dep]
|
||||||
|
|
||||||
|
ltc_dep = dependency('ltc', version : '>=1.1.4', required : false)
|
||||||
|
if ltc_dep.found()
|
||||||
|
cdata.set('HAVE_LTC', 1)
|
||||||
|
tc_deps += [ltc_dep]
|
||||||
|
endif
|
||||||
|
|
||||||
gsttimecode = library('gsttimecode',
|
gsttimecode = library('gsttimecode',
|
||||||
timecode_sources,
|
timecode_sources,
|
||||||
c_args : gst_plugins_bad_args,
|
c_args : gst_plugins_bad_args,
|
||||||
|
|
Loading…
Reference in a new issue