/*********************************************************************************** * Internal functions ***********************************************************************************/ static void hls_progress_buffer_flush_data(HLSProgressBuffer *element) { guint i = 0; g_mutex_lock(element->lock); element->srcresult = GST_FLOW_WRONG_STATE; g_cond_signal(element->add_cond); g_cond_signal(element->del_cond); element->cache_write_index = -1; element->cache_read_index = 0; for (i = 0; i < NUM_OF_CACHED_SEGMENTS; i++) { if (element->cache[i]) { cache_set_write_position(element->cache[i], 0); cache_set_read_position(element->cache[i], 0); element->cache_size[i] = 0; element->cache_write_ready[i] = TRUE; } } g_mutex_unlock(element->lock); }
/*********************************************************************************** * Seek implementation ***********************************************************************************/ static gboolean progress_buffer_perform_push_seek(ProgressBuffer *element, GstPad *pad, GstEvent *event) { GstFormat format; gdouble rate; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 position; GstSegment segment; gst_event_parse_seek(event, &rate, &format, &flags, &start_type, &position, &stop_type, NULL); if (format != GST_FORMAT_BYTES || start_type != GST_SEEK_TYPE_SET) return FALSE; if (stop_type != GST_SEEK_TYPE_NONE) { gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_WARNING, GST_CORE_ERROR, GST_CORE_ERROR_SEEK, g_strdup("stop_type != GST_SEEK_TYPE_NONE. Seeking to stop is not supported."), NULL, ("progressbuffer.c"), ("progress_buffer_perform_push_seek"), 0); return FALSE; } if (flags & GST_SEEK_FLAG_FLUSH) gst_pad_push_event(pad, gst_event_new_flush_start()); // Signal the task to stop if it's waiting. g_mutex_lock(&element->lock); element->srcresult = GST_FLOW_FLUSHING; g_cond_signal(&element->add_cond); g_mutex_unlock(&element->lock); GST_PAD_STREAM_LOCK(pad); // Wait for task to stop g_mutex_lock(&element->lock); element->srcresult = GST_FLOW_OK; #ifdef ENABLE_SOURCE_SEEKING element->instant_seek = (position >= element->sink_segment.start && (position - (gint64)element->sink_segment.position) <= element->bandwidth * element->wait_tolerance); if (element->instant_seek) { cache_set_read_position(element->cache, position - element->cache_read_offset); gst_segment_init(&segment, GST_FORMAT_BYTES); segment.rate = rate; segment.start = position; segment.stop = element->sink_segment.stop; segment.position = position; progress_buffer_set_pending_event(element, gst_event_new_segment(&segment)); } else { // Clear any pending events, since we doing seek. reset_eos(element, TRUE); } #else cache_set_read_position(element->cache, position - element->cache_read_offset); gst_segment_init(&segment, GST_FORMAT_BYTES); segment.rate = rate; segment.start = position; segment.stop = element->sink_segment.stop; segment.position = position; progress_buffer_set_pending_event(element, gst_event_new_segment(&segment)); #endif g_mutex_unlock(&element->lock); #ifdef ENABLE_SOURCE_SEEKING if (!element->instant_seek) { element->is_source_seeking = TRUE; if (!gst_pad_push_event(element->sinkpad, gst_event_new_seek(rate, GST_FORMAT_BYTES, flags, GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, 0))) { element->instant_seek = TRUE; cache_set_read_position(element->cache, position - element->cache_read_offset); gst_segment_init(&segment, GST_FORMAT_BYTES); segment.rate = rate; segment.start = position; segment.stop = element->sink_segment.stop; segment.position = position; progress_buffer_set_pending_event(element, gst_event_new_segment(&segment)); } element->is_source_seeking = FALSE; } #endif if (flags & GST_SEEK_FLAG_FLUSH) gst_pad_push_event(pad, gst_event_new_flush_stop(TRUE)); gst_pad_start_task(element->srcpad, progress_buffer_loop, element, NULL); GST_PAD_STREAM_UNLOCK(pad); // INLINE - gst_event_unref() gst_event_unref(event); return TRUE; }
/** * progress_buffer_enqueue_item() * * Add an item in the queue. Must be called in the locked context. Item may be event or data. */ static GstFlowReturn progress_buffer_enqueue_item(ProgressBuffer *element, GstMiniObject *item) { gboolean signal = FALSE; if (GST_IS_BUFFER (item)) { gdouble elapsed; // update sink segment position element->sink_segment.position = GST_BUFFER_OFFSET(GST_BUFFER(item)) + gst_buffer_get_size (GST_BUFFER(item)); if(element->sink_segment.stop < element->sink_segment.position) // This must never happen. return GST_FLOW_ERROR; cache_write_buffer(element->cache, GST_BUFFER(item)); elapsed = g_timer_elapsed(element->bandwidth_timer, NULL); element->subtotal += gst_buffer_get_size (GST_BUFFER(item)); if (elapsed > 1.0) { element->bandwidth = element->subtotal/elapsed; element->subtotal = 0; g_timer_start(element->bandwidth_timer); } // send buffer progress position up (used to track buffer fill, etc.) signal = send_position_message(element, signal); } else if (GST_IS_EVENT (item)) { GstEvent *event = GST_EVENT_CAST (item); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: element->eos_status.eos = TRUE; if (element->sink_segment.position < element->sink_segment.stop) element->sink_segment.stop = element->sink_segment.position; progress_buffer_set_pending_event(element, NULL); signal = send_position_message(element, TRUE); gst_event_unref(event); // INLINE - gst_event_unref() break; case GST_EVENT_SEGMENT: { GstSegment segment; element->unexpected = FALSE; gst_event_copy_segment (event, &segment); if (segment.format != GST_FORMAT_BYTES) { gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, GST_STREAM_ERROR, GST_STREAM_ERROR_FORMAT, g_strdup("GST_FORMAT_BYTES buffers expected."), NULL, ("progressbuffer.c"), ("progress_buffer_enqueue_item"), 0); gst_event_unref(event); // INLINE - gst_event_unref() return GST_FLOW_ERROR; } if (segment.stop - segment.start <= 0) { gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, g_strdup("Only limited content is supported by progressbuffer."), NULL, ("progressbuffer.c"), ("progress_buffer_enqueue_item"), 0); gst_event_unref(event); // INLINE - gst_event_unref() return GST_FLOW_ERROR; } if ((segment.flags & GST_SEGMENT_FLAG_UPDATE) == GST_SEGMENT_FLAG_UPDATE) // Updating segments create new cache. { if (element->cache) destroy_cache(element->cache); element->cache = create_cache(); if (!element->cache) { gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ_WRITE, g_strdup("Couldn't create backing cache"), NULL, ("progressbuffer.c"), ("progress_buffer_enqueue_item"), 0); gst_event_unref(event); // INLINE - gst_event_unref() return GST_FLOW_ERROR; } } else { cache_set_write_position(element->cache, 0); cache_set_read_position(element->cache, 0); element->cache_read_offset = segment.start; } gst_segment_copy_into (&segment, &element->sink_segment); progress_buffer_set_pending_event(element, event); element->instant_seek = TRUE; signal = send_position_message(element, TRUE); break; } default: gst_event_unref(event); // INLINE - gst_event_unref() break; } } if (signal) g_cond_signal(&element->add_cond); return GST_FLOW_OK; }
/** * hls_progress_buffer_sink_event() * * Receives event from the sink pad (currently, data from javasource). When an event comes in, * we get the data from the pad by getting at the ProgressBuffer* object associated with the pad. */ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstEvent *event) { HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(GST_PAD_PARENT(pad)); gboolean ret = FALSE; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_NEWSEGMENT: { gboolean update; GstFormat format; gdouble rate, arate; gint64 start, stop, time; g_mutex_lock(element->lock); if (element->srcresult != GST_FLOW_OK) { // INLINE - gst_event_unref() gst_event_unref(event); g_mutex_unlock(element->lock); return TRUE; } g_mutex_unlock(element->lock); if (element->is_eos) { element->is_eos = FALSE; element->srcresult = GST_FLOW_OK; if (gst_pad_is_linked(element->srcpad)) gst_pad_start_task(element->srcpad, hls_progress_buffer_loop, element); } // In HLS mode javasource will set time to correct position in time unit, even if segment in byte units. // Maybe not perfect, but works. gst_event_parse_new_segment_full(event, &update, &rate, &arate, &format, &start, &stop, &time); // INLINE - gst_event_unref() gst_event_unref(event); ret = TRUE; if (stop - start <= 0) { gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, g_strdup("Only limited content is supported by hlsprogressbuffer."), NULL, ("hlsprogressbuffer.c"), ("hls_progress_buffer_src_event"), 0); return TRUE; } if (element->send_new_segment) { event = gst_event_new_new_segment(update, rate, GST_FORMAT_TIME, 0, -1, time); element->send_new_segment = FALSE; ret = gst_pad_push_event(element->srcpad, event); } // Get and prepare next write segment g_mutex_lock(element->lock); element->cache_write_index = (element->cache_write_index + 1) % NUM_OF_CACHED_SEGMENTS; while (element->srcresult == GST_FLOW_OK && !element->cache_write_ready[element->cache_write_index]) { g_mutex_unlock(element->lock); send_hls_full_message(element); g_mutex_lock(element->lock); g_cond_wait(element->del_cond, element->lock); if (element->srcresult != GST_FLOW_OK) { g_mutex_unlock(element->lock); return TRUE; } } element->cache_size[element->cache_write_index] = stop; element->cache_write_ready[element->cache_write_index] = FALSE; cache_set_write_position(element->cache[element->cache_write_index], 0); cache_set_read_position(element->cache[element->cache_write_index], 0); g_mutex_unlock(element->lock); send_hls_resume_message(element); // Send resume message for each segment } break; case GST_EVENT_FLUSH_START: g_mutex_lock(element->lock); element->is_flushing = TRUE; g_mutex_unlock(element->lock); ret = gst_pad_push_event(element->srcpad, event); hls_progress_buffer_flush_data(element); if (gst_pad_is_linked(element->srcpad)) gst_pad_pause_task(element->srcpad); break; case GST_EVENT_FLUSH_STOP: ret = gst_pad_push_event(element->srcpad, event); g_mutex_lock(element->lock); element->send_new_segment = TRUE; element->is_flushing = FALSE; element->srcresult = GST_FLOW_OK; if (!element->is_eos && gst_pad_is_linked(element->srcpad)) gst_pad_start_task(element->srcpad, hls_progress_buffer_loop, element); g_mutex_unlock(element->lock); break; case GST_EVENT_EOS: send_hls_eos_message(element); // Just in case we stall g_mutex_lock(element->lock); element->is_eos = TRUE; g_cond_signal(element->add_cond); g_mutex_unlock(element->lock); // INLINE - gst_event_unref() gst_event_unref(event); ret = TRUE; break; default: ret = gst_pad_push_event(element->srcpad, event); break; } return ret; }