Merge branch 'master' into 0.11

Conflicts:
	ext/theora/gsttheoraenc.c
This commit is contained in:
Wim Taymans 2011-06-06 16:27:12 +02:00
commit c88ee10c9b
6 changed files with 243 additions and 95 deletions

View file

@ -62,11 +62,6 @@ GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
? GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf) \ ? GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf) \
: GST_BUFFER_TIMESTAMP (buf)) : GST_BUFFER_TIMESTAMP (buf))
#define GST_BUFFER_RUNNING_TIME(buf, oggpad) \
(GST_BUFFER_DURATION_IS_VALID (buf) \
? gst_segment_to_running_time (&(oggpad)->segment, GST_FORMAT_TIME, \
GST_BUFFER_TIMESTAMP (buf)) : 0)
#define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]" #define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
#define GST_GP_CAST(_gp) ((gint64) _gp) #define GST_GP_CAST(_gp) ((gint64) _gp)
@ -87,11 +82,13 @@ enum
/* set to 0.5 seconds by default */ /* set to 0.5 seconds by default */
#define DEFAULT_MAX_DELAY G_GINT64_CONSTANT(500000000) #define DEFAULT_MAX_DELAY G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_PAGE_DELAY G_GINT64_CONSTANT(500000000) #define DEFAULT_MAX_PAGE_DELAY G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_TOLERANCE G_GINT64_CONSTANT(40000000)
enum enum
{ {
ARG_0, ARG_0,
ARG_MAX_DELAY, ARG_MAX_DELAY,
ARG_MAX_PAGE_DELAY, ARG_MAX_PAGE_DELAY,
ARG_MAX_TOLERANCE
}; };
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
@ -167,6 +164,11 @@ gst_ogg_mux_class_init (GstOggMuxClass * klass)
"Maximum delay for sending out a page", 0, G_MAXUINT64, "Maximum delay for sending out a page", 0, G_MAXUINT64,
DEFAULT_MAX_PAGE_DELAY, DEFAULT_MAX_PAGE_DELAY,
(GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
"Maximum timestamp difference for maintaining perfect granules",
0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
(GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gst_ogg_mux_change_state; gstelement_class->change_state = gst_ogg_mux_change_state;
@ -220,6 +222,7 @@ gst_ogg_mux_init (GstOggMux * ogg_mux)
ogg_mux->max_delay = DEFAULT_MAX_DELAY; ogg_mux->max_delay = DEFAULT_MAX_DELAY;
ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY; ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
gst_ogg_mux_clear (ogg_mux); gst_ogg_mux_clear (ogg_mux);
} }
@ -403,6 +406,8 @@ gst_ogg_mux_request_new_pad (GstElement * element,
oggpad->pagebuffers = g_queue_new (); oggpad->pagebuffers = g_queue_new ();
oggpad->map.headers = NULL; oggpad->map.headers = NULL;
oggpad->map.queued = NULL; oggpad->map.queued = NULL;
oggpad->next_granule = 0;
oggpad->keyframe_granule = -1;
gst_segment_init (&oggpad->segment, GST_FORMAT_TIME); gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
@ -500,7 +505,7 @@ gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
/* Ensure we have monotonically increasing timestamps in the output. */ /* Ensure we have monotonically increasing timestamps in the output. */
if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
gint64 run_time = GST_BUFFER_RUNNING_TIME (buffer, oggpad); gint64 run_time = GST_BUFFER_TIMESTAMP (buffer);
if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts) if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts; GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
else else
@ -699,11 +704,6 @@ gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
if (secondtime == GST_CLOCK_TIME_NONE) if (secondtime == GST_CLOCK_TIME_NONE)
return 1; return 1;
firsttime = gst_segment_to_running_time (&first->segment, GST_FORMAT_TIME,
firsttime);
secondtime = gst_segment_to_running_time (&second->segment, GST_FORMAT_TIME,
secondtime);
/* first buffer has higher timestamp, second one should go first */ /* first buffer has higher timestamp, second one should go first */
if (secondtime < firsttime) if (secondtime < firsttime)
return 1; return 1;
@ -723,6 +723,116 @@ gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
return 0; return 0;
} }
static GstBuffer *
gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
GstBuffer * buf)
{
GstClockTime time;
gint64 duration, granule, limit;
GstClockTime next_time;
GstClockTimeDiff diff;
ogg_packet packet;
gsize size;
/* ensure messing with metadata is ok */
buf = gst_buffer_make_writable (buf);
/* convert time to running time, so we need no longer bother about that */
time = GST_BUFFER_TIMESTAMP (buf);
if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
time = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME, time);
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
gst_buffer_unref (buf);
return NULL;
} else {
GST_BUFFER_TIMESTAMP (buf) = time;
}
}
/* now come up with granulepos stuff corresponding to time */
if (!pad->have_type ||
pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
goto no_granule;
packet.packet = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
packet.bytes = size;
duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
gst_buffer_unmap (buf, packet.packet, size);
/* give up if no duration can be determined, relying on upstream */
if (G_UNLIKELY (duration < 0)) {
/* well, if some day we really could handle sparse input ... */
if (pad->map.is_sparse) {
limit = 1;
diff = 2;
goto resync;
}
GST_WARNING_OBJECT (pad->collect.pad,
"failed to determine packet duration");
goto no_granule;
}
GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
duration);
/* determine granule corresponding to time,
* using the inverse of oggdemux' granule -> time */
/* see if interpolated granule matches good enough */
granule = pad->next_granule;
next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
diff = GST_CLOCK_DIFF (next_time, time);
/* we tolerate deviation up to configured or within granule granularity */
limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
limit = MAX (limit, ogg_mux->max_tolerance);
GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
"time %" GST_TIME_FORMAT " --> ts diff %" GST_TIME_FORMAT
" < tolerance %" GST_TIME_FORMAT " (?)",
granule, GST_TIME_ARGS (next_time), GST_TIME_ARGS (ABS (diff)),
GST_TIME_ARGS (limit));
resync:
/* if not good enough, determine granule based on time */
if (diff > limit || diff < -limit) {
granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
GST_SECOND * pad->map.granulerate_d);
GST_DEBUG_OBJECT (pad->collect.pad,
"resyncing to determined granule %" G_GINT64_FORMAT, granule);
}
if (pad->map.is_ogm || pad->map.is_sparse) {
pad->next_granule = granule;
} else {
granule += duration;
pad->next_granule = granule;
}
/* track previous keyframe */
if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
pad->keyframe_granule = granule;
/* determine corresponding time and granulepos */
GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
GST_BUFFER_OFFSET_END (buf) =
gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
pad->keyframe_granule);
return buf;
/* ERRORS */
no_granule:
{
GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
"falling back to upstream provided metadata");
return buf;
}
}
/* make sure at least one buffer is queued on all pads, two if possible /* make sure at least one buffer is queued on all pads, two if possible
* *
* if pad->buffer == NULL, pad->next_buffer != NULL, then * if pad->buffer == NULL, pad->next_buffer != NULL, then
@ -786,11 +896,6 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
packet.packet = gst_buffer_map (buf, &size, NULL, GST_MAP_READ); packet.packet = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
packet.bytes = size; packet.bytes = size;
if (GST_BUFFER_OFFSET_END_IS_VALID (buf))
packet.granulepos = GST_BUFFER_OFFSET_END (buf);
else
packet.granulepos = 0;
/* if we're not yet in data mode, ensure we're setup on the first packet */ /* if we're not yet in data mode, ensure we're setup on the first packet */
if (!pad->have_type) { if (!pad->have_type) {
GstCaps *caps; GstCaps *caps;
@ -837,8 +942,25 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
"got data buffer in control state, switching to data mode"); "got data buffer in control state, switching to data mode");
/* this is a data buffer so switch to data state */ /* this is a data buffer so switch to data state */
pad->state = GST_OGG_PAD_STATE_DATA; pad->state = GST_OGG_PAD_STATE_DATA;
/* check if this type of stream allows generating granulepos
* metadata here, if not, upstream will have to provide */
if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
"relying on upstream");
/* disable metadata code path, otherwise not used anyway */
pad->map.granulerate_n = 0;
}
} }
} }
/* so now we should have a real data packet;
* see that it is properly decorated */
if (G_LIKELY (buf)) {
buf = gst_ogg_mux_decorate_buffer (ogg_mux, pad, buf);
if (G_UNLIKELY (!buf))
GST_DEBUG_OBJECT (data->pad, "buffer clipped");
}
} else { } else {
GST_DEBUG_OBJECT (data->pad, "EOS on pad"); GST_DEBUG_OBJECT (data->pad, "EOS on pad");
if (!pad->eos) { if (!pad->eos) {
@ -1626,6 +1748,9 @@ gst_ogg_mux_get_property (GObject * object,
case ARG_MAX_PAGE_DELAY: case ARG_MAX_PAGE_DELAY:
g_value_set_uint64 (value, ogg_mux->max_page_delay); g_value_set_uint64 (value, ogg_mux->max_page_delay);
break; break;
case ARG_MAX_TOLERANCE:
g_value_set_uint64 (value, ogg_mux->max_tolerance);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -1647,6 +1772,9 @@ gst_ogg_mux_set_property (GObject * object,
case ARG_MAX_PAGE_DELAY: case ARG_MAX_PAGE_DELAY:
ogg_mux->max_page_delay = g_value_get_uint64 (value); ogg_mux->max_page_delay = g_value_get_uint64 (value);
break; break;
case ARG_MAX_TOLERANCE:
ogg_mux->max_tolerance = g_value_get_uint64 (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;

View file

@ -82,6 +82,9 @@ typedef struct
gboolean prev_delta; /* was the previous buffer a delta frame */ gboolean prev_delta; /* was the previous buffer a delta frame */
gboolean data_pushed; /* whether we pushed data already */ gboolean data_pushed; /* whether we pushed data already */
gint64 next_granule; /* expected granule of next buffer ts */
gint64 keyframe_granule; /* granule of last preceding keyframe */
GstPadEventFunction collect_event; GstPadEventFunction collect_event;
gboolean always_flush_page; gboolean always_flush_page;
@ -123,6 +126,7 @@ struct _GstOggMux
guint64 max_delay; guint64 max_delay;
guint64 max_page_delay; guint64 max_page_delay;
guint64 max_tolerance;
GstOggPadData *delta_pad; /* when a delta frame is detected on a stream, we mark GstOggPadData *delta_pad; /* when a delta frame is detected on a stream, we mark
pages as delta frames up to the page that has the pages as delta frames up to the page that has the

View file

@ -1130,6 +1130,89 @@ theora_enc_write_multipass_cache (GstTheoraEnc * enc, gboolean begin,
return TRUE; return TRUE;
} }
static GstFlowReturn
theora_enc_encode_and_push (GstTheoraEnc * enc, ogg_packet op,
GstClockTime timestamp, GstClockTime running_time,
GstClockTime duration, GstBuffer * buffer)
{
GstFlowReturn ret;
th_ycbcr_buffer ycbcr;
gint res;
guint8 *data;
gsize size;
data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
theora_enc_init_buffer (ycbcr, &enc->info, data);
if (theora_enc_is_discontinuous (enc, running_time, duration)) {
theora_enc_reset (enc);
enc->granulepos_offset =
gst_util_uint64_scale (running_time, enc->fps_n,
GST_SECOND * enc->fps_d);
enc->timestamp_offset = running_time;
enc->next_ts = 0;
enc->next_discont = TRUE;
}
if (enc->multipass_cache_fd
&& enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) {
if (!theora_enc_read_multipass_cache (enc)) {
ret = GST_FLOW_ERROR;
goto multipass_read_failed;
}
}
res = th_encode_ycbcr_in (enc->encoder, ycbcr);
/* none of the failure cases can happen here */
g_assert (res == 0);
if (enc->multipass_cache_fd
&& enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) {
if (!theora_enc_write_multipass_cache (enc, FALSE, FALSE)) {
ret = GST_FLOW_ERROR;
goto multipass_write_failed;
}
}
ret = GST_FLOW_OK;
while (th_encode_packetout (enc->encoder, 0, &op)) {
GstClockTime next_time;
next_time = th_granule_time (enc->encoder, op.granulepos) * GST_SECOND;
ret =
theora_push_packet (enc, &op, timestamp, enc->next_ts,
next_time - enc->next_ts);
enc->next_ts = next_time;
if (ret != GST_FLOW_OK)
goto data_push;
}
done:
gst_buffer_unmap (buffer, data, size);
gst_buffer_unref (buffer);
return ret;
/* ERRORS */
multipass_read_failed:
{
GST_DEBUG_OBJECT (enc, "multipass read failed");
goto done;
}
multipass_write_failed:
{
GST_DEBUG_OBJECT (enc, "multipass write failed");
goto done;
}
data_push:
{
GST_DEBUG_OBJECT (enc, "error pushing buffer: %s", gst_flow_get_name (ret));
goto done;
}
}
static GstFlowReturn static GstFlowReturn
theora_enc_chain (GstPad * pad, GstBuffer * buffer) theora_enc_chain (GstPad * pad, GstBuffer * buffer)
{ {
@ -1275,80 +1358,12 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
enc->next_ts = 0; enc->next_ts = 0;
} }
{ ret = theora_enc_encode_and_push (enc, op, timestamp, running_time, duration,
th_ycbcr_buffer ycbcr; buffer);
gint res;
guint8 *data;
gsize size;
data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
theora_enc_init_buffer (ycbcr, &enc->info, data);
if (theora_enc_is_discontinuous (enc, running_time, duration)) {
theora_enc_reset (enc);
enc->granulepos_offset =
gst_util_uint64_scale (running_time, enc->fps_n,
GST_SECOND * enc->fps_d);
enc->timestamp_offset = running_time;
enc->next_ts = 0;
enc->next_discont = TRUE;
}
if (enc->multipass_cache_fd
&& enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) {
if (!theora_enc_read_multipass_cache (enc)) {
gst_buffer_unmap (buffer, data, size);
ret = GST_FLOW_ERROR;
goto multipass_read_failed;
}
}
res = th_encode_ycbcr_in (enc->encoder, ycbcr);
/* none of the failure cases can happen here */
g_assert (res == 0);
if (enc->multipass_cache_fd
&& enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) {
if (!theora_enc_write_multipass_cache (enc, FALSE, FALSE)) {
gst_buffer_unmap (buffer, data, size);
ret = GST_FLOW_ERROR;
goto multipass_write_failed;
}
}
ret = GST_FLOW_OK;
while (th_encode_packetout (enc->encoder, 0, &op)) {
GstClockTime next_time;
next_time = th_granule_time (enc->encoder, op.granulepos) * GST_SECOND;
ret =
theora_push_packet (enc, &op, timestamp, enc->next_ts,
next_time - enc->next_ts);
enc->next_ts = next_time;
if (ret != GST_FLOW_OK) {
gst_buffer_unmap (buffer, data, size);
goto data_push;
}
}
gst_buffer_unmap (buffer, data, size);
gst_buffer_unref (buffer);
}
return ret; return ret;
/* ERRORS */ /* ERRORS */
multipass_read_failed:
{
gst_buffer_unref (buffer);
return ret;
}
multipass_write_failed:
{
gst_buffer_unref (buffer);
return ret;
}
header_buffer_alloc: header_buffer_alloc:
{ {
gst_buffer_unref (buffer); gst_buffer_unref (buffer);
@ -1359,11 +1374,6 @@ header_push:
gst_buffer_unref (buffer); gst_buffer_unref (buffer);
return ret; return ret;
} }
data_push:
{
gst_buffer_unref (buffer);
return ret;
}
encoder_disabled: encoder_disabled:
{ {
GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL),

View file

@ -1025,9 +1025,9 @@ handle_current_async (GstDiscoverer * dc)
{ {
GSource *source; GSource *source;
static GSourceCallbackFuncs cb_funcs = { static GSourceCallbackFuncs cb_funcs = {
.ref = _void_g_object_ref, _void_g_object_ref,
.unref = g_object_unref, g_object_unref,
.get = get_async_cb, get_async_cb,
}; };
/* Attach a timeout to the main context */ /* Attach a timeout to the main context */

View file

@ -1215,7 +1215,9 @@ gst_tag_demux_sink_activate (GstPad * sinkpad)
demux->priv->strip_start + demux->priv->strip_end) { demux->priv->strip_start + demux->priv->strip_end) {
/* There was no data (probably due to a truncated file) */ /* There was no data (probably due to a truncated file) */
GST_DEBUG_OBJECT (demux, "No data in file"); GST_DEBUG_OBJECT (demux, "No data in file");
return FALSE; /* so we don't know about type either */
GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
goto done_activate;
} }
/* 3 - Do typefinding on data */ /* 3 - Do typefinding on data */

View file

@ -980,7 +980,11 @@ gst_adder_request_new_pad (GstElement * element, GstPadTemplate * templ,
adder = GST_ADDER (element); adder = GST_ADDER (element);
/* increment pad counter */ /* increment pad counter */
#if GLIB_CHECK_VERSION(2,29,5)
padcount = g_atomic_int_add (&adder->padcount, 1);
#else
padcount = g_atomic_int_exchange_and_add (&adder->padcount, 1); padcount = g_atomic_int_exchange_and_add (&adder->padcount, 1);
#endif
name = g_strdup_printf ("sink%d", padcount); name = g_strdup_printf ("sink%d", padcount);
newpad = gst_pad_new_from_template (templ, name); newpad = gst_pad_new_from_template (templ, name);