mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-07 06:52:41 +00:00
Merge branch 'master' into 0.11
Conflicts: ext/celt/gstceltdec.c ext/opus/gstopusdec.c ext/opus/gstopusdec.h ext/opus/gstopusenc.c ext/opus/gstopusenc.h ext/opus/gstopusparse.c
This commit is contained in:
commit
bc6ed0bf97
26 changed files with 1157 additions and 1590 deletions
|
@ -1,12 +1,13 @@
|
|||
plugin_LTLIBRARIES = libgstcelt.la
|
||||
|
||||
libgstcelt_la_SOURCES = gstcelt.c gstceltdec.c gstceltenc.c
|
||||
libgstcelt_la_CFLAGS = \
|
||||
libgstcelt_la_CFLAGS = -DGST_USE_UNSTABLE_API \
|
||||
$(GST_PLUGINS_BASE_CFLAGS) \
|
||||
$(GST_CFLAGS) \
|
||||
$(CELT_CFLAGS)
|
||||
libgstcelt_la_LIBADD = \
|
||||
$(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \
|
||||
$(GST_PLUGINS_BASE_LIBS) \
|
||||
-lgstaudio-$(GST_MAJORMINOR) -lgsttag-$(GST_MAJORMINOR) \
|
||||
$(GST_BASE_LIBS) \
|
||||
$(GST_LIBS) \
|
||||
$(CELT_LIBS)
|
||||
|
|
|
@ -68,38 +68,26 @@ GST_STATIC_PAD_TEMPLATE ("sink",
|
|||
);
|
||||
|
||||
#define gst_celt_dec_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstCeltDec, gst_celt_dec, GST_TYPE_ELEMENT);
|
||||
G_DEFINE_TYPE (GstCeltDec, gst_celt_dec, GST_TYPE_AUDIO_DECODER);
|
||||
|
||||
static gboolean celt_dec_sink_event (GstPad * pad, GstEvent * event);
|
||||
static GstFlowReturn celt_dec_chain (GstPad * pad, GstBuffer * buf);
|
||||
static gboolean celt_dec_sink_setcaps (GstPad * pad, GstCaps * caps);
|
||||
static GstStateChangeReturn celt_dec_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static gboolean celt_dec_src_event (GstPad * pad, GstEvent * event);
|
||||
static gboolean celt_dec_src_query (GstPad * pad, GstQuery * query);
|
||||
static gboolean celt_dec_sink_query (GstPad * pad, GstQuery * query);
|
||||
static const GstQueryType *celt_get_src_query_types (GstPad * pad);
|
||||
static const GstQueryType *celt_get_sink_query_types (GstPad * pad);
|
||||
static gboolean celt_dec_convert (GstPad * pad,
|
||||
GstFormat src_format, gint64 src_value,
|
||||
GstFormat * dest_format, gint64 * dest_value);
|
||||
|
||||
static GstFlowReturn celt_dec_chain_parse_data (GstCeltDec * dec,
|
||||
GstBuffer * buf, GstClockTime timestamp, GstClockTime duration);
|
||||
static GstFlowReturn celt_dec_chain_parse_header (GstCeltDec * dec,
|
||||
GstBuffer * buf);
|
||||
static GstFlowReturn celt_dec_chain_parse_comments (GstCeltDec * dec,
|
||||
GstBuffer * buf);
|
||||
static gboolean gst_celt_dec_start (GstAudioDecoder * dec);
|
||||
static gboolean gst_celt_dec_stop (GstAudioDecoder * dec);
|
||||
static gboolean gst_celt_dec_set_format (GstAudioDecoder * bdec,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn gst_celt_dec_handle_frame (GstAudioDecoder * dec,
|
||||
GstBuffer * buffer);
|
||||
|
||||
static void
|
||||
gst_celt_dec_class_init (GstCeltDecClass * klass)
|
||||
{
|
||||
GstElementClass *gstelement_class;
|
||||
GstAudioDecoderClass *gstbase_class;
|
||||
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbase_class = (GstAudioDecoderClass *) klass;
|
||||
|
||||
gstelement_class->change_state = GST_DEBUG_FUNCPTR (celt_dec_change_state);
|
||||
gstbase_class->start = GST_DEBUG_FUNCPTR (gst_celt_dec_start);
|
||||
gstbase_class->stop = GST_DEBUG_FUNCPTR (gst_celt_dec_stop);
|
||||
gstbase_class->set_format = GST_DEBUG_FUNCPTR (gst_celt_dec_set_format);
|
||||
gstbase_class->handle_frame = GST_DEBUG_FUNCPTR (gst_celt_dec_handle_frame);
|
||||
|
||||
gst_element_class_add_pad_template (gstelement_class,
|
||||
gst_static_pad_template_get (&celt_dec_src_factory));
|
||||
|
@ -118,11 +106,8 @@ gst_celt_dec_class_init (GstCeltDecClass * klass)
|
|||
static void
|
||||
gst_celt_dec_reset (GstCeltDec * dec)
|
||||
{
|
||||
gst_segment_init (&dec->segment, GST_FORMAT_UNDEFINED);
|
||||
dec->granulepos = -1;
|
||||
dec->packetno = 0;
|
||||
dec->frame_size = 0;
|
||||
dec->frame_duration = 0;
|
||||
if (dec->state) {
|
||||
celt_decoder_destroy (dec->state);
|
||||
dec->state = NULL;
|
||||
|
@ -145,411 +130,36 @@ gst_celt_dec_reset (GstCeltDec * dec)
|
|||
static void
|
||||
gst_celt_dec_init (GstCeltDec * dec)
|
||||
{
|
||||
dec->sinkpad =
|
||||
gst_pad_new_from_static_template (&celt_dec_sink_factory, "sink");
|
||||
gst_pad_set_chain_function (dec->sinkpad, GST_DEBUG_FUNCPTR (celt_dec_chain));
|
||||
gst_pad_set_event_function (dec->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (celt_dec_sink_event));
|
||||
gst_pad_set_query_type_function (dec->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (celt_get_sink_query_types));
|
||||
gst_pad_set_query_function (dec->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (celt_dec_sink_query));
|
||||
gst_pad_set_setcaps_function (dec->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (celt_dec_sink_setcaps));
|
||||
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
|
||||
|
||||
dec->srcpad = gst_pad_new_from_static_template (&celt_dec_src_factory, "src");
|
||||
gst_pad_use_fixed_caps (dec->srcpad);
|
||||
gst_pad_set_event_function (dec->srcpad,
|
||||
GST_DEBUG_FUNCPTR (celt_dec_src_event));
|
||||
gst_pad_set_query_type_function (dec->srcpad,
|
||||
GST_DEBUG_FUNCPTR (celt_get_src_query_types));
|
||||
gst_pad_set_query_function (dec->srcpad,
|
||||
GST_DEBUG_FUNCPTR (celt_dec_src_query));
|
||||
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
|
||||
|
||||
gst_celt_dec_reset (dec);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_sink_setcaps (GstPad * pad, GstCaps * caps)
|
||||
gst_celt_dec_start (GstAudioDecoder * dec)
|
||||
{
|
||||
GstCeltDec *dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
gboolean ret = TRUE;
|
||||
GstStructure *s;
|
||||
const GValue *streamheader;
|
||||
GstCeltDec *cd = GST_CELT_DEC (dec);
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
if ((streamheader = gst_structure_get_value (s, "streamheader")) &&
|
||||
G_VALUE_HOLDS (streamheader, GST_TYPE_ARRAY) &&
|
||||
gst_value_array_get_size (streamheader) >= 2) {
|
||||
const GValue *header, *vorbiscomment;
|
||||
GstBuffer *buf;
|
||||
GstFlowReturn res = GST_FLOW_OK;
|
||||
GST_DEBUG_OBJECT (dec, "start");
|
||||
gst_celt_dec_reset (cd);
|
||||
|
||||
header = gst_value_array_get_value (streamheader, 0);
|
||||
if (header && G_VALUE_HOLDS (header, GST_TYPE_BUFFER)) {
|
||||
buf = gst_value_get_buffer (header);
|
||||
res = celt_dec_chain_parse_header (dec, buf);
|
||||
if (res != GST_FLOW_OK)
|
||||
goto done;
|
||||
gst_buffer_replace (&dec->streamheader, buf);
|
||||
}
|
||||
/* we know about concealment */
|
||||
gst_audio_decoder_set_plc_aware (dec, TRUE);
|
||||
|
||||
vorbiscomment = gst_value_array_get_value (streamheader, 1);
|
||||
if (vorbiscomment && G_VALUE_HOLDS (vorbiscomment, GST_TYPE_BUFFER)) {
|
||||
buf = gst_value_get_buffer (vorbiscomment);
|
||||
res = celt_dec_chain_parse_comments (dec, buf);
|
||||
if (res != GST_FLOW_OK)
|
||||
goto done;
|
||||
gst_buffer_replace (&dec->vorbiscomment, buf);
|
||||
}
|
||||
|
||||
g_list_foreach (dec->extra_headers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (dec->extra_headers);
|
||||
dec->extra_headers = NULL;
|
||||
|
||||
if (gst_value_array_get_size (streamheader) > 2) {
|
||||
gint i, n;
|
||||
|
||||
n = gst_value_array_get_size (streamheader);
|
||||
for (i = 2; i < n; i++) {
|
||||
header = gst_value_array_get_value (streamheader, i);
|
||||
buf = gst_value_get_buffer (header);
|
||||
dec->extra_headers =
|
||||
g_list_prepend (dec->extra_headers, gst_buffer_ref (buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
gst_object_unref (dec);
|
||||
return ret;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_convert (GstPad * pad,
|
||||
GstFormat src_format, gint64 src_value,
|
||||
GstFormat * dest_format, gint64 * dest_value)
|
||||
gst_celt_dec_stop (GstAudioDecoder * dec)
|
||||
{
|
||||
gboolean res = TRUE;
|
||||
GstCeltDec *dec;
|
||||
guint64 scale = 1;
|
||||
GstCeltDec *cd = GST_CELT_DEC (dec);
|
||||
|
||||
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
GST_DEBUG_OBJECT (dec, "stop");
|
||||
gst_celt_dec_reset (cd);
|
||||
|
||||
if (dec->packetno < 1) {
|
||||
res = FALSE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (src_format == *dest_format) {
|
||||
*dest_value = src_value;
|
||||
res = TRUE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pad == dec->sinkpad &&
|
||||
(src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES)) {
|
||||
res = FALSE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
switch (src_format) {
|
||||
case GST_FORMAT_TIME:
|
||||
switch (*dest_format) {
|
||||
case GST_FORMAT_BYTES:
|
||||
scale = sizeof (gint16) * dec->header.nb_channels;
|
||||
case GST_FORMAT_DEFAULT:
|
||||
*dest_value =
|
||||
gst_util_uint64_scale_int (scale * src_value,
|
||||
dec->header.sample_rate, GST_SECOND);
|
||||
break;
|
||||
default:
|
||||
res = FALSE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GST_FORMAT_DEFAULT:
|
||||
switch (*dest_format) {
|
||||
case GST_FORMAT_BYTES:
|
||||
*dest_value = src_value * sizeof (gint16) * dec->header.nb_channels;
|
||||
break;
|
||||
case GST_FORMAT_TIME:
|
||||
*dest_value =
|
||||
gst_util_uint64_scale_int (src_value, GST_SECOND,
|
||||
dec->header.sample_rate);
|
||||
break;
|
||||
default:
|
||||
res = FALSE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GST_FORMAT_BYTES:
|
||||
switch (*dest_format) {
|
||||
case GST_FORMAT_DEFAULT:
|
||||
*dest_value = src_value / (sizeof (gint16) * dec->header.nb_channels);
|
||||
break;
|
||||
case GST_FORMAT_TIME:
|
||||
*dest_value = gst_util_uint64_scale_int (src_value, GST_SECOND,
|
||||
dec->header.sample_rate * sizeof (gint16) *
|
||||
dec->header.nb_channels);
|
||||
break;
|
||||
default:
|
||||
res = FALSE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
res = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
gst_object_unref (dec);
|
||||
return res;
|
||||
}
|
||||
|
||||
static const GstQueryType *
|
||||
celt_get_sink_query_types (GstPad * pad)
|
||||
{
|
||||
static const GstQueryType celt_dec_sink_query_types[] = {
|
||||
GST_QUERY_CONVERT,
|
||||
0
|
||||
};
|
||||
|
||||
return celt_dec_sink_query_types;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_sink_query (GstPad * pad, GstQuery * query)
|
||||
{
|
||||
GstCeltDec *dec;
|
||||
gboolean res;
|
||||
|
||||
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CONVERT:
|
||||
{
|
||||
GstFormat src_fmt, dest_fmt;
|
||||
gint64 src_val, dest_val;
|
||||
|
||||
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
|
||||
res = celt_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val);
|
||||
if (res) {
|
||||
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = gst_pad_query_default (pad, query);
|
||||
break;
|
||||
}
|
||||
|
||||
gst_object_unref (dec);
|
||||
return res;
|
||||
}
|
||||
|
||||
static const GstQueryType *
|
||||
celt_get_src_query_types (GstPad * pad)
|
||||
{
|
||||
static const GstQueryType celt_dec_src_query_types[] = {
|
||||
GST_QUERY_POSITION,
|
||||
GST_QUERY_DURATION,
|
||||
0
|
||||
};
|
||||
|
||||
return celt_dec_src_query_types;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_src_query (GstPad * pad, GstQuery * query)
|
||||
{
|
||||
GstCeltDec *dec;
|
||||
gboolean res = FALSE;
|
||||
|
||||
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_POSITION:{
|
||||
GstSegment segment;
|
||||
GstFormat format;
|
||||
gint64 cur;
|
||||
|
||||
gst_query_parse_position (query, &format, NULL);
|
||||
|
||||
GST_PAD_STREAM_LOCK (dec->sinkpad);
|
||||
segment = dec->segment;
|
||||
GST_PAD_STREAM_UNLOCK (dec->sinkpad);
|
||||
|
||||
if (segment.format != GST_FORMAT_TIME) {
|
||||
GST_DEBUG_OBJECT (dec, "segment not initialised yet");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((res = celt_dec_convert (dec->srcpad, GST_FORMAT_TIME,
|
||||
segment.last_stop, &format, &cur))) {
|
||||
gst_query_set_position (query, format, cur);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_QUERY_DURATION:{
|
||||
GstFormat format = GST_FORMAT_TIME;
|
||||
gint64 dur;
|
||||
|
||||
/* get duration from demuxer */
|
||||
if (!gst_pad_query_peer_duration (dec->sinkpad, &format, &dur))
|
||||
break;
|
||||
|
||||
gst_query_parse_duration (query, &format, NULL);
|
||||
|
||||
/* and convert it into the requested format */
|
||||
if ((res = celt_dec_convert (dec->srcpad, GST_FORMAT_TIME,
|
||||
dur, &format, &dur))) {
|
||||
gst_query_set_duration (query, format, dur);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = gst_pad_query_default (pad, query);
|
||||
break;
|
||||
}
|
||||
|
||||
gst_object_unref (dec);
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_src_event (GstPad * pad, GstEvent * event)
|
||||
{
|
||||
gboolean res = FALSE;
|
||||
GstCeltDec *dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
|
||||
GST_LOG_OBJECT (dec, "handling %s event", GST_EVENT_TYPE_NAME (event));
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_SEEK:{
|
||||
GstFormat format, tformat;
|
||||
gdouble rate;
|
||||
GstEvent *real_seek;
|
||||
GstSeekFlags flags;
|
||||
GstSeekType cur_type, stop_type;
|
||||
gint64 cur, stop;
|
||||
gint64 tcur, tstop;
|
||||
|
||||
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
|
||||
&stop_type, &stop);
|
||||
|
||||
/* we have to ask our peer to seek to time here as we know
|
||||
* nothing about how to generate a granulepos from the src
|
||||
* formats or anything.
|
||||
*
|
||||
* First bring the requested format to time
|
||||
*/
|
||||
tformat = GST_FORMAT_TIME;
|
||||
if (!(res = celt_dec_convert (pad, format, cur, &tformat, &tcur)))
|
||||
break;
|
||||
if (!(res = celt_dec_convert (pad, format, stop, &tformat, &tstop)))
|
||||
break;
|
||||
|
||||
/* then seek with time on the peer */
|
||||
real_seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
|
||||
flags, cur_type, tcur, stop_type, tstop);
|
||||
|
||||
GST_LOG_OBJECT (dec, "seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (tcur));
|
||||
|
||||
res = gst_pad_push_event (dec->sinkpad, real_seek);
|
||||
gst_event_unref (event);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = gst_pad_event_default (pad, event);
|
||||
break;
|
||||
}
|
||||
|
||||
gst_object_unref (dec);
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
celt_dec_sink_event (GstPad * pad, GstEvent * event)
|
||||
{
|
||||
GstCeltDec *dec;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
|
||||
GST_LOG_OBJECT (dec, "handling %s event", GST_EVENT_TYPE_NAME (event));
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_NEWSEGMENT:{
|
||||
GstFormat format;
|
||||
gdouble rate, arate;
|
||||
gint64 start, stop, time;
|
||||
gboolean update;
|
||||
|
||||
gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
|
||||
&start, &stop, &time);
|
||||
|
||||
if (format != GST_FORMAT_TIME)
|
||||
goto newseg_wrong_format;
|
||||
|
||||
if (rate <= 0.0)
|
||||
goto newseg_wrong_rate;
|
||||
|
||||
if (update) {
|
||||
/* time progressed without data, see if we can fill the gap with
|
||||
* some concealment data */
|
||||
if (dec->segment.last_stop < start) {
|
||||
GstClockTime duration;
|
||||
|
||||
duration = start - dec->segment.last_stop;
|
||||
celt_dec_chain_parse_data (dec, NULL, dec->segment.last_stop,
|
||||
duration);
|
||||
}
|
||||
}
|
||||
|
||||
/* now configure the values */
|
||||
gst_segment_set_newsegment_full (&dec->segment, update,
|
||||
rate, arate, GST_FORMAT_TIME, start, stop, time);
|
||||
|
||||
dec->granulepos = -1;
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "segment now: cur = %" GST_TIME_FORMAT " [%"
|
||||
GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]",
|
||||
GST_TIME_ARGS (dec->segment.last_stop),
|
||||
GST_TIME_ARGS (dec->segment.start),
|
||||
GST_TIME_ARGS (dec->segment.stop));
|
||||
|
||||
ret = gst_pad_push_event (dec->srcpad, event);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = gst_pad_event_default (pad, event);
|
||||
break;
|
||||
}
|
||||
|
||||
gst_object_unref (dec);
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
newseg_wrong_format:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dec, "received non TIME newsegment");
|
||||
gst_object_unref (dec);
|
||||
return FALSE;
|
||||
}
|
||||
newseg_wrong_rate:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dec, "negative rates not supported yet");
|
||||
gst_object_unref (dec);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
celt_dec_chain_parse_header (GstCeltDec * dec, GstBuffer * buf)
|
||||
gst_celt_dec_parse_header (GstCeltDec * dec, GstBuffer * buf)
|
||||
{
|
||||
GstCaps *caps;
|
||||
gint error = CELT_OK;
|
||||
|
@ -596,9 +206,6 @@ celt_dec_chain_parse_header (GstCeltDec * dec, GstBuffer * buf)
|
|||
celt_mode_info (dec->mode, CELT_GET_FRAME_SIZE, &dec->frame_size);
|
||||
#endif
|
||||
|
||||
dec->frame_duration = gst_util_uint64_scale_int (dec->frame_size,
|
||||
GST_SECOND, dec->header.sample_rate);
|
||||
|
||||
/* set caps */
|
||||
caps = gst_caps_new_simple ("audio/x-raw-int",
|
||||
"rate", G_TYPE_INT, dec->header.sample_rate,
|
||||
|
@ -610,7 +217,7 @@ celt_dec_chain_parse_header (GstCeltDec * dec, GstBuffer * buf)
|
|||
GST_DEBUG_OBJECT (dec, "rate=%d channels=%d frame-size=%d",
|
||||
dec->header.sample_rate, dec->header.nb_channels, dec->frame_size);
|
||||
|
||||
if (!gst_pad_set_caps (dec->srcpad, caps))
|
||||
if (!gst_pad_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec), caps))
|
||||
goto nego_failed;
|
||||
|
||||
gst_caps_unref (caps);
|
||||
|
@ -650,7 +257,7 @@ nego_failed:
|
|||
}
|
||||
|
||||
static GstFlowReturn
|
||||
celt_dec_chain_parse_comments (GstCeltDec * dec, GstBuffer * buf)
|
||||
gst_celt_dec_parse_comments (GstCeltDec * dec, GstBuffer * buf)
|
||||
{
|
||||
GstTagList *list;
|
||||
gchar *ver, *encoder = NULL;
|
||||
|
@ -685,7 +292,8 @@ celt_dec_chain_parse_comments (GstCeltDec * dec, GstBuffer * buf)
|
|||
|
||||
GST_INFO_OBJECT (dec, "tags: %" GST_PTR_FORMAT, list);
|
||||
|
||||
gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, list);
|
||||
gst_element_found_tags_for_pad (GST_ELEMENT (dec),
|
||||
GST_AUDIO_DECODER_SRC_PAD (dec), list);
|
||||
|
||||
g_free (encoder);
|
||||
g_free (ver);
|
||||
|
@ -694,8 +302,7 @@ celt_dec_chain_parse_comments (GstCeltDec * dec, GstBuffer * buf)
|
|||
}
|
||||
|
||||
static GstFlowReturn
|
||||
celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf,
|
||||
GstClockTime timestamp, GstClockTime duration)
|
||||
gst_celt_dec_parse_data (GstCeltDec * dec, GstBuffer * buf)
|
||||
{
|
||||
GstFlowReturn res = GST_FLOW_OK;
|
||||
gint size;
|
||||
|
@ -705,33 +312,23 @@ celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf,
|
|||
gint error = CELT_OK;
|
||||
int skip = 0;
|
||||
|
||||
if (timestamp != -1) {
|
||||
dec->segment.last_stop = timestamp;
|
||||
dec->granulepos = -1;
|
||||
}
|
||||
if (!dec->frame_size)
|
||||
goto not_negotiated;
|
||||
|
||||
if (buf) {
|
||||
if (G_LIKELY (GST_BUFFER_SIZE (buf))) {
|
||||
data = GST_BUFFER_DATA (buf);
|
||||
size = GST_BUFFER_SIZE (buf);
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "received buffer of size %u", size);
|
||||
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)
|
||||
&& GST_BUFFER_OFFSET_END_IS_VALID (buf)) {
|
||||
dec->granulepos = GST_BUFFER_OFFSET_END (buf);
|
||||
GST_DEBUG_OBJECT (dec,
|
||||
"Taking granulepos from upstream: %" G_GUINT64_FORMAT,
|
||||
dec->granulepos);
|
||||
}
|
||||
|
||||
/* copy timestamp */
|
||||
} else {
|
||||
/* FIXME ? actually consider how much concealment is needed */
|
||||
/* concealment data, pass NULL as the bits parameters */
|
||||
GST_DEBUG_OBJECT (dec, "creating concealment data");
|
||||
data = NULL;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
if (dec->discont) {
|
||||
/* FIXME really needed ?; this might lead to skipping samples below
|
||||
* which kind of messes with subsequent timestamping */
|
||||
if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) {
|
||||
#ifdef CELT_GET_LOOKAHEAD_REQUEST
|
||||
/* what will be 0.11.5, I guess, but no versioning yet in git */
|
||||
celt_decoder_ctl (dec->state, CELT_GET_LOOKAHEAD_REQUEST, &skip);
|
||||
|
@ -740,9 +337,9 @@ celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf,
|
|||
#endif
|
||||
}
|
||||
|
||||
res = gst_pad_alloc_buffer_and_set_caps (dec->srcpad,
|
||||
res = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec),
|
||||
GST_BUFFER_OFFSET_NONE, dec->frame_size * dec->header.nb_channels * 2,
|
||||
GST_PAD_CAPS (dec->srcpad), &outbuf);
|
||||
GST_PAD_CAPS (GST_AUDIO_DECODER_SRC_PAD (dec)), &outbuf);
|
||||
|
||||
if (res != GST_FLOW_OK) {
|
||||
GST_DEBUG_OBJECT (dec, "buf alloc flow: %s", gst_flow_get_name (res));
|
||||
|
@ -775,59 +372,88 @@ celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf,
|
|||
skip * dec->header.nb_channels * 2;
|
||||
}
|
||||
|
||||
if (dec->granulepos == -1) {
|
||||
if (dec->segment.format != GST_FORMAT_TIME) {
|
||||
GST_WARNING_OBJECT (dec, "segment not initialized or not TIME format");
|
||||
dec->granulepos = dec->frame_size;
|
||||
} else {
|
||||
dec->granulepos = gst_util_uint64_scale_int (dec->segment.last_stop,
|
||||
dec->header.sample_rate, GST_SECOND) + dec->frame_size;
|
||||
}
|
||||
GST_DEBUG_OBJECT (dec, "granulepos=%" G_GINT64_FORMAT, dec->granulepos);
|
||||
}
|
||||
|
||||
if (!GST_CLOCK_TIME_IS_VALID (timestamp))
|
||||
timestamp = gst_util_uint64_scale_int (dec->granulepos - dec->frame_size,
|
||||
GST_SECOND, dec->header.sample_rate);
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "timestamp=%" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (timestamp));
|
||||
|
||||
GST_BUFFER_OFFSET (outbuf) = dec->granulepos - dec->frame_size;
|
||||
GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos;
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
|
||||
GST_BUFFER_DURATION (outbuf) = dec->frame_duration;
|
||||
if (dec->discont) {
|
||||
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
|
||||
dec->discont = 0;
|
||||
}
|
||||
|
||||
dec->granulepos += dec->frame_size;
|
||||
dec->segment.last_stop += dec->frame_duration;
|
||||
|
||||
GST_LOG_OBJECT (dec, "pushing buffer with ts=%" GST_TIME_FORMAT ", dur=%"
|
||||
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
|
||||
GST_TIME_ARGS (dec->frame_duration));
|
||||
|
||||
res = gst_pad_push (dec->srcpad, outbuf);
|
||||
res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
|
||||
|
||||
if (res != GST_FLOW_OK)
|
||||
GST_DEBUG_OBJECT (dec, "flow: %s", gst_flow_get_name (res));
|
||||
|
||||
return res;
|
||||
|
||||
/* ERRORS */
|
||||
not_negotiated:
|
||||
{
|
||||
GST_ELEMENT_ERROR (dec, CORE, NEGOTIATION, (NULL),
|
||||
("decoder not initialized"));
|
||||
return GST_FLOW_NOT_NEGOTIATED;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_celt_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps)
|
||||
{
|
||||
GstCeltDec *dec = GST_CELT_DEC (bdec);
|
||||
gboolean ret = TRUE;
|
||||
GstStructure *s;
|
||||
const GValue *streamheader;
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
if ((streamheader = gst_structure_get_value (s, "streamheader")) &&
|
||||
G_VALUE_HOLDS (streamheader, GST_TYPE_ARRAY) &&
|
||||
gst_value_array_get_size (streamheader) >= 2) {
|
||||
const GValue *header, *vorbiscomment;
|
||||
GstBuffer *buf;
|
||||
GstFlowReturn res = GST_FLOW_OK;
|
||||
|
||||
header = gst_value_array_get_value (streamheader, 0);
|
||||
if (header && G_VALUE_HOLDS (header, GST_TYPE_BUFFER)) {
|
||||
buf = gst_value_get_buffer (header);
|
||||
res = gst_celt_dec_parse_header (dec, buf);
|
||||
if (res != GST_FLOW_OK)
|
||||
goto done;
|
||||
gst_buffer_replace (&dec->streamheader, buf);
|
||||
}
|
||||
|
||||
vorbiscomment = gst_value_array_get_value (streamheader, 1);
|
||||
if (vorbiscomment && G_VALUE_HOLDS (vorbiscomment, GST_TYPE_BUFFER)) {
|
||||
buf = gst_value_get_buffer (vorbiscomment);
|
||||
res = gst_celt_dec_parse_comments (dec, buf);
|
||||
if (res != GST_FLOW_OK)
|
||||
goto done;
|
||||
gst_buffer_replace (&dec->vorbiscomment, buf);
|
||||
}
|
||||
|
||||
g_list_foreach (dec->extra_headers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (dec->extra_headers);
|
||||
dec->extra_headers = NULL;
|
||||
|
||||
if (gst_value_array_get_size (streamheader) > 2) {
|
||||
gint i, n;
|
||||
|
||||
n = gst_value_array_get_size (streamheader);
|
||||
for (i = 2; i < n; i++) {
|
||||
header = gst_value_array_get_value (streamheader, i);
|
||||
buf = gst_value_get_buffer (header);
|
||||
dec->extra_headers =
|
||||
g_list_prepend (dec->extra_headers, gst_buffer_ref (buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
celt_dec_chain (GstPad * pad, GstBuffer * buf)
|
||||
gst_celt_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf)
|
||||
{
|
||||
GstFlowReturn res;
|
||||
GstCeltDec *dec;
|
||||
|
||||
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
|
||||
dec = GST_CELT_DEC (bdec);
|
||||
|
||||
if (GST_BUFFER_IS_DISCONT (buf)) {
|
||||
dec->discont = TRUE;
|
||||
}
|
||||
/* no fancy draining */
|
||||
if (G_UNLIKELY (!buf))
|
||||
return GST_FLOW_OK;
|
||||
|
||||
/* If we have the streamheader and vorbiscomment from the caps already
|
||||
* ignore them here */
|
||||
|
@ -835,10 +461,14 @@ celt_dec_chain (GstPad * pad, GstBuffer * buf)
|
|||
if (GST_BUFFER_SIZE (dec->streamheader) == GST_BUFFER_SIZE (buf)
|
||||
&& memcmp (GST_BUFFER_DATA (dec->streamheader), GST_BUFFER_DATA (buf),
|
||||
GST_BUFFER_SIZE (buf)) == 0) {
|
||||
GST_DEBUG_OBJECT (dec, "found streamheader");
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
res = GST_FLOW_OK;
|
||||
} else if (GST_BUFFER_SIZE (dec->vorbiscomment) == GST_BUFFER_SIZE (buf)
|
||||
&& memcmp (GST_BUFFER_DATA (dec->vorbiscomment), GST_BUFFER_DATA (buf),
|
||||
GST_BUFFER_SIZE (buf)) == 0) {
|
||||
GST_DEBUG_OBJECT (dec, "found vorbiscomments");
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
res = GST_FLOW_OK;
|
||||
} else {
|
||||
GList *l;
|
||||
|
@ -848,66 +478,36 @@ celt_dec_chain (GstPad * pad, GstBuffer * buf)
|
|||
if (GST_BUFFER_SIZE (header) == GST_BUFFER_SIZE (buf) &&
|
||||
memcmp (GST_BUFFER_DATA (header), GST_BUFFER_DATA (buf),
|
||||
GST_BUFFER_SIZE (buf)) == 0) {
|
||||
GST_DEBUG_OBJECT (dec, "found extra header buffer");
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
res = GST_FLOW_OK;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
res =
|
||||
celt_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
res = gst_celt_dec_parse_data (dec, buf);
|
||||
}
|
||||
} else {
|
||||
/* Otherwise fall back to packet counting and assume that the
|
||||
* first two packets are the headers. */
|
||||
if (dec->packetno == 0)
|
||||
res = celt_dec_chain_parse_header (dec, buf);
|
||||
else if (dec->packetno == 1)
|
||||
res = celt_dec_chain_parse_comments (dec, buf);
|
||||
else if (dec->packetno <= 1 + dec->header.extra_headers)
|
||||
if (dec->packetno == 0) {
|
||||
GST_DEBUG_OBJECT (dec, "counted streamheader");
|
||||
res = gst_celt_dec_parse_header (dec, buf);
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
} else if (dec->packetno == 1) {
|
||||
GST_DEBUG_OBJECT (dec, "counted vorbiscomments");
|
||||
res = gst_celt_dec_parse_comments (dec, buf);
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
} else if (dec->packetno <= 1 + dec->header.extra_headers) {
|
||||
GST_DEBUG_OBJECT (dec, "counted extra header");
|
||||
gst_audio_decoder_finish_frame (bdec, NULL, 1);
|
||||
res = GST_FLOW_OK;
|
||||
else
|
||||
res = celt_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
} else {
|
||||
res = gst_celt_dec_parse_data (dec, buf);
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
dec->packetno++;
|
||||
|
||||
gst_buffer_unref (buf);
|
||||
gst_object_unref (dec);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
celt_dec_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstCeltDec *dec = GST_CELT_DEC (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = parent_class->change_state (element, transition);
|
||||
if (ret != GST_STATE_CHANGE_SUCCESS)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
gst_celt_dec_reset (dec);
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#define __GST_CELT_DEC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiodecoder.h>
|
||||
#include <celt/celt.h>
|
||||
#include <celt/celt_header.h>
|
||||
|
||||
|
@ -42,22 +43,15 @@ typedef struct _GstCeltDec GstCeltDec;
|
|||
typedef struct _GstCeltDecClass GstCeltDecClass;
|
||||
|
||||
struct _GstCeltDec {
|
||||
GstElement element;
|
||||
|
||||
/* pads */
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
GstAudioDecoder element;
|
||||
|
||||
CELTDecoder *state;
|
||||
CELTMode *mode;
|
||||
CELTHeader header;
|
||||
|
||||
gint frame_size;
|
||||
GstClockTime frame_duration;
|
||||
guint64 packetno;
|
||||
|
||||
GstSegment segment; /* STREAM LOCK */
|
||||
gint64 granulepos; /* -1 = needs to be set from current time */
|
||||
gboolean discont;
|
||||
|
||||
GstBuffer *streamheader;
|
||||
|
@ -66,7 +60,7 @@ struct _GstCeltDec {
|
|||
};
|
||||
|
||||
struct _GstCeltDecClass {
|
||||
GstElementClass parent_class;
|
||||
GstAudioDecoderClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_celt_dec_get_type (void);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@
|
|||
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstadapter.h>
|
||||
#include <gst/audio/gstaudioencoder.h>
|
||||
|
||||
#include <celt/celt.h>
|
||||
#include <celt/celt_header.h>
|
||||
|
@ -49,16 +49,11 @@ typedef struct _GstCeltEnc GstCeltEnc;
|
|||
typedef struct _GstCeltEncClass GstCeltEncClass;
|
||||
|
||||
struct _GstCeltEnc {
|
||||
GstElement element;
|
||||
|
||||
/* pads */
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
GstAudioEncoder element;
|
||||
|
||||
CELTHeader header;
|
||||
CELTMode *mode;
|
||||
CELTEncoder *state;
|
||||
GstAdapter *adapter;
|
||||
|
||||
gint bitrate;
|
||||
gint frame_size;
|
||||
|
@ -72,26 +67,12 @@ struct _GstCeltEnc {
|
|||
gint channels;
|
||||
gint rate;
|
||||
|
||||
gboolean setup;
|
||||
gboolean header_sent;
|
||||
gboolean eos;
|
||||
|
||||
guint64 samples_in;
|
||||
guint64 bytes_out;
|
||||
|
||||
guint64 frameno;
|
||||
guint64 frameno_out;
|
||||
|
||||
GstClockTime start_ts;
|
||||
GstClockTime next_ts;
|
||||
guint64 granulepos_offset;
|
||||
GSList *headers;
|
||||
};
|
||||
|
||||
struct _GstCeltEncClass {
|
||||
GstElementClass parent_class;
|
||||
|
||||
/* signals */
|
||||
void (*frame_encoded) (GstElement *element);
|
||||
GstAudioEncoderClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_celt_enc_get_type (void);
|
||||
|
|
|
@ -5,5 +5,6 @@ libgstmimic_la_SOURCES = gstmimic.c gstmimdec.c gstmimenc.c
|
|||
libgstmimic_la_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(MIMIC_CFLAGS)
|
||||
libgstmimic_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(MIMIC_LIBS)
|
||||
libgstmimic_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||
libgstmimic_la_LIBTOOLFLAGS = --tag=disable-static
|
||||
|
||||
noinst_HEADERS = gstmimdec.h gstmimenc.h
|
||||
|
|
|
@ -36,6 +36,7 @@ libgstopencv_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(OPENCV_LIBS) \
|
|||
$(GSTPB_BASE_LIBS) -lgstvideo-$(GST_MAJORMINOR)
|
||||
|
||||
libgstopencv_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||
libgstopencv_la_LIBTOOLFLAGS = --tag=disable-static
|
||||
|
||||
# headers we need but don't want installed
|
||||
noinst_HEADERS = gstopencvvideofilter.h gstopencvutils.h \
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Copyright (C) 2005 Thomas Vander Stichele <thomas@apestaart.org>
|
||||
* Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
||||
* Copyright (C) 2008 Michael Sheldon <mike@mikeasoft.com>
|
||||
* Copyright (C) 2011 Stefan Sauer <ensonic@users.sf.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
|
@ -48,14 +49,27 @@
|
|||
*
|
||||
* Performs face detection on videos and images.
|
||||
*
|
||||
* The image is scaled down multiple times using the GstFacedetect::scale-factor
|
||||
* until the size is <= GstFacedetect::min-size-width or
|
||||
* GstFacedetect::min-size-height.
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch-0.10 videotestsrc ! decodebin ! ffmpegcolorspace ! facedetect ! ffmpegcolorspace ! xvimagesink
|
||||
* ]|
|
||||
* gst-launch-0.10 autovideosrc ! decodebin2 ! colorspace ! facedetect ! colorspace ! xvimagesink
|
||||
* ]| Detect and show faces
|
||||
* |[
|
||||
* gst-launch-0.10 autovideosrc ! video/x-raw-yuv,width=320,height=240 ! colorspace ! facedetect min-size-width=60 min-size-height=60 ! colorspace ! xvimagesink
|
||||
* ]| Detect large faces on a smaller image
|
||||
*
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
/* FIXME: development version of OpenCV has CV_HAAR_FIND_BIGGEST_OBJECT which
|
||||
* we might want to use if available
|
||||
* see https://code.ros.org/svn/opencv/trunk/opencv/modules/objdetect/src/haar.cpp
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
@ -67,7 +81,10 @@
|
|||
GST_DEBUG_CATEGORY_STATIC (gst_facedetect_debug);
|
||||
#define GST_CAT_DEFAULT gst_facedetect_debug
|
||||
|
||||
#define DEFAULT_PROFILE "/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml"
|
||||
#define DEFAULT_FACE_PROFILE "/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml"
|
||||
#define DEFAULT_NOSE_PROFILE "/usr/share/opencv/haarcascades/haarcascade_mcs_nose.xml"
|
||||
#define DEFAULT_MOUTH_PROFILE "/usr/share/opencv/haarcascades/haarcascade_mcs_mouth.xml"
|
||||
#define DEFAULT_EYES_PROFILE "/usr/share/opencv/haarcascades/haarcascade_mcs_eyepair_small.xml"
|
||||
#define DEFAULT_SCALE_FACTOR 1.1
|
||||
#define DEFAULT_FLAGS 0
|
||||
#define DEFAULT_MIN_NEIGHBORS 3
|
||||
|
@ -85,7 +102,10 @@ enum
|
|||
{
|
||||
PROP_0,
|
||||
PROP_DISPLAY,
|
||||
PROP_PROFILE,
|
||||
PROP_FACE_PROFILE,
|
||||
PROP_NOSE_PROFILE,
|
||||
PROP_MOUTH_PROFILE,
|
||||
PROP_EYES_PROFILE,
|
||||
PROP_SCALE_FACTOR,
|
||||
PROP_MIN_NEIGHBORS,
|
||||
PROP_FLAGS,
|
||||
|
@ -141,7 +161,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
|||
GST_STATIC_CAPS ("video/x-raw-rgb")
|
||||
);
|
||||
|
||||
GST_BOILERPLATE (Gstfacedetect, gst_facedetect, GstOpencvVideoFilter,
|
||||
GST_BOILERPLATE (GstFacedetect, gst_facedetect, GstOpencvVideoFilter,
|
||||
GST_TYPE_OPENCV_VIDEO_FILTER);
|
||||
|
||||
static void gst_facedetect_set_property (GObject * object, guint prop_id,
|
||||
|
@ -155,22 +175,33 @@ static gboolean gst_facedetect_set_caps (GstOpencvVideoFilter * transform,
|
|||
static GstFlowReturn gst_facedetect_transform_ip (GstOpencvVideoFilter * base,
|
||||
GstBuffer * buf, IplImage * img);
|
||||
|
||||
static void gst_facedetect_load_profile (Gstfacedetect * filter);
|
||||
static CvHaarClassifierCascade *gst_facedetect_load_profile (GstFacedetect *
|
||||
filter, gchar * profile);
|
||||
|
||||
/* Clean up */
|
||||
static void
|
||||
gst_facedetect_finalize (GObject * obj)
|
||||
{
|
||||
Gstfacedetect *filter = GST_FACEDETECT (obj);
|
||||
GstFacedetect *filter = GST_FACEDETECT (obj);
|
||||
|
||||
if (filter->cvGray) {
|
||||
if (filter->cvGray)
|
||||
cvReleaseImage (&filter->cvGray);
|
||||
}
|
||||
if (filter->cvStorage) {
|
||||
if (filter->cvStorage)
|
||||
cvReleaseMemStorage (&filter->cvStorage);
|
||||
}
|
||||
|
||||
g_free (filter->profile);
|
||||
g_free (filter->face_profile);
|
||||
g_free (filter->nose_profile);
|
||||
g_free (filter->mouth_profile);
|
||||
g_free (filter->eyes_profile);
|
||||
|
||||
if (filter->cvFaceDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvFaceDetect);
|
||||
if (filter->cvNoseDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvNoseDetect);
|
||||
if (filter->cvMouthDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvMouthDetect);
|
||||
if (filter->cvEyesDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvEyesDetect);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (obj);
|
||||
}
|
||||
|
@ -196,7 +227,7 @@ gst_facedetect_base_init (gpointer gclass)
|
|||
|
||||
/* initialize the facedetect's class */
|
||||
static void
|
||||
gst_facedetect_class_init (GstfacedetectClass * klass)
|
||||
gst_facedetect_class_init (GstFacedetectClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstOpencvVideoFilterClass *gstopencvbasefilter_class;
|
||||
|
@ -215,17 +246,31 @@ gst_facedetect_class_init (GstfacedetectClass * klass)
|
|||
g_param_spec_boolean ("display", "Display",
|
||||
"Sets whether the detected faces should be highlighted in the output",
|
||||
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_PROFILE,
|
||||
g_param_spec_string ("profile", "Profile",
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_FACE_PROFILE,
|
||||
g_param_spec_string ("profile", "Face profile",
|
||||
"Location of Haar cascade file to use for face detection",
|
||||
DEFAULT_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
DEFAULT_FACE_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_NOSE_PROFILE,
|
||||
g_param_spec_string ("nose-profile", "Nose profile",
|
||||
"Location of Haar cascade file to use for nose detection",
|
||||
DEFAULT_NOSE_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_MOUTH_PROFILE,
|
||||
g_param_spec_string ("mouth-profile", "Mouth profile",
|
||||
"Location of Haar cascade file to use for mouth detection",
|
||||
DEFAULT_MOUTH_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_EYES_PROFILE,
|
||||
g_param_spec_string ("eyes-profile", "Eyes profile",
|
||||
"Location of Haar cascade file to use for eye-pair detection",
|
||||
DEFAULT_EYES_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_FLAGS,
|
||||
g_param_spec_flags ("flags", "Flags", "Flags to cvHaarDetectObjects",
|
||||
GST_TYPE_OPENCV_FACE_DETECT_FLAGS, DEFAULT_FLAGS,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_SCALE_FACTOR,
|
||||
g_param_spec_double ("scale-factor", "Scale factor",
|
||||
"Factor by which the windows is scaled after each scan",
|
||||
"Factor by which the frame is scaled after each object scan",
|
||||
1.1, 10.0, DEFAULT_SCALE_FACTOR,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_MIN_NEIGHBORS,
|
||||
|
@ -234,31 +279,39 @@ gst_facedetect_class_init (GstfacedetectClass * klass)
|
|||
"an object", 0, G_MAXINT, DEFAULT_MIN_NEIGHBORS,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_MIN_SIZE_WIDTH,
|
||||
g_param_spec_int ("min-size-width", "Minimum size width",
|
||||
"Minimum window width size", 0, G_MAXINT, DEFAULT_MIN_SIZE_WIDTH,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_param_spec_int ("min-size-width", "Minimum face width",
|
||||
"Minimum area width to be recognized as a face", 0, G_MAXINT,
|
||||
DEFAULT_MIN_SIZE_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_MIN_SIZE_HEIGHT,
|
||||
g_param_spec_int ("min-size-height", "Minimum size height",
|
||||
"Minimum window height size", 0, G_MAXINT, DEFAULT_MIN_SIZE_HEIGHT,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_param_spec_int ("min-size-height", "Minimum face height",
|
||||
"Minimum area height to be recognized as a face", 0, G_MAXINT,
|
||||
DEFAULT_MIN_SIZE_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
/* initialize the new element
|
||||
* instantiate pads and add them to element
|
||||
* set pad calback functions
|
||||
* initialize instance structure
|
||||
*/
|
||||
static void
|
||||
gst_facedetect_init (Gstfacedetect * filter, GstfacedetectClass * gclass)
|
||||
gst_facedetect_init (GstFacedetect * filter, GstFacedetectClass * gclass)
|
||||
{
|
||||
filter->profile = g_strdup (DEFAULT_PROFILE);
|
||||
filter->face_profile = g_strdup (DEFAULT_FACE_PROFILE);
|
||||
filter->nose_profile = g_strdup (DEFAULT_NOSE_PROFILE);
|
||||
filter->mouth_profile = g_strdup (DEFAULT_MOUTH_PROFILE);
|
||||
filter->eyes_profile = g_strdup (DEFAULT_EYES_PROFILE);
|
||||
filter->display = TRUE;
|
||||
filter->scale_factor = DEFAULT_SCALE_FACTOR;
|
||||
filter->min_neighbors = DEFAULT_MIN_NEIGHBORS;
|
||||
filter->flags = DEFAULT_FLAGS;
|
||||
filter->min_size_width = DEFAULT_MIN_SIZE_WIDTH;
|
||||
filter->min_size_height = DEFAULT_MIN_SIZE_HEIGHT;
|
||||
gst_facedetect_load_profile (filter);
|
||||
filter->cvFaceDetect =
|
||||
gst_facedetect_load_profile (filter, filter->face_profile);
|
||||
filter->cvNoseDetect =
|
||||
gst_facedetect_load_profile (filter, filter->nose_profile);
|
||||
filter->cvMouthDetect =
|
||||
gst_facedetect_load_profile (filter, filter->mouth_profile);
|
||||
filter->cvEyesDetect =
|
||||
gst_facedetect_load_profile (filter, filter->eyes_profile);
|
||||
|
||||
gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter),
|
||||
TRUE);
|
||||
|
@ -268,13 +321,40 @@ static void
|
|||
gst_facedetect_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
Gstfacedetect *filter = GST_FACEDETECT (object);
|
||||
GstFacedetect *filter = GST_FACEDETECT (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PROFILE:
|
||||
g_free (filter->profile);
|
||||
filter->profile = g_value_dup_string (value);
|
||||
gst_facedetect_load_profile (filter);
|
||||
case PROP_FACE_PROFILE:
|
||||
g_free (filter->face_profile);
|
||||
if (filter->cvFaceDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvFaceDetect);
|
||||
filter->face_profile = g_value_dup_string (value);
|
||||
filter->cvFaceDetect =
|
||||
gst_facedetect_load_profile (filter, filter->face_profile);
|
||||
break;
|
||||
case PROP_NOSE_PROFILE:
|
||||
g_free (filter->nose_profile);
|
||||
if (filter->cvNoseDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvNoseDetect);
|
||||
filter->nose_profile = g_value_dup_string (value);
|
||||
filter->cvNoseDetect =
|
||||
gst_facedetect_load_profile (filter, filter->nose_profile);
|
||||
break;
|
||||
case PROP_MOUTH_PROFILE:
|
||||
g_free (filter->mouth_profile);
|
||||
if (filter->cvMouthDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvMouthDetect);
|
||||
filter->mouth_profile = g_value_dup_string (value);
|
||||
filter->cvMouthDetect =
|
||||
gst_facedetect_load_profile (filter, filter->mouth_profile);
|
||||
break;
|
||||
case PROP_EYES_PROFILE:
|
||||
g_free (filter->eyes_profile);
|
||||
if (filter->cvEyesDetect)
|
||||
cvReleaseHaarClassifierCascade (&filter->cvEyesDetect);
|
||||
filter->eyes_profile = g_value_dup_string (value);
|
||||
filter->cvEyesDetect =
|
||||
gst_facedetect_load_profile (filter, filter->eyes_profile);
|
||||
break;
|
||||
case PROP_DISPLAY:
|
||||
filter->display = g_value_get_boolean (value);
|
||||
|
@ -304,11 +384,20 @@ static void
|
|||
gst_facedetect_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
Gstfacedetect *filter = GST_FACEDETECT (object);
|
||||
GstFacedetect *filter = GST_FACEDETECT (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PROFILE:
|
||||
g_value_set_string (value, filter->profile);
|
||||
case PROP_FACE_PROFILE:
|
||||
g_value_set_string (value, filter->face_profile);
|
||||
break;
|
||||
case PROP_NOSE_PROFILE:
|
||||
g_value_set_string (value, filter->nose_profile);
|
||||
break;
|
||||
case PROP_MOUTH_PROFILE:
|
||||
g_value_set_string (value, filter->mouth_profile);
|
||||
break;
|
||||
case PROP_EYES_PROFILE:
|
||||
g_value_set_string (value, filter->eyes_profile);
|
||||
break;
|
||||
case PROP_DISPLAY:
|
||||
g_value_set_boolean (value, filter->display);
|
||||
|
@ -342,7 +431,7 @@ gst_facedetect_set_caps (GstOpencvVideoFilter * transform, gint in_width,
|
|||
gint in_height, gint in_depth, gint in_channels,
|
||||
gint out_width, gint out_height, gint out_depth, gint out_channels)
|
||||
{
|
||||
Gstfacedetect *filter;
|
||||
GstFacedetect *filter;
|
||||
|
||||
filter = GST_FACEDETECT (transform);
|
||||
|
||||
|
@ -361,7 +450,7 @@ gst_facedetect_set_caps (GstOpencvVideoFilter * transform, gint in_width,
|
|||
}
|
||||
|
||||
static GstMessage *
|
||||
gst_facedetect_message_new (Gstfacedetect * filter, GstBuffer * buf)
|
||||
gst_facedetect_message_new (GstFacedetect * filter, GstBuffer * buf)
|
||||
{
|
||||
GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (filter);
|
||||
GstStructure *s;
|
||||
|
@ -389,21 +478,29 @@ static GstFlowReturn
|
|||
gst_facedetect_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
|
||||
IplImage * img)
|
||||
{
|
||||
Gstfacedetect *filter;
|
||||
CvSeq *faces;
|
||||
int i;
|
||||
GstFacedetect *filter = GST_FACEDETECT (base);
|
||||
|
||||
filter = GST_FACEDETECT (base);
|
||||
|
||||
cvCvtColor (img, filter->cvGray, CV_RGB2GRAY);
|
||||
cvClearMemStorage (filter->cvStorage);
|
||||
|
||||
if (filter->cvCascade) {
|
||||
if (filter->cvFaceDetect) {
|
||||
GstMessage *msg = NULL;
|
||||
GValue facelist = { 0 };
|
||||
CvSeq *faces;
|
||||
CvSeq *mouth, *nose, *eyes;
|
||||
gint i;
|
||||
gboolean do_display = FALSE;
|
||||
|
||||
if (filter->display) {
|
||||
if (gst_buffer_is_writable (buf)) {
|
||||
do_display = TRUE;
|
||||
} else {
|
||||
GST_LOG_OBJECT (filter, "Buffer is not writable, not drawing faces.");
|
||||
}
|
||||
}
|
||||
|
||||
cvCvtColor (img, filter->cvGray, CV_RGB2GRAY);
|
||||
cvClearMemStorage (filter->cvStorage);
|
||||
|
||||
faces =
|
||||
cvHaarDetectObjects (filter->cvGray, filter->cvCascade,
|
||||
cvHaarDetectObjects (filter->cvGray, filter->cvFaceDetect,
|
||||
filter->cvStorage, filter->scale_factor, filter->min_neighbors,
|
||||
filter->flags, cvSize (filter->min_size_width, filter->min_size_height)
|
||||
#if (CV_MAJOR_VERSION >= 2) && (CV_MINOR_VERSION >= 2)
|
||||
|
@ -419,36 +516,168 @@ gst_facedetect_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
|
|||
for (i = 0; i < (faces ? faces->total : 0); i++) {
|
||||
CvRect *r = (CvRect *) cvGetSeqElem (faces, i);
|
||||
GValue value = { 0 };
|
||||
GstStructure *s;
|
||||
guint mw = filter->min_size_width / 8;
|
||||
guint mh = filter->min_size_height / 8;
|
||||
guint rnx, rny, rnw, rnh;
|
||||
guint rmx, rmy, rmw, rmh;
|
||||
guint rex, rey, rew, reh;
|
||||
gboolean have_nose, have_mouth, have_eyes;
|
||||
|
||||
GstStructure *s = gst_structure_new ("face",
|
||||
/* detect face features */
|
||||
|
||||
rnx = r->x + r->width / 4;
|
||||
rny = r->y + r->height / 4;
|
||||
rnw = r->width / 2;
|
||||
rnh = r->height / 2;
|
||||
cvSetImageROI (filter->cvGray, cvRect (rnx, rny, rnw, rnh));
|
||||
nose =
|
||||
cvHaarDetectObjects (filter->cvGray, filter->cvNoseDetect,
|
||||
filter->cvStorage, filter->scale_factor, filter->min_neighbors,
|
||||
filter->flags, cvSize (mw, mh)
|
||||
#if (CV_MAJOR_VERSION >= 2) && (CV_MINOR_VERSION >= 2)
|
||||
, cvSize (mw + 2, mh + 2)
|
||||
#endif
|
||||
);
|
||||
have_nose = (nose && nose->total);
|
||||
cvResetImageROI (filter->cvGray);
|
||||
|
||||
rmx = r->x;
|
||||
rmy = r->y + r->height / 2;
|
||||
rmw = r->width;
|
||||
rmh = r->height / 2;
|
||||
cvSetImageROI (filter->cvGray, cvRect (rmx, rmy, rmw, rmh));
|
||||
mouth =
|
||||
cvHaarDetectObjects (filter->cvGray, filter->cvMouthDetect,
|
||||
filter->cvStorage, filter->scale_factor, filter->min_neighbors,
|
||||
filter->flags, cvSize (mw, mh)
|
||||
#if (CV_MAJOR_VERSION >= 2) && (CV_MINOR_VERSION >= 2)
|
||||
, cvSize (mw + 2, mh + 2)
|
||||
#endif
|
||||
);
|
||||
have_mouth = (mouth && mouth->total);
|
||||
cvResetImageROI (filter->cvGray);
|
||||
|
||||
rex = r->x;
|
||||
rey = r->y;
|
||||
rew = r->width;
|
||||
reh = r->height / 2;
|
||||
cvSetImageROI (filter->cvGray, cvRect (rex, rey, rew, reh));
|
||||
eyes =
|
||||
cvHaarDetectObjects (filter->cvGray, filter->cvEyesDetect,
|
||||
filter->cvStorage, filter->scale_factor, filter->min_neighbors,
|
||||
filter->flags, cvSize (mw, mh)
|
||||
#if (CV_MAJOR_VERSION >= 2) && (CV_MINOR_VERSION >= 2)
|
||||
, cvSize (mw + 2, mh + 2)
|
||||
#endif
|
||||
);
|
||||
have_eyes = (eyes && eyes->total);
|
||||
cvResetImageROI (filter->cvGray);
|
||||
|
||||
GST_LOG_OBJECT (filter,
|
||||
"%2d/%2d: x,y = %4u,%4u: w.h = %4u,%4u : features(e,n,m) = %d,%d,%d",
|
||||
i, faces->total, r->x, r->y, r->width, r->height,
|
||||
have_eyes, have_nose, have_mouth);
|
||||
|
||||
/* ignore 'face' where we don't fix mount/nose/eyes ? */
|
||||
if (!(have_eyes && have_nose && have_mouth))
|
||||
continue;
|
||||
|
||||
s = gst_structure_new ("face",
|
||||
"x", G_TYPE_UINT, r->x,
|
||||
"y", G_TYPE_UINT, r->y,
|
||||
"width", G_TYPE_UINT, r->width,
|
||||
"height", G_TYPE_UINT, r->height, NULL);
|
||||
|
||||
GstMessage *m = gst_message_new_element (GST_OBJECT (filter), s);
|
||||
if (nose && nose->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (nose, 0);
|
||||
GST_LOG_OBJECT (filter, "nose/%d: x,y = %4u,%4u: w.h = %4u,%4u",
|
||||
nose->total, rnx + sr->x, rny + sr->y, sr->width, sr->height);
|
||||
gst_structure_set (s,
|
||||
"nose->x", G_TYPE_UINT, rnx + sr->x,
|
||||
"nose->y", G_TYPE_UINT, rny + sr->y,
|
||||
"nose->width", G_TYPE_UINT, sr->width,
|
||||
"nose->height", G_TYPE_UINT, sr->height, NULL);
|
||||
}
|
||||
if (mouth && mouth->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (mouth, 0);
|
||||
GST_LOG_OBJECT (filter, "mouth/%d: x,y = %4u,%4u: w.h = %4u,%4u",
|
||||
mouth->total, rmx + sr->x, rmy + sr->y, sr->width, sr->height);
|
||||
gst_structure_set (s,
|
||||
"mouth->x", G_TYPE_UINT, rmx + sr->x,
|
||||
"mouth->y", G_TYPE_UINT, rmy + sr->y,
|
||||
"mouth->width", G_TYPE_UINT, sr->width,
|
||||
"mouth->height", G_TYPE_UINT, sr->height, NULL);
|
||||
}
|
||||
if (eyes && eyes->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (eyes, 0);
|
||||
GST_LOG_OBJECT (filter, "eyes/%d: x,y = %4u,%4u: w.h = %4u,%4u",
|
||||
eyes->total, rex + sr->x, rey + sr->y, sr->width, sr->height);
|
||||
gst_structure_set (s,
|
||||
"eyes->x", G_TYPE_UINT, rex + sr->x,
|
||||
"eyes->y", G_TYPE_UINT, rey + sr->y,
|
||||
"eyes->width", G_TYPE_UINT, sr->width,
|
||||
"eyes->height", G_TYPE_UINT, sr->height, NULL);
|
||||
}
|
||||
|
||||
g_value_init (&value, GST_TYPE_STRUCTURE);
|
||||
gst_value_set_structure (&value, s);
|
||||
gst_value_list_append_value (&facelist, &value);
|
||||
g_value_unset (&value);
|
||||
|
||||
gst_element_post_message (GST_ELEMENT (filter), m);
|
||||
if (do_display) {
|
||||
CvPoint center;
|
||||
CvSize axes;
|
||||
gdouble w, h;
|
||||
gint cb = 255 - ((i & 3) << 7);
|
||||
gint cg = 255 - ((i & 12) << 5);
|
||||
gint cr = 255 - ((i & 48) << 3);
|
||||
|
||||
if (filter->display) {
|
||||
if (gst_buffer_is_writable (buf)) {
|
||||
CvPoint center;
|
||||
int radius;
|
||||
center.x = cvRound ((r->x + r->width * 0.5));
|
||||
center.y = cvRound ((r->y + r->height * 0.5));
|
||||
radius = cvRound ((r->width + r->height) * 0.25);
|
||||
cvCircle (img, center, radius, CV_RGB (255, 32, 32), 3, 8, 0);
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (filter, "Buffer is not writable, not drawing "
|
||||
"circles for faces");
|
||||
w = r->width / 2;
|
||||
h = r->height / 2;
|
||||
center.x = cvRound ((r->x + w));
|
||||
center.y = cvRound ((r->y + h));
|
||||
axes.width = w;
|
||||
axes.height = h * 1.25; /* tweak for face form */
|
||||
cvEllipse (img, center, axes, 0.0, 0.0, 360.0, CV_RGB (cr, cg, cb),
|
||||
3, 8, 0);
|
||||
|
||||
if (nose && nose->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (nose, 0);
|
||||
|
||||
w = sr->width / 2;
|
||||
h = sr->height / 2;
|
||||
center.x = cvRound ((rnx + sr->x + w));
|
||||
center.y = cvRound ((rny + sr->y + h));
|
||||
axes.width = w;
|
||||
axes.height = h * 1.25; /* tweak for nose form */
|
||||
cvEllipse (img, center, axes, 0.0, 0.0, 360.0, CV_RGB (cr, cg, cb),
|
||||
1, 8, 0);
|
||||
}
|
||||
if (mouth && mouth->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (mouth, 0);
|
||||
|
||||
w = sr->width / 2;
|
||||
h = sr->height / 2;
|
||||
center.x = cvRound ((rmx + sr->x + w));
|
||||
center.y = cvRound ((rmy + sr->y + h));
|
||||
axes.width = w * 1.5; /* tweak for mouth form */
|
||||
axes.height = h;
|
||||
cvEllipse (img, center, axes, 0.0, 0.0, 360.0, CV_RGB (cr, cg, cb),
|
||||
1, 8, 0);
|
||||
}
|
||||
if (eyes && eyes->total) {
|
||||
CvRect *sr = (CvRect *) cvGetSeqElem (eyes, 0);
|
||||
|
||||
w = sr->width / 2;
|
||||
h = sr->height / 2;
|
||||
center.x = cvRound ((rex + sr->x + w));
|
||||
center.y = cvRound ((rey + sr->y + h));
|
||||
axes.width = w * 1.5; /* tweak for eyes form */
|
||||
axes.height = h;
|
||||
cvEllipse (img, center, axes, 0.0, 0.0, 360.0, CV_RGB (cr, cg, cb),
|
||||
1, 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
|
@ -462,14 +691,16 @@ gst_facedetect_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
|
|||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_facedetect_load_profile (Gstfacedetect * filter)
|
||||
static CvHaarClassifierCascade *
|
||||
gst_facedetect_load_profile (GstFacedetect * filter, gchar * profile)
|
||||
{
|
||||
filter->cvCascade =
|
||||
(CvHaarClassifierCascade *) cvLoad (filter->profile, 0, 0, 0);
|
||||
if (!filter->cvCascade) {
|
||||
GST_WARNING ("Couldn't load Haar classifier cascade: %s.", filter->profile);
|
||||
CvHaarClassifierCascade *cascade;
|
||||
|
||||
if (!(cascade = (CvHaarClassifierCascade *) cvLoad (profile, 0, 0, 0))) {
|
||||
GST_WARNING_OBJECT (filter, "Couldn't load Haar classifier cascade: %s.",
|
||||
profile);
|
||||
}
|
||||
return cascade;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Copyright (C) 2005 Thomas Vander Stichele <thomas@apestaart.org>
|
||||
* Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
||||
* Copyright (C) 2008 Michael Sheldon <mike@mikeasoft.com>
|
||||
* Copyright (C) 2011 Stefan Sauer <ensonic@users.sf.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
|
@ -59,23 +60,26 @@ G_BEGIN_DECLS
|
|||
#define GST_TYPE_FACEDETECT \
|
||||
(gst_facedetect_get_type())
|
||||
#define GST_FACEDETECT(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FACEDETECT,Gstfacedetect))
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FACEDETECT,GstFacedetect))
|
||||
#define GST_FACEDETECT_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FACEDETECT,GstfacedetectClass))
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FACEDETECT,GstFacedetectClass))
|
||||
#define GST_IS_FACEDETECT(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FACEDETECT))
|
||||
#define GST_IS_FACEDETECT_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FACEDETECT))
|
||||
typedef struct _Gstfacedetect Gstfacedetect;
|
||||
typedef struct _GstfacedetectClass GstfacedetectClass;
|
||||
typedef struct _GstFacedetect GstFacedetect;
|
||||
typedef struct _GstFacedetectClass GstFacedetectClass;
|
||||
|
||||
struct _Gstfacedetect
|
||||
struct _GstFacedetect
|
||||
{
|
||||
GstOpencvVideoFilter element;
|
||||
|
||||
gboolean display;
|
||||
|
||||
gchar *profile;
|
||||
gchar *face_profile;
|
||||
gchar *nose_profile;
|
||||
gchar *mouth_profile;
|
||||
gchar *eyes_profile;
|
||||
gdouble scale_factor;
|
||||
gint min_neighbors;
|
||||
gint flags;
|
||||
|
@ -83,11 +87,14 @@ struct _Gstfacedetect
|
|||
gint min_size_height;
|
||||
|
||||
IplImage *cvGray;
|
||||
CvHaarClassifierCascade *cvCascade;
|
||||
CvHaarClassifierCascade *cvFaceDetect;
|
||||
CvHaarClassifierCascade *cvNoseDetect;
|
||||
CvHaarClassifierCascade *cvMouthDetect;
|
||||
CvHaarClassifierCascade *cvEyesDetect;
|
||||
CvMemStorage *cvStorage;
|
||||
};
|
||||
|
||||
struct _GstfacedetectClass
|
||||
struct _GstFacedetectClass
|
||||
{
|
||||
GstOpencvVideoFilterClass parent_class;
|
||||
};
|
||||
|
|
|
@ -67,14 +67,14 @@ GST_STATIC_PAD_TEMPLATE ("sink",
|
|||
|
||||
G_DEFINE_TYPE (GstOpusDec, gst_opus_dec, GST_TYPE_AUDIO_DECODER);
|
||||
|
||||
static GstFlowReturn gst_opus_dec_parse_header (GstOpusDec * dec,
|
||||
GstBuffer * buf);
|
||||
static gboolean gst_opus_dec_start (GstAudioDecoder * dec);
|
||||
static gboolean gst_opus_dec_stop (GstAudioDecoder * dec);
|
||||
static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * dec,
|
||||
GstBuffer * buffer);
|
||||
static gboolean gst_opus_dec_set_format (GstAudioDecoder * bdec,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn opus_dec_chain_parse_data (GstOpusDec * dec,
|
||||
GstBuffer * buf, GstClockTime timestamp, GstClockTime duration);
|
||||
|
||||
static void
|
||||
gst_opus_dec_class_init (GstOpusDecClass * klass)
|
||||
|
@ -112,8 +112,6 @@ gst_opus_dec_reset (GstOpusDec * dec)
|
|||
dec->state = NULL;
|
||||
}
|
||||
|
||||
dec->next_ts = 0;
|
||||
|
||||
gst_buffer_replace (&dec->streamheader, NULL);
|
||||
gst_buffer_replace (&dec->vorbiscomment, NULL);
|
||||
}
|
||||
|
@ -167,17 +165,19 @@ gst_opus_dec_setup_from_peer_caps (GstOpusDec * dec)
|
|||
{
|
||||
GstPad *srcpad, *peer;
|
||||
GstStructure *s;
|
||||
GstCaps *caps, *template_caps, *peer_caps;
|
||||
GstCaps *caps;
|
||||
const GstCaps *template_caps;
|
||||
const GstCaps *peer_caps;
|
||||
|
||||
srcpad = GST_AUDIO_DECODER_SRC_PAD (dec);
|
||||
peer = gst_pad_get_peer (srcpad);
|
||||
|
||||
if (peer) {
|
||||
template_caps = gst_pad_get_pad_template_caps (srcpad);
|
||||
peer_caps = gst_pad_get_caps (peer, NULL);
|
||||
peer_caps = gst_pad_get_caps (peer);
|
||||
GST_DEBUG_OBJECT (dec, "Peer caps: %" GST_PTR_FORMAT, peer_caps);
|
||||
caps = gst_caps_intersect (template_caps, peer_caps);
|
||||
gst_caps_fixate (caps);
|
||||
gst_pad_fixate_caps (peer, caps);
|
||||
GST_DEBUG_OBJECT (dec, "Fixated caps: %" GST_PTR_FORMAT, caps);
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
|
@ -203,8 +203,7 @@ gst_opus_dec_setup_from_peer_caps (GstOpusDec * dec)
|
|||
}
|
||||
|
||||
static GstFlowReturn
|
||||
opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf,
|
||||
GstClockTime timestamp, GstClockTime duration)
|
||||
opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf)
|
||||
{
|
||||
GstFlowReturn res = GST_FLOW_OK;
|
||||
gsize size, out_size;
|
||||
|
@ -218,6 +217,8 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf,
|
|||
if (dec->state == NULL) {
|
||||
gst_opus_dec_setup_from_peer_caps (dec);
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "Creating decoder with %d channels, %d Hz",
|
||||
dec->n_channels, dec->sample_rate);
|
||||
dec->state = opus_decoder_create (dec->sample_rate, dec->n_channels, &err);
|
||||
if (!dec->state || err != OPUS_OK)
|
||||
goto creation_failed;
|
||||
|
@ -227,8 +228,6 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf,
|
|||
data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "received buffer of size %u", size);
|
||||
|
||||
/* copy timestamp */
|
||||
} else {
|
||||
/* concealment data, pass NULL as the bits parameters */
|
||||
GST_DEBUG_OBJECT (dec, "creating concealment data");
|
||||
|
@ -261,20 +260,6 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf,
|
|||
}
|
||||
GST_DEBUG_OBJECT (dec, "decoded %d samples", n);
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
|
||||
} else {
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = dec->next_ts;
|
||||
}
|
||||
|
||||
GST_BUFFER_DURATION (outbuf) =
|
||||
gst_util_uint64_scale (n, GST_SECOND, dec->sample_rate);
|
||||
dec->next_ts = GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf);
|
||||
|
||||
GST_LOG_OBJECT (dec, "pushing buffer with ts=%" GST_TIME_FORMAT ", dur=%"
|
||||
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
|
||||
GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)));
|
||||
|
||||
res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
|
||||
|
||||
if (res != GST_FLOW_OK)
|
||||
|
@ -395,8 +380,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
|
|||
gst_audio_decoder_finish_frame (adec, NULL, 1);
|
||||
res = GST_FLOW_OK;
|
||||
} else {
|
||||
res = opus_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
res = opus_dec_chain_parse_data (dec, buf);
|
||||
}
|
||||
} else {
|
||||
/* Otherwise fall back to packet counting and assume that the
|
||||
|
@ -408,8 +392,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
|
|||
res = gst_opus_dec_parse_header (dec, buf);
|
||||
gst_audio_decoder_finish_frame (adec, NULL, 1);
|
||||
} else {
|
||||
res = opus_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
res = opus_dec_chain_parse_data (dec, buf);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
|
@ -418,14 +401,12 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
|
|||
res = gst_opus_dec_parse_comments (dec, buf);
|
||||
gst_audio_decoder_finish_frame (adec, NULL, 1);
|
||||
} else {
|
||||
res = opus_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
res = opus_dec_chain_parse_data (dec, buf);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
res = opus_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_DURATION (buf));
|
||||
res = opus_dec_chain_parse_data (dec, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ struct _GstOpusDec {
|
|||
OpusDecoder *state;
|
||||
|
||||
guint64 packetno;
|
||||
GstClockTime next_ts;
|
||||
|
||||
GstBuffer *streamheader;
|
||||
GstBuffer *vorbiscomment;
|
||||
|
|
|
@ -94,9 +94,7 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
|
|||
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("audio/x-opus, "
|
||||
"rate = (int) { 8000, 12000, 16000, 24000, 48000 }, "
|
||||
"channels = (int) [ 1, 2 ], " "frame-size = (int) [ 2, 60 ]")
|
||||
GST_STATIC_CAPS ("audio/x-opus")
|
||||
);
|
||||
|
||||
#define DEFAULT_AUDIO TRUE
|
||||
|
@ -144,6 +142,9 @@ static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc,
|
|||
GstBuffer * buf);
|
||||
static GstFlowReturn gst_opus_enc_pre_push (GstAudioEncoder * benc,
|
||||
GstBuffer ** buffer);
|
||||
static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc);
|
||||
|
||||
static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buffer);
|
||||
|
||||
static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf);
|
||||
static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc);
|
||||
|
@ -157,13 +158,16 @@ static void
|
|||
gst_opus_enc_class_init (GstOpusEncClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *element_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstAudioEncoderClass *base_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
element_class = (GstElementClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
base_class = (GstAudioEncoderClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_opus_enc_set_property;
|
||||
gobject_class->get_property = gst_opus_enc_get_property;
|
||||
|
||||
gst_element_class_add_pad_template (element_class,
|
||||
gst_static_pad_template_get (&src_factory));
|
||||
gst_element_class_add_pad_template (element_class,
|
||||
|
@ -180,9 +184,6 @@ gst_opus_enc_class_init (GstOpusEncClass * klass)
|
|||
base_class->pre_push = GST_DEBUG_FUNCPTR (gst_opus_enc_pre_push);
|
||||
base_class->event = GST_DEBUG_FUNCPTR (gst_opus_enc_sink_event);
|
||||
|
||||
gobject_class->set_property = gst_opus_enc_set_property;
|
||||
gobject_class->get_property = gst_opus_enc_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_AUDIO,
|
||||
g_param_spec_boolean ("audio", "Audio or voice",
|
||||
"Audio or voice", DEFAULT_AUDIO,
|
||||
|
@ -205,7 +206,7 @@ gst_opus_enc_class_init (GstOpusEncClass * klass)
|
|||
"Constant bit rate", DEFAULT_CBR,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_CONSTRAINED_VBR,
|
||||
g_param_spec_boolean ("constrained-cbr", "Constrained VBR",
|
||||
g_param_spec_boolean ("constrained-vbr", "Constrained VBR",
|
||||
"Constrained VBR", DEFAULT_CONSTRAINED_VBR,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_COMPLEXITY,
|
||||
|
@ -237,12 +238,11 @@ gst_opus_enc_finalize (GObject * object)
|
|||
|
||||
enc = GST_OPUS_ENC (object);
|
||||
|
||||
GST_DEBUG_OBJECT (enc, "finalize");
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_opus_enc_init (GstOpusEnc * enc)
|
||||
gst_opus_enc_init (GstOpusEnc * enc, GstOpusEncClass * klass)
|
||||
{
|
||||
GstAudioEncoder *benc = GST_AUDIO_ENCODER (enc);
|
||||
|
||||
|
@ -273,7 +273,7 @@ gst_opus_enc_start (GstAudioEncoder * benc)
|
|||
GstOpusEnc *enc = GST_OPUS_ENC (benc);
|
||||
|
||||
GST_DEBUG_OBJECT (enc, "start");
|
||||
enc->tags = gst_tag_list_new_empty ();
|
||||
enc->tags = gst_tag_list_new ();
|
||||
enc->header_sent = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -297,6 +297,15 @@ gst_opus_enc_stop (GstAudioEncoder * benc)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gint64
|
||||
gst_opus_enc_get_latency (GstOpusEnc * enc)
|
||||
{
|
||||
gint64 latency = gst_util_uint64_scale (enc->frame_samples, GST_SECOND,
|
||||
enc->sample_rate);
|
||||
GST_DEBUG_OBJECT (enc, "Latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency));
|
||||
return latency;
|
||||
}
|
||||
|
||||
static gint
|
||||
gst_opus_enc_get_frame_samples (GstOpusEnc * enc)
|
||||
{
|
||||
|
@ -345,7 +354,6 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
|
|||
opus_encoder_destroy (enc->state);
|
||||
enc->state = NULL;
|
||||
}
|
||||
|
||||
if (!gst_opus_enc_setup (enc))
|
||||
return FALSE;
|
||||
|
||||
|
@ -354,7 +362,6 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
|
|||
/* feedback to base class */
|
||||
gst_audio_encoder_set_latency (benc,
|
||||
gst_opus_enc_get_latency (enc), gst_opus_enc_get_latency (enc));
|
||||
|
||||
gst_audio_encoder_set_frame_samples_min (benc,
|
||||
enc->frame_samples * enc->n_channels * 2);
|
||||
gst_audio_encoder_set_frame_samples_max (benc,
|
||||
|
@ -364,15 +371,6 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gint64
|
||||
gst_opus_enc_get_latency (GstOpusEnc * enc)
|
||||
{
|
||||
gint64 latency = gst_util_uint64_scale (enc->frame_samples, GST_SECOND,
|
||||
enc->sample_rate);
|
||||
GST_DEBUG_OBJECT (enc, "Latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency));
|
||||
return latency;
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
gst_opus_enc_create_id_buffer (GstOpusEnc * enc)
|
||||
{
|
||||
|
@ -495,8 +493,8 @@ gst_opus_enc_sink_event (GstAudioEncoder * benc, GstEvent * event)
|
|||
static GstFlowReturn
|
||||
gst_opus_enc_pre_push (GstAudioEncoder * benc, GstBuffer ** buffer)
|
||||
{
|
||||
GstOpusEnc *enc;
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
GstOpusEnc *enc;
|
||||
|
||||
enc = GST_OPUS_ENC (benc);
|
||||
|
||||
|
@ -522,6 +520,39 @@ gst_opus_enc_pre_push (GstAudioEncoder * benc, GstBuffer ** buffer)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf)
|
||||
{
|
||||
guint8 *bdata, *data, *mdata = NULL;
|
||||
gsize bsize, size;
|
||||
gsize bytes = enc->frame_samples * enc->n_channels * 2;
|
||||
gsize bytes_per_packet =
|
||||
(enc->bitrate * enc->frame_samples / enc->sample_rate + 4) / 8;
|
||||
gint ret = GST_FLOW_OK;
|
||||
|
||||
if (G_LIKELY (buf)) {
|
||||
bdata = GST_BUFFER_DATA (buf);
|
||||
bsize = GST_BUFFER_SIZE (buf);
|
||||
if (G_UNLIKELY (bsize % bytes)) {
|
||||
GST_DEBUG_OBJECT (enc, "draining; adding silence samples");
|
||||
|
||||
size = ((bsize / bytes) + 1) * bytes;
|
||||
mdata = g_malloc0 (size);
|
||||
memcpy (mdata, bdata, bsize);
|
||||
bdata = NULL;
|
||||
data = mdata;
|
||||
} else {
|
||||
data = bdata;
|
||||
size = bsize;
|
||||
}
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (enc, "nothing to drain");
|
||||
goto done;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf)
|
||||
{
|
||||
|
@ -669,7 +700,6 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
|
|||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
enc = GST_OPUS_ENC (benc);
|
||||
|
||||
GST_DEBUG_OBJECT (enc, "handle_frame");
|
||||
|
||||
if (!enc->header_sent) {
|
||||
|
@ -684,17 +714,13 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
|
|||
buf2 = gst_opus_enc_create_metadata_buffer (enc);
|
||||
|
||||
/* mark and put on caps */
|
||||
caps =
|
||||
gst_caps_new_simple ("audio/x-opus", "rate", G_TYPE_INT,
|
||||
enc->sample_rate, "channels", G_TYPE_INT, enc->n_channels, "frame-size",
|
||||
G_TYPE_INT, enc->frame_size, NULL);
|
||||
caps = gst_caps_from_string ("audio/x-opus");
|
||||
caps = _gst_caps_set_buffer_array (caps, "streamheader", buf1, buf2, NULL);
|
||||
|
||||
/* negotiate with these caps */
|
||||
GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
|
||||
|
||||
gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
/* push out buffers */
|
||||
/* store buffers for later pre_push sending */
|
||||
|
@ -703,12 +729,11 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
|
|||
GST_DEBUG_OBJECT (enc, "storing header buffers");
|
||||
enc->headers = g_slist_prepend (enc->headers, buf2);
|
||||
enc->headers = g_slist_prepend (enc->headers, buf1);
|
||||
|
||||
enc->header_sent = TRUE;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (enc, "received buffer %p of %u bytes", buf,
|
||||
buf ? gst_buffer_get_size (buf) : 0);
|
||||
buf ? GST_BUFFER_SIZE (buf) : 0);
|
||||
|
||||
ret = gst_opus_enc_encode (enc, buf);
|
||||
|
||||
|
|
|
@ -48,11 +48,7 @@ typedef struct _GstOpusEnc GstOpusEnc;
|
|||
typedef struct _GstOpusEncClass GstOpusEncClass;
|
||||
|
||||
struct _GstOpusEnc {
|
||||
GstAudioEncoder element;
|
||||
|
||||
/* pads */
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
GstAudioEncoder element;
|
||||
|
||||
OpusEncoder *state;
|
||||
|
||||
|
@ -74,7 +70,8 @@ struct _GstOpusEnc {
|
|||
|
||||
gboolean setup;
|
||||
gboolean header_sent;
|
||||
GSList *headers;
|
||||
|
||||
GSList *headers;
|
||||
|
||||
GstTagList *tags;
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ libgstvp8_la_LIBADD = \
|
|||
$(GST_PLUGINS_BASE_LIBS) -lgsttag-@GST_MAJORMINOR@ -lgstvideo-@GST_MAJORMINOR@ \
|
||||
$(GST_BASE_LIBS) $(GST_LIBS) $(VPX_LIBS)
|
||||
libgstvp8_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||
libgstvp8_la_LIBTOOLFLAGS = --tag=disable-static
|
||||
|
||||
noinst_HEADERS = \
|
||||
gstvp8dec.h \
|
||||
|
|
|
@ -681,8 +681,8 @@ gst_mpeg_video_parse_picture_header (GstMpegVideoPictureHdr * hdr,
|
|||
if (hdr->pic_type == 0 || hdr->pic_type > 4)
|
||||
goto failed; /* Corrupted picture packet */
|
||||
|
||||
/* skype VBV delay */
|
||||
if (!gst_bit_reader_skip (&br, 8))
|
||||
/* skip VBV delay */
|
||||
if (!gst_bit_reader_skip (&br, 16))
|
||||
goto failed;
|
||||
|
||||
if (hdr->pic_type == GST_MPEG_VIDEO_PICTURE_TYPE_P
|
||||
|
|
|
@ -38,7 +38,7 @@ G_BEGIN_DECLS
|
|||
/**
|
||||
* GstMpegVideoPacketTypeCode:
|
||||
* @GST_MPEG_VIDEO_PACKET_PICTURE: Picture packet starting code
|
||||
* @GST_MPEG_VIDEO_PACKET_SLICE_MIN: Picture packet starting code
|
||||
* @GST_MPEG_VIDEO_PACKET_SLICE_MIN: Slice min packet starting code
|
||||
* @GST_MPEG_VIDEO_PACKET_SLICE_MAX: Slice max packet starting code
|
||||
* @GST_MPEG_VIDEO_PACKET_USER_DATA: User data packet starting code
|
||||
* @GST_MPEG_VIDEO_PACKET_SEQUENCE : Sequence packet starting code
|
||||
|
@ -186,7 +186,7 @@ typedef struct _GstMpegVideoTypeOffsetSize GstMpegVideoTypeOffsetSize;
|
|||
* @width: Width of each frame
|
||||
* @height: Height of each frame
|
||||
* @par_w: Calculated Pixel Aspect Ratio width
|
||||
* @par_h: Pixel Aspect Ratio height
|
||||
* @par_h: Calculated Pixel Aspect Ratio height
|
||||
* @fps_n: Calculated Framrate nominator
|
||||
* @fps_d: Calculated Framerate denominator
|
||||
* @bitrate_value: Value of the bitrate as is in the stream (400bps unit)
|
||||
|
|
|
@ -84,6 +84,8 @@
|
|||
* unreffed or replaced by a new user set element. Initially only elements
|
||||
* needed for view finder mode are created to speed up startup. Image bin and
|
||||
* video bin elements are created when setting the mode or starting capture.
|
||||
* GstCameraBin must be in the PLAYING state before #GstCameraBin::capture-start
|
||||
* is called.
|
||||
* </para>
|
||||
* </refsect2>
|
||||
* <refsect2>
|
||||
|
|
|
@ -183,8 +183,10 @@
|
|||
|
||||
#define GST_CAMERA_BIN2_PROCESSING_DEC(c) \
|
||||
{ \
|
||||
if (g_atomic_int_dec_and_test (&c->processing_counter)) \
|
||||
if (g_atomic_int_dec_and_test (&c->processing_counter)) { \
|
||||
g_object_notify (G_OBJECT (c), "idle"); \
|
||||
GST_DEBUG_OBJECT ((c), "Camerabin now idle"); \
|
||||
} \
|
||||
GST_DEBUG_OBJECT ((c), "Processing counter decremented"); \
|
||||
}
|
||||
|
||||
|
@ -368,11 +370,24 @@ gst_camera_bin_start_capture (GstCameraBin2 * camerabin)
|
|||
GST_DEBUG_OBJECT (camerabin, "Received start-capture");
|
||||
|
||||
/* check that we have a valid location */
|
||||
if (camerabin->mode == MODE_VIDEO && camerabin->location == NULL) {
|
||||
GST_ELEMENT_ERROR (camerabin, RESOURCE, OPEN_WRITE,
|
||||
(_("File location is set to NULL, please set it to a valid filename")),
|
||||
(NULL));
|
||||
return;
|
||||
if (camerabin->mode == MODE_VIDEO) {
|
||||
if (camerabin->location == NULL) {
|
||||
GST_ELEMENT_ERROR (camerabin, RESOURCE, OPEN_WRITE,
|
||||
(_("File location is set to NULL, please set it to a valid filename")), (NULL));
|
||||
return;
|
||||
}
|
||||
|
||||
g_mutex_lock (camerabin->video_capture_mutex);
|
||||
while (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING) {
|
||||
g_cond_wait (camerabin->video_state_cond, camerabin->video_capture_mutex);
|
||||
}
|
||||
if (camerabin->video_state != GST_CAMERA_BIN_VIDEO_IDLE) {
|
||||
GST_WARNING_OBJECT (camerabin, "Another video recording is ongoing"
|
||||
" (state %d), cannot start a new one", camerabin->video_state);
|
||||
g_mutex_unlock (camerabin->video_capture_mutex);
|
||||
return;
|
||||
}
|
||||
camerabin->video_state = GST_CAMERA_BIN_VIDEO_STARTING;
|
||||
}
|
||||
|
||||
GST_CAMERA_BIN2_PROCESSING_INC (camerabin);
|
||||
|
@ -384,12 +399,6 @@ gst_camera_bin_start_capture (GstCameraBin2 * camerabin)
|
|||
if (camerabin->audio_src) {
|
||||
GstClock *clock = gst_pipeline_get_clock (GST_PIPELINE_CAST (camerabin));
|
||||
|
||||
/* need to reset eos status (pads could be flushing) */
|
||||
gst_element_set_state (camerabin->audio_capsfilter, GST_STATE_READY);
|
||||
gst_element_set_state (camerabin->audio_volume, GST_STATE_READY);
|
||||
|
||||
gst_element_sync_state_with_parent (camerabin->audio_capsfilter);
|
||||
gst_element_sync_state_with_parent (camerabin->audio_volume);
|
||||
gst_element_set_state (camerabin->audio_src, GST_STATE_PAUSED);
|
||||
|
||||
gst_element_set_base_time (camerabin->audio_src,
|
||||
|
@ -420,8 +429,13 @@ gst_camera_bin_start_capture (GstCameraBin2 * camerabin)
|
|||
}
|
||||
|
||||
g_signal_emit_by_name (camerabin->src, "start-capture", NULL);
|
||||
if (camerabin->mode == MODE_VIDEO && camerabin->audio_src)
|
||||
gst_element_set_state (camerabin->audio_src, GST_STATE_PLAYING);
|
||||
if (camerabin->mode == MODE_VIDEO) {
|
||||
if (camerabin->audio_src)
|
||||
gst_element_set_state (camerabin->audio_src, GST_STATE_PLAYING);
|
||||
|
||||
camerabin->video_state = GST_CAMERA_BIN_VIDEO_RECORDING;
|
||||
g_mutex_unlock (camerabin->video_capture_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to push tags after start capture because the video elements
|
||||
|
@ -458,12 +472,19 @@ static void
|
|||
gst_camera_bin_stop_capture (GstCameraBin2 * camerabin)
|
||||
{
|
||||
GST_DEBUG_OBJECT (camerabin, "Received stop-capture");
|
||||
if (camerabin->src)
|
||||
g_signal_emit_by_name (camerabin->src, "stop-capture", NULL);
|
||||
if (camerabin->mode == MODE_VIDEO) {
|
||||
g_mutex_lock (camerabin->video_capture_mutex);
|
||||
if (camerabin->video_state == GST_CAMERA_BIN_VIDEO_RECORDING) {
|
||||
if (camerabin->src)
|
||||
g_signal_emit_by_name (camerabin->src, "stop-capture", NULL);
|
||||
|
||||
if (camerabin->mode == MODE_VIDEO && camerabin->audio_src) {
|
||||
camerabin->audio_drop_eos = FALSE;
|
||||
gst_element_send_event (camerabin->audio_src, gst_event_new_eos ());
|
||||
camerabin->video_state = GST_CAMERA_BIN_VIDEO_FINISHING;
|
||||
if (camerabin->audio_src) {
|
||||
camerabin->audio_drop_eos = FALSE;
|
||||
gst_element_send_event (camerabin->audio_src, gst_event_new_eos ());
|
||||
}
|
||||
}
|
||||
g_mutex_unlock (camerabin->video_capture_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,11 +515,8 @@ gst_camera_bin_src_notify_readyforcapture (GObject * obj, GParamSpec * pspec,
|
|||
gchar *location = NULL;
|
||||
|
||||
if (camera->mode == MODE_VIDEO) {
|
||||
/* a video recording is about to start, we reset the videobin to clear eos/flushing state
|
||||
* also need to clean the queue ! capsfilter before it */
|
||||
/* a video recording is about to start, change the filesink location */
|
||||
gst_element_set_state (camera->videosink, GST_STATE_NULL);
|
||||
gst_element_set_state (camera->video_encodebin, GST_STATE_NULL);
|
||||
gst_element_set_state (camera->videobin_capsfilter, GST_STATE_NULL);
|
||||
location = g_strdup_printf (camera->location, camera->capture_index);
|
||||
GST_DEBUG_OBJECT (camera, "Switching videobin location to %s", location);
|
||||
g_object_set (camera->videosink, "location", location, NULL);
|
||||
|
@ -509,21 +527,9 @@ gst_camera_bin_src_notify_readyforcapture (GObject * obj, GParamSpec * pspec,
|
|||
* and could cause problems in a camerabin2 state change */
|
||||
gst_element_set_state (camera->videosink, GST_STATE_NULL);
|
||||
}
|
||||
gst_element_set_state (camera->video_encodebin, GST_STATE_PLAYING);
|
||||
gst_element_set_state (camera->videobin_capsfilter, GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
camera->capture_index++;
|
||||
} else {
|
||||
if (camera->mode == MODE_VIDEO && camera->audio_src) {
|
||||
/* FIXME We need to set audiosrc to null to make it resync the ringbuffer
|
||||
* while bug https://bugzilla.gnome.org/show_bug.cgi?id=648359 isn't
|
||||
* fixed.
|
||||
*
|
||||
* Also, we set to NULL here to stop capturing audio through to the next
|
||||
* video mode start capture and to clear EOS. */
|
||||
gst_element_set_state (camera->audio_src, GST_STATE_NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,6 +541,8 @@ gst_camera_bin_dispose (GObject * object)
|
|||
g_free (camerabin->location);
|
||||
g_mutex_free (camerabin->preview_list_mutex);
|
||||
g_mutex_free (camerabin->image_capture_mutex);
|
||||
g_mutex_free (camerabin->video_capture_mutex);
|
||||
g_cond_free (camerabin->video_state_cond);
|
||||
|
||||
if (camerabin->src_capture_notify_id)
|
||||
g_signal_handler_disconnect (camerabin->src,
|
||||
|
@ -892,6 +900,8 @@ gst_camera_bin_init (GstCameraBin2 * camera)
|
|||
camera->flags = DEFAULT_FLAGS;
|
||||
camera->preview_list_mutex = g_mutex_new ();
|
||||
camera->image_capture_mutex = g_mutex_new ();
|
||||
camera->video_capture_mutex = g_mutex_new ();
|
||||
camera->video_state_cond = g_cond_new ();
|
||||
|
||||
/* capsfilters are created here as we proxy their caps properties and
|
||||
* this way we avoid having to store the caps while on NULL state to
|
||||
|
@ -963,6 +973,46 @@ gst_camera_bin_skip_next_preview (GstCameraBin2 * camerabin)
|
|||
g_mutex_unlock (camerabin->preview_list_mutex);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gst_camera_bin_video_reset_elements (gpointer u_data)
|
||||
{
|
||||
GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (u_data);
|
||||
|
||||
GST_DEBUG_OBJECT (camerabin, "Resetting video elements state");
|
||||
g_mutex_lock (camerabin->video_capture_mutex);
|
||||
|
||||
/* reset element states to clear eos/flushing pads */
|
||||
gst_element_set_state (camerabin->video_encodebin, GST_STATE_READY);
|
||||
gst_element_set_state (camerabin->videobin_capsfilter, GST_STATE_READY);
|
||||
gst_element_sync_state_with_parent (camerabin->videobin_capsfilter);
|
||||
gst_element_sync_state_with_parent (camerabin->video_encodebin);
|
||||
|
||||
if (camerabin->audio_src) {
|
||||
gst_element_set_state (camerabin->audio_capsfilter, GST_STATE_READY);
|
||||
gst_element_set_state (camerabin->audio_volume, GST_STATE_READY);
|
||||
|
||||
/* FIXME We need to set audiosrc to null to make it resync the ringbuffer
|
||||
* while bug https://bugzilla.gnome.org/show_bug.cgi?id=648359 isn't
|
||||
* fixed.
|
||||
*
|
||||
* Also, we don't reinit the audiosrc to keep audio devices from being open
|
||||
* and running until we really need them */
|
||||
gst_element_set_state (camerabin->audio_src, GST_STATE_NULL);
|
||||
|
||||
gst_element_sync_state_with_parent (camerabin->audio_capsfilter);
|
||||
gst_element_sync_state_with_parent (camerabin->audio_volume);
|
||||
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (camerabin, "Setting video state to idle");
|
||||
camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
|
||||
g_cond_signal (camerabin->video_state_cond);
|
||||
g_mutex_unlock (camerabin->video_capture_mutex);
|
||||
|
||||
gst_object_unref (camerabin);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_camera_bin_handle_message (GstBin * bin, GstMessage * message)
|
||||
{
|
||||
|
@ -1035,9 +1085,24 @@ gst_camera_bin_handle_message (GstBin * bin, GstMessage * message)
|
|||
case GST_MESSAGE_EOS:{
|
||||
GstElement *src = GST_ELEMENT (GST_MESSAGE_SRC (message));
|
||||
if (src == GST_CAMERA_BIN2_CAST (bin)->videosink) {
|
||||
|
||||
g_mutex_lock (camerabin->video_capture_mutex);
|
||||
GST_DEBUG_OBJECT (bin, "EOS from video branch");
|
||||
g_assert (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING);
|
||||
|
||||
gst_video_capture_bin_post_video_done (GST_CAMERA_BIN2_CAST (bin));
|
||||
dec_counter = TRUE;
|
||||
|
||||
if (!g_thread_create (gst_camera_bin_video_reset_elements,
|
||||
gst_object_ref (camerabin), FALSE, NULL)) {
|
||||
GST_WARNING_OBJECT (camerabin, "Failed to create thread to "
|
||||
"reset video elements' state, video recordings may not work "
|
||||
"anymore");
|
||||
gst_object_unref (camerabin);
|
||||
camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
|
||||
}
|
||||
|
||||
g_mutex_unlock (camerabin->video_capture_mutex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1810,6 +1875,7 @@ gst_camera_bin_change_state (GstElement * element, GstStateChange trans)
|
|||
|
||||
gst_tag_setter_reset_tags (GST_TAG_SETTER (camera));
|
||||
GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER (camera);
|
||||
camera->video_state = GST_CAMERA_BIN_VIDEO_IDLE;
|
||||
|
||||
g_mutex_lock (camera->image_capture_mutex);
|
||||
g_slist_foreach (camera->image_location_list, (GFunc) g_free, NULL);
|
||||
|
|
|
@ -45,6 +45,14 @@ typedef enum
|
|||
} GstCamFlags;
|
||||
|
||||
|
||||
typedef enum _GstCameraBinVideoState
|
||||
{
|
||||
GST_CAMERA_BIN_VIDEO_IDLE=0,
|
||||
GST_CAMERA_BIN_VIDEO_STARTING=1,
|
||||
GST_CAMERA_BIN_VIDEO_RECORDING=2,
|
||||
GST_CAMERA_BIN_VIDEO_FINISHING=3
|
||||
} GstCameraBinVideoState;
|
||||
|
||||
typedef struct _GstCameraBin2 GstCameraBin2;
|
||||
typedef struct _GstCameraBin2Class GstCameraBin2Class;
|
||||
|
||||
|
@ -119,6 +127,10 @@ struct _GstCameraBin2
|
|||
|
||||
gboolean audio_drop_eos;
|
||||
|
||||
GMutex *video_capture_mutex;
|
||||
GCond *video_state_cond;
|
||||
GstCameraBinVideoState video_state;
|
||||
|
||||
/* properties */
|
||||
gint mode;
|
||||
gchar *location;
|
||||
|
|
|
@ -45,8 +45,7 @@ static GstStaticPadTemplate sink_template =
|
|||
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/mpeg, "
|
||||
"mpegversion = (int) 4, "
|
||||
"parsed = (boolean) false, " "systemstream = (boolean) false")
|
||||
"mpegversion = (int) 4, " "systemstream = (boolean) false")
|
||||
);
|
||||
|
||||
/* Properties */
|
||||
|
@ -634,7 +633,7 @@ plugin_init (GstPlugin * plugin)
|
|||
GST_DEBUG_CATEGORY_INIT (mpeg4v_parse_debug, "mpeg4videoparse", 0,
|
||||
"MPEG-4 video parser");
|
||||
|
||||
if (!gst_element_register (plugin, "mpeg4videoparse", GST_RANK_SECONDARY,
|
||||
if (!gst_element_register (plugin, "mpeg4videoparse", GST_RANK_PRIMARY + 1,
|
||||
gst_mpeg4vparse_get_type ()))
|
||||
return FALSE;
|
||||
|
||||
|
|
|
@ -1,117 +1,151 @@
|
|||
mpegtsparse rebasing
|
||||
tsdemux/tsparse TODO
|
||||
--------------------
|
||||
|
||||
Rationale :
|
||||
-----------
|
||||
* clock for live streams
|
||||
In order for playback to happen at the same rate as on the producer,
|
||||
we need to estimate the remote clock based on capture time and PCR
|
||||
values.
|
||||
For this estimation to be as accurate as possible, the capture time
|
||||
needs to happen on the sources.
|
||||
=> Ensure live sources actually timestamp their buffers
|
||||
Once we have accurate timestamps, we can use an algorithm to
|
||||
calculate the PCR/local-clock skew.
|
||||
=> Use the EPTLA algorithm as used in -good/rtp/rtpmanager/
|
||||
gstrtpjitterbuffer
|
||||
|
||||
mpegtsparse code is more sane to handle and work with.
|
||||
* Seeking
|
||||
=> Split out in a separate file/object. It is polluting tsdemux for
|
||||
code readability/clarity.
|
||||
|
||||
We need a modular demuxer
|
||||
|
||||
We need to avoid duplicating code regarding mpeg-ts in a gazillion
|
||||
elements and allow easy creatiof new elements.
|
||||
|
||||
|
||||
Battleplan :
|
||||
------------
|
||||
* Figure out code from mpegtsparse which would be also needed for a
|
||||
mpeg-ts demuxer (ex: packet/psi/pcr parsing).
|
||||
* Extract common code into a base mpegtsbase class.
|
||||
* Refactor mpegtsparse to subclass that base class.
|
||||
* Create a minimalistic demuxer that creates pads (based on PSI info)
|
||||
and outputs ES packets (let's say mpeg audio and video to start with)
|
||||
|
||||
Potential subclasses :
|
||||
----------------------
|
||||
* MpegTSParse : Program splitter. Given an incoming multi-program
|
||||
mpeg-ts stream, it can provide request pads for each program. Each
|
||||
of those pads will contain the ts packets specific to that program.
|
||||
|
||||
* TSDemux : Program demuxer. Given an incoming single or multi-program
|
||||
mpeg-ts stream, it will reconstruct the original Program Streams of
|
||||
the selected program and output them on dynamically created pads.
|
||||
|
||||
* HDVSplitter : Given an incoming HDV mpeg-ts stream, it will locate
|
||||
the beginning of new scenes and output a mpeg-ts stream with the
|
||||
PAT/PMT/AUX packets properly ordered and marked with DISCONT, so
|
||||
that the following pipeline will automatically cut up a tape dump
|
||||
into individual scenes:
|
||||
filesrc ! hdvsplit ! multifilesink next-file=discont
|
||||
|
||||
Code/Design common to a program-spliter and a demuxer :
|
||||
-------------------------------------------------------
|
||||
* Parsing TS packets
|
||||
* Establishing PAT/PMT mapping
|
||||
* Handling the notions of Programs/Streams
|
||||
* Seeking ?
|
||||
|
||||
One proposal... would be to have the base class automatically create
|
||||
all the structures (and relationships) for the following objects:
|
||||
|
||||
* Programs (from PAT/PMT, dunno if it could come from something
|
||||
else)
|
||||
* Program id
|
||||
* Streams contained in that program (with links to them)
|
||||
* Which stream contains the PCR
|
||||
* Metadata ?
|
||||
* Streams (ideally... in a table for fast access)
|
||||
* We want to be able to have stream-type specific information
|
||||
easily accessible also (like mpeg video specific data)
|
||||
* Maybe some other info ???
|
||||
|
||||
The subclasses would then be able to make their own decision based
|
||||
on those objects.
|
||||
Maybe we could have some virtual methods that will be called when a
|
||||
new program is detected, a new stream is added, etc...
|
||||
|
||||
It is the subclass who decides what's to do with a given packet once
|
||||
it's been parsed.
|
||||
tsparse : forward it as-is to the pad corresponding to the program
|
||||
tsdemux : forward it to the proper PS parser
|
||||
hdvsplit : ?
|
||||
|
||||
|
||||
Ideas to be taken into account for a proper demuxer :
|
||||
-----------------------------------------------------
|
||||
* Push-based (with inacurrate seeking)
|
||||
* Pull-based (with fast *AND* accurate seeking)
|
||||
* Modular system to add stream-type specific helper parsing
|
||||
* Doesn't have to be fully fledged, just enough to help any kind of
|
||||
seeking and scanning code.
|
||||
* ...
|
||||
|
||||
Problems to figure out :
|
||||
------------------------
|
||||
* clock
|
||||
Needed for proper dvb playback. mpegtsdemux currently does internal
|
||||
clock estimation... to provide a clock with PCR estimations.
|
||||
A proper way to solve that would be to timestamp the buffers at the
|
||||
source element using the system clock, and then adjusting the PCR
|
||||
against those values. (i.e. doing the opposite of what's done in
|
||||
mpegtsdemux, but it will be more accurate since the timestamping is
|
||||
done at the source).
|
||||
|
||||
|
||||
Bugs that need fixing :
|
||||
-----------------------
|
||||
* Perfomance : Creation/Destruction of buffers is slow
|
||||
* => This is due to g_type_instance_create using a dogslow rwlock
|
||||
which take up to 50% of gst_adapter_take_buffer()
|
||||
=> Bugzilla #585375 (performance and contention problems)
|
||||
|
||||
Code structure:
|
||||
|
||||
MpegTSBase
|
||||
+--- MpegTSParse
|
||||
+--- TSDemux
|
||||
|
||||
|
||||
Known limitations and problems :
|
||||
--------------------------------
|
||||
* mpegtspacketizer
|
||||
* Assumes 188 bytes packets. It should support all modes.
|
||||
* offset/timestamp of incoming buffers need to be carried on to the
|
||||
sub-buffers in order for several demuxer features to work correctly.
|
||||
|
||||
* mpegtsparser
|
||||
* SERIOUS room for improvement performance-wise (see callgrind)
|
||||
|
||||
|
||||
|
||||
|
||||
Synchronization, Scheduling and Timestamping
|
||||
--------------------------------------------
|
||||
|
||||
A mpeg-ts demuxer can be used in a variety of situations:
|
||||
* lives streaming over DVB, UDP, RTP,..
|
||||
* play-as-you-download like HTTP Live Streaming or UPNP/DLNA
|
||||
* random-access local playback, file, Bluray, ...
|
||||
|
||||
Those use-cases can be categorized in 3 different categories:
|
||||
* Push-based scheduling with live sources [0]
|
||||
* Push-based scheduling with non-live sources
|
||||
* Pull-based scheduling with fast random-access
|
||||
|
||||
Due to the nature of timing within the mpeg-ts format, we need to
|
||||
pay extra attention to the outgoing NEWSEGMENT event and buffer
|
||||
timestamps in order to guarantee proper playback and synchronization
|
||||
of the stream.
|
||||
|
||||
|
||||
1) Live push-based scheduling
|
||||
|
||||
The NEWSEGMENT event will be in time format and is forwarded as is,
|
||||
and the values are cached locally.
|
||||
|
||||
Since the clock is running when the upstream buffers are captured,
|
||||
the outgoing buffer timestamps need to correspond to the incoming
|
||||
buffer timestamp values.
|
||||
|
||||
=> A delta, DTS_delta between incoming buffer timestamp and
|
||||
DTS/PTS needs to be computed.
|
||||
|
||||
=> The outgoing buffers will be timestamped with their PTS values
|
||||
(overflow corrected) offseted by that initial DTS_delta.
|
||||
|
||||
A latency is introduced between the time the buffer containing the
|
||||
first bit of a Access Unit is received in the demuxer and the moment
|
||||
the demuxer pushed out the buffer corresponding to that Access Unit.
|
||||
|
||||
=> That latency needs to be reported. It corresponds to the
|
||||
biggest Access Unit spacing, in this case 1/video-framerate.
|
||||
|
||||
According to the ISO/IEC 13818-1:2007 specifications, D.0.1 Timing
|
||||
mode, the "coded audio and video that represent sound and pictures
|
||||
that are to be presented simultaneously may be separated in time
|
||||
within the coded bit stream by ==>as much as one second<=="
|
||||
|
||||
=> The demuxer will therefore report an added latency of 1s to
|
||||
handle this interleave.
|
||||
|
||||
|
||||
2) Non-live push-based scheduling
|
||||
|
||||
If the upstream NEWSEGMENT is in time format, the NEWSEGMENT event
|
||||
is forwarded as is, and the values are cached locally.
|
||||
|
||||
If upstream does provide a NEWSEGMENT in another format, we need to
|
||||
compute one by taking the default values:
|
||||
start : 0
|
||||
stop : GST_CLOCK_TIME_NONE
|
||||
time : 0
|
||||
|
||||
Since no prerolling is happening downstream and the incoming buffers
|
||||
do not have capture timestamps, we need to ensure the first buffer
|
||||
we push out corresponds to the base segment start runing time.
|
||||
|
||||
=> A delta between the first DTS to output and the segment start
|
||||
position needs to be computed.
|
||||
|
||||
=> The outgoing buffers will be timestamped with their PTS values
|
||||
(overflow corrected) offseted by that initial delta.
|
||||
|
||||
Latency is reported just as with the live use-case.
|
||||
|
||||
|
||||
3) Random access pull-mode
|
||||
|
||||
We do not get a NEWSEGMENT event from upstream, we therefore need to
|
||||
compute the outgoing values.
|
||||
|
||||
The base stream/running time corresponds to the DTS of the first
|
||||
buffer we will output. The DTS_delta becomes that earliest DTS.
|
||||
|
||||
=> FILLME
|
||||
|
||||
X) General notes
|
||||
|
||||
It is assumed that PTS/DTS rollovers are detected and corrected such
|
||||
as the outgoing timestamps never rollover. This can be easily
|
||||
handled by correcting the DTS_delta when such rollovers are
|
||||
detected. The maximum value of a GstClockTimeDiff is almost 3
|
||||
centuries, we therefore have enough margin to handle a decent number
|
||||
of rollovers.
|
||||
|
||||
The generic equation for calculating outgoing buffer timestamps
|
||||
therefore becomes:
|
||||
|
||||
D = DTS_delta, with rollover corrections
|
||||
PTS = PTS of the buffer we are going to push out
|
||||
TS = Timestamp of the outgoing buffer
|
||||
|
||||
==> TS = PTS + D
|
||||
|
||||
If seeking is handled upstream for push-based cases, whether live or
|
||||
not, no extra modification is required.
|
||||
|
||||
If seeking is handled by the demuxer in the non-live push-based
|
||||
cases (converting from TIME to BYTES), the demuxer will need to
|
||||
set the segment start/time values to the requested seek position.
|
||||
The DTS_delta will also have to be recomputed to take into account
|
||||
the seek position.
|
||||
|
||||
|
||||
[0] When talking about live sources, we mean this in the GStreamer
|
||||
definition of live sources, which is to say sources where if we miss
|
||||
the capture, we will miss the data to be captured. Sources which do
|
||||
internal buffering (like TCP connections or file descriptors) are
|
||||
*NOT* live sources.
|
||||
|
|
|
@ -44,6 +44,12 @@
|
|||
#include "payload_parsers.h"
|
||||
#include "pesparse.h"
|
||||
|
||||
/*
|
||||
* tsdemux
|
||||
*
|
||||
* See TODO for explanations on improvements needed
|
||||
*/
|
||||
|
||||
/* latency in mseconds */
|
||||
#define TS_LATENCY 700
|
||||
|
||||
|
@ -74,8 +80,6 @@ static GQuark QUARK_PTS;
|
|||
static GQuark QUARK_DTS;
|
||||
static GQuark QUARK_OFFSET;
|
||||
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PENDING_PACKET_EMPTY = 0, /* No pending packet/buffer
|
||||
|
|
|
@ -91,6 +91,7 @@ static void gst_h264_parse_get_property (GObject * object, guint prop_id,
|
|||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static gboolean gst_h264_parse_set_caps (GstBaseParse * parse, GstCaps * caps);
|
||||
static GstCaps *gst_h264_parse_get_caps (GstBaseParse * parse);
|
||||
static GstFlowReturn gst_h264_parse_chain (GstPad * pad, GstBuffer * buffer);
|
||||
|
||||
static void
|
||||
|
@ -138,6 +139,7 @@ gst_h264_parse_class_init (GstH264ParseClass * klass)
|
|||
parse_class->pre_push_frame =
|
||||
GST_DEBUG_FUNCPTR (gst_h264_parse_pre_push_frame);
|
||||
parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_h264_parse_set_caps);
|
||||
parse_class->get_sink_caps = GST_DEBUG_FUNCPTR (gst_h264_parse_get_caps);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -337,7 +339,7 @@ gst_h264_parse_wrap_nal (GstH264Parse * h264parse, guint format, guint8 * data,
|
|||
guint size)
|
||||
{
|
||||
GstBuffer *buf;
|
||||
const guint nl = h264parse->nal_length_size;
|
||||
guint nl = h264parse->nal_length_size;
|
||||
|
||||
GST_DEBUG_OBJECT (h264parse, "nal length %d", size);
|
||||
|
||||
|
@ -345,7 +347,10 @@ gst_h264_parse_wrap_nal (GstH264Parse * h264parse, guint format, guint8 * data,
|
|||
if (format == GST_H264_PARSE_FORMAT_AVC) {
|
||||
GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), size << (32 - 8 * nl));
|
||||
} else {
|
||||
g_assert (nl == 4);
|
||||
/* HACK: nl should always be 4 here, otherwise this won't work.
|
||||
* There are legit cases where nl in avc stream is 2, but byte-stream
|
||||
* SC is still always 4 bytes. */
|
||||
nl = 4;
|
||||
GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), 1);
|
||||
}
|
||||
|
||||
|
@ -1333,6 +1338,38 @@ refuse_caps:
|
|||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_h264_parse_get_caps (GstBaseParse * parse)
|
||||
{
|
||||
GstCaps *peercaps;
|
||||
GstCaps *res;
|
||||
|
||||
peercaps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (parse));
|
||||
if (peercaps) {
|
||||
guint i, n;
|
||||
|
||||
peercaps = gst_caps_make_writable (peercaps);
|
||||
n = gst_caps_get_size (peercaps);
|
||||
for (i = 0; i < n; i++) {
|
||||
GstStructure *s = gst_caps_get_structure (peercaps, i);
|
||||
gst_structure_remove_field (s, "alignment");
|
||||
gst_structure_remove_field (s, "stream-format");
|
||||
}
|
||||
|
||||
res =
|
||||
gst_caps_intersect_full (peercaps,
|
||||
gst_pad_get_pad_template_caps (GST_BASE_PARSE_SRC_PAD (parse)),
|
||||
GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (peercaps);
|
||||
} else {
|
||||
res =
|
||||
gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_PARSE_SRC_PAD
|
||||
(parse)));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_h264_parse_chain (GstPad * pad, GstBuffer * buffer)
|
||||
{
|
||||
|
|
|
@ -11,4 +11,4 @@ libgstacmmp3dec_la_LIBADD = \
|
|||
-lgsttag-$(GST_MAJORMINOR) \
|
||||
-lmsacm32
|
||||
libgstacmmp3dec_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(DIRECTSOUND_LDFLAGS)
|
||||
|
||||
libgstacmmp3dec_la_LIBTOOLFLAGS = --tag=disable-static
|
||||
|
|
|
@ -23,4 +23,4 @@ libgstlinsys_la_CFLAGS = \
|
|||
libgstlinsys_la_LDFLAGS = \
|
||||
$(GST_PLUGIN_LDFLAGS)
|
||||
libgstlinsys_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS)
|
||||
|
||||
libgstlinsys_la_LIBTOOLFLAGS = --tag=disable-static
|
||||
|
|
|
@ -622,6 +622,22 @@ wait_for_element_message (GstElement * camera, const gchar * name,
|
|||
return msg;
|
||||
}
|
||||
|
||||
static void
|
||||
wait_for_idle_state (void)
|
||||
{
|
||||
gboolean idle = FALSE;
|
||||
|
||||
/* not the ideal way, but should be enough for testing */
|
||||
while (idle == FALSE) {
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
if (idle)
|
||||
break;
|
||||
|
||||
g_usleep (GST_SECOND / 5);
|
||||
}
|
||||
fail_unless (idle);
|
||||
}
|
||||
|
||||
GST_START_TEST (test_single_image_capture)
|
||||
{
|
||||
gboolean idle;
|
||||
|
@ -652,8 +668,7 @@ GST_START_TEST (test_single_image_capture)
|
|||
/* check that we got a preview image */
|
||||
check_preview_image (camera, image_filename, 0);
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
check_file_validity (image_filename, 0, NULL, 0, 0, NO_AUDIO);
|
||||
}
|
||||
|
@ -705,8 +720,7 @@ GST_START_TEST (test_multiple_image_captures)
|
|||
check_preview_image (camera, image_filename, i);
|
||||
}
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
for (i = 0; i < 3; i++) {
|
||||
check_file_validity (image_filename, i, NULL, widths[i], heights[i],
|
||||
|
@ -756,8 +770,7 @@ GST_START_TEST (test_single_video_recording)
|
|||
fail_unless (msg != NULL);
|
||||
gst_message_unref (msg);
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
|
||||
check_file_validity (video_filename, 0, NULL, 0, 0, WITH_AUDIO);
|
||||
|
@ -819,8 +832,7 @@ GST_START_TEST (test_multiple_video_recordings)
|
|||
|
||||
check_preview_image (camera, video_filename, i);
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
}
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
|
||||
|
@ -834,7 +846,6 @@ GST_END_TEST;
|
|||
|
||||
GST_START_TEST (test_image_video_cycle)
|
||||
{
|
||||
gboolean idle;
|
||||
gint i;
|
||||
|
||||
if (!camera)
|
||||
|
@ -854,8 +865,7 @@ GST_START_TEST (test_image_video_cycle)
|
|||
const gchar *img_filename;
|
||||
const gchar *vid_filename;
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
|
||||
/* take a picture */
|
||||
img_filename = make_const_file_name (image_filename, i);
|
||||
|
@ -885,10 +895,9 @@ GST_START_TEST (test_image_video_cycle)
|
|||
gst_message_unref (msg);
|
||||
|
||||
check_preview_image (camera, vid_filename, i);
|
||||
|
||||
/* wait for capture to finish */
|
||||
g_usleep (G_USEC_PER_SEC);
|
||||
}
|
||||
|
||||
wait_for_idle_state ();
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
|
||||
/* validate all the files */
|
||||
|
@ -1193,8 +1202,7 @@ GST_START_TEST (test_idle_property)
|
|||
|
||||
check_preview_image (camera, video_filename, 0);
|
||||
|
||||
g_object_get (camera, "idle", &idle, NULL);
|
||||
fail_unless (idle);
|
||||
wait_for_idle_state ();
|
||||
|
||||
gst_element_set_state (GST_ELEMENT (camera), GST_STATE_NULL);
|
||||
|
||||
|
|
Loading…
Reference in a new issue