static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); gchar *playlist = NULL; if (hlsdemux->client) gst_m3u8_client_free (hlsdemux->client); hlsdemux->client = gst_m3u8_client_new (demux->manifest_uri, demux->manifest_base_uri); GST_INFO_OBJECT (demux, "Changed location: %s (base uri: %s)", demux->manifest_uri, GST_STR_NULL (demux->manifest_base_uri)); playlist = gst_hls_src_buf_to_utf8_playlist (buf); if (playlist == NULL) { GST_WARNING_OBJECT (demux, "Error validating first playlist."); return FALSE; } else if (!gst_m3u8_client_update (hlsdemux->client, playlist)) { /* In most cases, this will happen if we set a wrong url in the * source element and we have received the 404 HTML response instead of * the playlist */ GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); return FALSE; } /* If this playlist is a variant playlist, select the first one * and update it */ if (gst_m3u8_client_has_variant_playlist (hlsdemux->client)) { GstM3U8 *child = NULL; GError *err = NULL; if (demux->connection_speed == 0) { GST_M3U8_CLIENT_LOCK (hlsdemux->client); child = hlsdemux->client->main->current_variant->data; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); } else { GList *tmp = gst_m3u8_client_get_playlist_for_bitrate (hlsdemux->client, demux->connection_speed); GST_M3U8_CLIENT_LOCK (hlsdemux->client); hlsdemux->client->main->current_variant = tmp; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); child = GST_M3U8 (tmp->data); } gst_m3u8_client_set_current (hlsdemux->client, child); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch the child playlist", err); return FALSE; } } return gst_hls_demux_setup_streams (demux); }
static gboolean gst_amc_audio_dec_open (GstAudioDecoder * decoder) { GstAmcAudioDec *self = GST_AMC_AUDIO_DEC (decoder); GstAmcAudioDecClass *klass = GST_AMC_AUDIO_DEC_GET_CLASS (self); GError *err = NULL; GST_DEBUG_OBJECT (self, "Opening decoder"); self->codec = gst_amc_codec_new (klass->codec_info->name, &err); if (!self->codec) { GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } self->started = FALSE; self->flushing = TRUE; GST_DEBUG_OBJECT (self, "Opened decoder"); return TRUE; }
static gboolean gst_amc_video_enc_open (GstVideoEncoder * encoder) { GstAmcVideoEnc *self = GST_AMC_VIDEO_ENC (encoder); GstAmcVideoEncClass *klass = GST_AMC_VIDEO_ENC_GET_CLASS (self); GError *err = NULL; GST_DEBUG_OBJECT (self, "Opening encoder"); self->codec = gst_amc_codec_new (klass->codec_info->name, &err); if (!self->codec) { GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } self->started = FALSE; self->flushing = TRUE; GST_DEBUG_OBJECT (self, "Opened encoder"); return TRUE; }
static gboolean gst_amc_audio_dec_set_format (GstAudioDecoder * decoder, GstCaps * caps) { GstAmcAudioDec *self; GstStructure *s; GstAmcFormat *format; const gchar *mime; gboolean is_format_change = FALSE; gboolean needs_disable = FALSE; gchar *format_string; gint rate, channels; GError *err = NULL; self = GST_AMC_AUDIO_DEC (decoder); GST_DEBUG_OBJECT (self, "Setting new caps %" GST_PTR_FORMAT, caps); /* Check if the caps change is a real format change or if only irrelevant * parts of the caps have changed or nothing at all. */ is_format_change |= (!self->input_caps || !gst_caps_is_equal (self->input_caps, caps)); needs_disable = self->started; /* If the component is not started and a real format change happens * we have to restart the component. If no real format change * happened we can just exit here. */ if (needs_disable && !is_format_change) { /* Framerate or something minor changed */ self->input_caps_changed = TRUE; GST_DEBUG_OBJECT (self, "Already running and caps did not change the format"); return TRUE; } if (needs_disable && is_format_change) { gst_amc_audio_dec_drain (self); GST_AUDIO_DECODER_STREAM_UNLOCK (self); gst_amc_audio_dec_stop (GST_AUDIO_DECODER (self)); GST_AUDIO_DECODER_STREAM_LOCK (self); gst_amc_audio_dec_close (GST_AUDIO_DECODER (self)); if (!gst_amc_audio_dec_open (GST_AUDIO_DECODER (self))) { GST_ERROR_OBJECT (self, "Failed to open codec again"); return FALSE; } if (!gst_amc_audio_dec_start (GST_AUDIO_DECODER (self))) { GST_ERROR_OBJECT (self, "Failed to start codec again"); } } /* srcpad task is not running at this point */ mime = caps_to_mime (caps); if (!mime) { GST_ERROR_OBJECT (self, "Failed to convert caps to mime"); return FALSE; } s = gst_caps_get_structure (caps, 0); if (!gst_structure_get_int (s, "rate", &rate) || !gst_structure_get_int (s, "channels", &channels)) { GST_ERROR_OBJECT (self, "Failed to get rate/channels"); return FALSE; } format = gst_amc_format_new_audio (mime, rate, channels, &err); if (!format) { GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } /* FIXME: These buffers needs to be valid until the codec is stopped again */ g_list_foreach (self->codec_datas, (GFunc) gst_buffer_unref, NULL); g_list_free (self->codec_datas); self->codec_datas = NULL; if (gst_structure_has_field (s, "codec_data")) { const GValue *h = gst_structure_get_value (s, "codec_data"); GstBuffer *codec_data = gst_value_get_buffer (h); GstMapInfo minfo; guint8 *data; gst_buffer_map (codec_data, &minfo, GST_MAP_READ); data = g_memdup (minfo.data, minfo.size); self->codec_datas = g_list_prepend (self->codec_datas, data); gst_amc_format_set_buffer (format, "csd-0", data, minfo.size, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); gst_buffer_unmap (codec_data, &minfo); } else if (gst_structure_has_field (s, "streamheader")) { const GValue *sh = gst_structure_get_value (s, "streamheader"); gint nsheaders = gst_value_array_get_size (sh); GstBuffer *buf; const GValue *h; gint i, j; gchar *fname; GstMapInfo minfo; guint8 *data; for (i = 0, j = 0; i < nsheaders; i++) { h = gst_value_array_get_value (sh, i); buf = gst_value_get_buffer (h); if (strcmp (mime, "audio/vorbis") == 0) { guint8 header_type; gst_buffer_extract (buf, 0, &header_type, 1); /* Only use the identification and setup packets */ if (header_type != 0x01 && header_type != 0x05) continue; } fname = g_strdup_printf ("csd-%d", j); gst_buffer_map (buf, &minfo, GST_MAP_READ); data = g_memdup (minfo.data, minfo.size); self->codec_datas = g_list_prepend (self->codec_datas, data); gst_amc_format_set_buffer (format, fname, data, minfo.size, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); gst_buffer_unmap (buf, &minfo); g_free (fname); j++; } } format_string = gst_amc_format_to_string (format, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); GST_DEBUG_OBJECT (self, "Configuring codec with format: %s", GST_STR_NULL (format_string)); g_free (format_string); if (!gst_amc_codec_configure (self->codec, format, 0, &err)) { GST_ERROR_OBJECT (self, "Failed to configure codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } gst_amc_format_free (format); if (!gst_amc_codec_start (self->codec, &err)) { GST_ERROR_OBJECT (self, "Failed to start codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } self->spf = -1; /* TODO: Implement for other codecs too */ if (gst_structure_has_name (s, "audio/mpeg")) { gint mpegversion = -1; gst_structure_get_int (s, "mpegversion", &mpegversion); if (mpegversion == 1) { gint layer = -1, mpegaudioversion = -1; gst_structure_get_int (s, "layer", &layer); gst_structure_get_int (s, "mpegaudioversion", &mpegaudioversion); if (layer == 1) self->spf = 384; else if (layer == 2) self->spf = 1152; else if (layer == 3 && mpegaudioversion != -1) self->spf = (mpegaudioversion == 1 ? 1152 : 576); } } self->started = TRUE; self->input_caps_changed = TRUE; /* Start the srcpad loop again */ self->flushing = FALSE; self->downstream_flow_ret = GST_FLOW_OK; gst_pad_start_task (GST_AUDIO_DECODER_SRC_PAD (self), (GstTaskFunction) gst_amc_audio_dec_loop, decoder, NULL); return TRUE; }
static void gst_amc_audio_dec_loop (GstAmcAudioDec * self) { GstFlowReturn flow_ret = GST_FLOW_OK; gboolean is_eos; GstAmcBuffer *buf; GstAmcBufferInfo buffer_info; gint idx; GError *err = NULL; GST_AUDIO_DECODER_STREAM_LOCK (self); retry: /*if (self->input_caps_changed) { idx = INFO_OUTPUT_FORMAT_CHANGED; } else { */ GST_DEBUG_OBJECT (self, "Waiting for available output buffer"); GST_AUDIO_DECODER_STREAM_UNLOCK (self); /* Wait at most 100ms here, some codecs don't fail dequeueing if * the codec is flushing, causing deadlocks during shutdown */ idx = gst_amc_codec_dequeue_output_buffer (self->codec, &buffer_info, 100000, &err); GST_AUDIO_DECODER_STREAM_LOCK (self); /*} */ if (idx < 0) { if (self->flushing) { g_clear_error (&err); goto flushing; } switch (idx) { case INFO_OUTPUT_BUFFERS_CHANGED: /* Handled internally */ g_assert_not_reached (); break; case INFO_OUTPUT_FORMAT_CHANGED:{ GstAmcFormat *format; gchar *format_string; GST_DEBUG_OBJECT (self, "Output format has changed"); format = gst_amc_codec_get_output_format (self->codec, &err); if (!format) goto format_error; format_string = gst_amc_format_to_string (format, &err); if (err) { gst_amc_format_free (format); goto format_error; } GST_DEBUG_OBJECT (self, "Got new output format: %s", format_string); g_free (format_string); if (!gst_amc_audio_dec_set_src_caps (self, format)) { gst_amc_format_free (format); goto format_error; } gst_amc_format_free (format); goto retry; } case INFO_TRY_AGAIN_LATER: GST_DEBUG_OBJECT (self, "Dequeueing output buffer timed out"); goto retry; case G_MININT: GST_ERROR_OBJECT (self, "Failure dequeueing output buffer"); goto dequeue_error; default: g_assert_not_reached (); break; } goto retry; } GST_DEBUG_OBJECT (self, "Got output buffer at index %d: offset %d size %d time %" G_GINT64_FORMAT " flags 0x%08x", idx, buffer_info.offset, buffer_info.size, buffer_info.presentation_time_us, buffer_info.flags); is_eos = ! !(buffer_info.flags & BUFFER_FLAG_END_OF_STREAM); buf = gst_amc_codec_get_output_buffer (self->codec, idx, &err); if (!buf) goto failed_to_get_output_buffer; if (buffer_info.size > 0) { GstBuffer *outbuf; GstMapInfo minfo; /* This sometimes happens at EOS or if the input is not properly framed, * let's handle it gracefully by allocating a new buffer for the current * caps and filling it */ if (buffer_info.size % self->info.bpf != 0) goto invalid_buffer_size; outbuf = gst_audio_decoder_allocate_output_buffer (GST_AUDIO_DECODER (self), buffer_info.size); if (!outbuf) goto failed_allocate; gst_buffer_map (outbuf, &minfo, GST_MAP_WRITE); if (self->needs_reorder) { gint i, n_samples, c, n_channels; gint *reorder_map = self->reorder_map; gint16 *dest, *source; dest = (gint16 *) minfo.data; source = (gint16 *) (buf->data + buffer_info.offset); n_samples = buffer_info.size / self->info.bpf; n_channels = self->info.channels; for (i = 0; i < n_samples; i++) { for (c = 0; c < n_channels; c++) { dest[i * n_channels + reorder_map[c]] = source[i * n_channels + c]; } } } else { orc_memcpy (minfo.data, buf->data + buffer_info.offset, buffer_info.size); } gst_buffer_unmap (outbuf, &minfo); if (self->spf != -1) { gst_adapter_push (self->output_adapter, outbuf); } else { flow_ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf, 1); } } gst_amc_buffer_free (buf); buf = NULL; if (self->spf != -1) { GstBuffer *outbuf; guint avail = gst_adapter_available (self->output_adapter); guint nframes; /* On EOS we take the complete adapter content, no matter * if it is a multiple of the codec frame size or not. * Otherwise we take a multiple of codec frames and push * them downstream */ avail /= self->info.bpf; if (!is_eos) { nframes = avail / self->spf; avail = nframes * self->spf; } else { nframes = (avail + self->spf - 1) / self->spf; } avail *= self->info.bpf; if (avail > 0) { outbuf = gst_adapter_take_buffer (self->output_adapter, avail); flow_ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf, nframes); } } if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) { if (self->flushing) { g_clear_error (&err); goto flushing; } goto failed_release; } if (is_eos || flow_ret == GST_FLOW_EOS) { GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); if (self->draining) { GST_DEBUG_OBJECT (self, "Drained"); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); } else if (flow_ret == GST_FLOW_OK) { GST_DEBUG_OBJECT (self, "Component signalled EOS"); flow_ret = GST_FLOW_EOS; } g_mutex_unlock (&self->drain_lock); GST_AUDIO_DECODER_STREAM_LOCK (self); } else { GST_DEBUG_OBJECT (self, "Finished frame: %s", gst_flow_get_name (flow_ret)); } self->downstream_flow_ret = flow_ret; if (flow_ret != GST_FLOW_OK) goto flow_error; GST_AUDIO_DECODER_STREAM_UNLOCK (self); return; dequeue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } format_error: { if (err) GST_ELEMENT_ERROR_FROM_ERROR (self, err); else GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("Failed to handle format")); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_release: { GST_AUDIO_DECODER_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } flushing: { GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_FLUSHING; GST_AUDIO_DECODER_STREAM_UNLOCK (self); return; } flow_error: { if (flow_ret == GST_FLOW_EOS) { GST_DEBUG_OBJECT (self, "EOS"); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); } else if (flow_ret < GST_FLOW_EOS) { GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Internal data stream error."), ("stream stopped, reason %s", gst_flow_get_name (flow_ret))); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); } else if (flow_ret == GST_FLOW_FLUSHING) { GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); } GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_to_get_output_buffer: { GST_AUDIO_DECODER_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } invalid_buffer_size: { GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("Invalid buffer size %u (bfp %d)", buffer_info.size, self->info.bpf)); gst_amc_codec_release_output_buffer (self->codec, idx, &err); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_allocate: { GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), ("Failed to allocate output buffer")); gst_amc_codec_release_output_buffer (self->codec, idx, &err); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_AUDIO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } }
static GstFlowReturn gst_amc_audio_dec_handle_frame (GstAudioDecoder * decoder, GstBuffer * inbuf) { GstAmcAudioDec *self; gint idx; GstAmcBuffer *buf; GstAmcBufferInfo buffer_info; guint offset = 0; GstClockTime timestamp, duration, timestamp_offset = 0; GstMapInfo minfo; GError *err = NULL; memset (&minfo, 0, sizeof (minfo)); self = GST_AMC_AUDIO_DEC (decoder); GST_DEBUG_OBJECT (self, "Handling frame"); /* Make sure to keep a reference to the input here, * it can be unreffed from the other thread if * finish_frame() is called */ if (inbuf) inbuf = gst_buffer_ref (inbuf); if (!self->started) { GST_ERROR_OBJECT (self, "Codec not started yet"); if (inbuf) gst_buffer_unref (inbuf); return GST_FLOW_NOT_NEGOTIATED; } if (self->flushing) goto flushing; if (self->downstream_flow_ret != GST_FLOW_OK) goto downstream_error; if (!inbuf) return gst_amc_audio_dec_drain (self); timestamp = GST_BUFFER_PTS (inbuf); duration = GST_BUFFER_DURATION (inbuf); gst_buffer_map (inbuf, &minfo, GST_MAP_READ); while (offset < minfo.size) { /* Make sure to release the base class stream lock, otherwise * _loop() can't call _finish_frame() and we might block forever * because no input buffers are released */ GST_AUDIO_DECODER_STREAM_UNLOCK (self); /* Wait at most 100ms here, some codecs don't fail dequeueing if * the codec is flushing, causing deadlocks during shutdown */ idx = gst_amc_codec_dequeue_input_buffer (self->codec, 100000, &err); GST_AUDIO_DECODER_STREAM_LOCK (self); if (idx < 0) { if (self->flushing || self->downstream_flow_ret == GST_FLOW_FLUSHING) { g_clear_error (&err); goto flushing; } switch (idx) { case INFO_TRY_AGAIN_LATER: GST_DEBUG_OBJECT (self, "Dequeueing input buffer timed out"); continue; /* next try */ break; case G_MININT: GST_ERROR_OBJECT (self, "Failed to dequeue input buffer"); goto dequeue_error; default: g_assert_not_reached (); break; } continue; } if (self->flushing) { memset (&buffer_info, 0, sizeof (buffer_info)); gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, NULL); goto flushing; } if (self->downstream_flow_ret != GST_FLOW_OK) { memset (&buffer_info, 0, sizeof (buffer_info)); gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, &err); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); goto downstream_error; } /* Now handle the frame */ /* Copy the buffer content in chunks of size as requested * by the port */ buf = gst_amc_codec_get_input_buffer (self->codec, idx, &err); if (!buf) goto failed_to_get_input_buffer; memset (&buffer_info, 0, sizeof (buffer_info)); buffer_info.offset = 0; buffer_info.size = MIN (minfo.size - offset, buf->size); gst_amc_buffer_set_position_and_limit (buf, NULL, buffer_info.offset, buffer_info.size); orc_memcpy (buf->data, minfo.data + offset, buffer_info.size); gst_amc_buffer_free (buf); buf = NULL; /* Interpolate timestamps if we're passing the buffer * in multiple chunks */ if (offset != 0 && duration != GST_CLOCK_TIME_NONE) { timestamp_offset = gst_util_uint64_scale (offset, duration, minfo.size); } if (timestamp != GST_CLOCK_TIME_NONE) { buffer_info.presentation_time_us = gst_util_uint64_scale (timestamp + timestamp_offset, 1, GST_USECOND); self->last_upstream_ts = timestamp + timestamp_offset; } if (duration != GST_CLOCK_TIME_NONE) self->last_upstream_ts += duration; if (offset == 0) { if (!GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DELTA_UNIT)) buffer_info.flags |= BUFFER_FLAG_SYNC_FRAME; } offset += buffer_info.size; GST_DEBUG_OBJECT (self, "Queueing buffer %d: size %d time %" G_GINT64_FORMAT " flags 0x%08x", idx, buffer_info.size, buffer_info.presentation_time_us, buffer_info.flags); if (!gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, &err)) { if (self->flushing) { g_clear_error (&err); goto flushing; } goto queue_error; } self->drained = FALSE; } gst_buffer_unmap (inbuf, &minfo); gst_buffer_unref (inbuf); return self->downstream_flow_ret; downstream_error: { GST_ERROR_OBJECT (self, "Downstream returned %s", gst_flow_get_name (self->downstream_flow_ret)); if (minfo.data) gst_buffer_unmap (inbuf, &minfo); if (inbuf) gst_buffer_unref (inbuf); return self->downstream_flow_ret; } failed_to_get_input_buffer: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); if (minfo.data) gst_buffer_unmap (inbuf, &minfo); if (inbuf) gst_buffer_unref (inbuf); return GST_FLOW_ERROR; } dequeue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); if (minfo.data) gst_buffer_unmap (inbuf, &minfo); if (inbuf) gst_buffer_unref (inbuf); return GST_FLOW_ERROR; } queue_error: { GST_AUDIO_DECODER_ERROR_FROM_ERROR (self, err); if (minfo.data) gst_buffer_unmap (inbuf, &minfo); if (inbuf) gst_buffer_unref (inbuf); return GST_FLOW_ERROR; } flushing: { GST_DEBUG_OBJECT (self, "Flushing -- returning FLUSHING"); if (minfo.data) gst_buffer_unmap (inbuf, &minfo); if (inbuf) gst_buffer_unref (inbuf); return GST_FLOW_FLUSHING; } }
static gboolean gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gdouble rate; GList *walk, *current_file = NULL; GstClockTime current_pos, target_pos; gint64 current_sequence; GstM3U8MediaFile *file; guint64 bitrate; gboolean snap_before, snap_after, snap_nearest, keyunit; gboolean reverse; gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); bitrate = gst_hls_demux_get_bitrate (hlsdemux); /* properly cleanup pending decryption status */ if (flags & GST_SEEK_FLAG_FLUSH) { gst_hls_demux_clear_pending_data (hlsdemux); } /* Use I-frame variants for trick modes */ if (hlsdemux->client->main->iframe_lists && rate < -1.0 && demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to I-frame variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->iframe_lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->iframe_lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL); } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 || demux->segment.rate > 1.0)) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to normal variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; /* TODO why not continue using the same? that was being used up to now? */ gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL); } GST_M3U8_CLIENT_LOCK (hlsdemux->client); file = GST_M3U8_MEDIA_FILE (hlsdemux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; reverse = rate < 0; target_pos = reverse ? stop : start; /* Snap to segment boundary. Improves seek performance on slow machines. */ keyunit = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); snap_nearest = (flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST; snap_before = ! !(flags & GST_SEEK_FLAG_SNAP_BEFORE); snap_after = ! !(flags & GST_SEEK_FLAG_SNAP_AFTER); /* FIXME: Here we need proper discont handling */ for (walk = hlsdemux->client->current->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; current_file = walk; if ((!reverse && snap_after) || snap_nearest) { if (current_pos >= target_pos) break; if (snap_nearest && target_pos - current_pos < file->duration / 2) break; } else if (reverse && snap_after) { /* check if the next fragment is our target, in this case we want to * start from the previous fragment */ GstClockTime next_pos = current_pos + file->duration; if (next_pos <= target_pos && target_pos < next_pos + file->duration) { break; } } else if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } if (walk == NULL) { GST_DEBUG_OBJECT (demux, "seeking further than track duration"); current_sequence++; } GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence); hlsdemux->reset_pts = TRUE; hlsdemux->client->sequence = current_sequence; hlsdemux->client->current_file = current_file ? current_file : hlsdemux->client->current->files; hlsdemux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); /* Play from the end of the current selected segment */ if (reverse && (snap_before || snap_after || snap_nearest)) current_pos += file->duration; if (keyunit || snap_before || snap_after || snap_nearest) { if (!reverse) gst_segment_do_seek (&demux->segment, rate, format, flags, start_type, current_pos, stop_type, stop, NULL); else gst_segment_do_seek (&demux->segment, rate, format, flags, start_type, start, stop_type, current_pos, NULL); } return TRUE; }
static gboolean gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gdouble rate; GList *walk, *current_file = NULL; GstClockTime current_pos, target_pos; gint64 current_sequence; GstM3U8MediaFile *file; guint64 bitrate; gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); bitrate = gst_hls_demux_get_bitrate (hlsdemux); /* properly cleanup pending decryption status */ if (flags & GST_SEEK_FLAG_FLUSH) { gst_hls_demux_decrypt_end (hlsdemux); } /* Use I-frame variants for trick modes */ if (hlsdemux->client->main->iframe_lists && rate < -1.0 && demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to I-frame variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->iframe_lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->iframe_lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL); } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 || demux->segment.rate > 1.0)) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to normal variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; /* TODO why not continue using the same? that was being used up to now? */ gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL); } GST_M3U8_CLIENT_LOCK (hlsdemux->client); file = GST_M3U8_MEDIA_FILE (hlsdemux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; target_pos = rate > 0 ? start : stop; /* FIXME: Here we need proper discont handling */ for (walk = hlsdemux->client->current->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; current_file = walk; if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } if (walk == NULL) { GST_DEBUG_OBJECT (demux, "seeking further than track duration"); current_sequence++; } GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence); hlsdemux->reset_pts = TRUE; hlsdemux->client->sequence = current_sequence; hlsdemux->client->current_file = current_file ? current_file : hlsdemux->client->current->files; hlsdemux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); return TRUE; }
static void gst_amc_video_enc_loop (GstAmcVideoEnc * self) { GstVideoCodecFrame *frame; GstFlowReturn flow_ret = GST_FLOW_OK; gboolean is_eos; GstAmcBufferInfo buffer_info; GstAmcBuffer *buf; gint idx; GError *err = NULL; GST_VIDEO_ENCODER_STREAM_LOCK (self); retry: GST_DEBUG_OBJECT (self, "Waiting for available output buffer"); GST_VIDEO_ENCODER_STREAM_UNLOCK (self); /* Wait at most 100ms here, some codecs don't fail dequeueing if * the codec is flushing, causing deadlocks during shutdown */ idx = gst_amc_codec_dequeue_output_buffer (self->codec, &buffer_info, 100000, &err); GST_VIDEO_ENCODER_STREAM_LOCK (self); /*} */ if (idx < 0 || self->amc_format) { if (self->flushing) { g_clear_error (&err); goto flushing; } /* The comments from https://android.googlesource.com/platform/cts/+/android-4.3_r3.1/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java * line 539 says INFO_OUTPUT_FORMAT_CHANGED is not expected for an encoder */ if (self->amc_format || idx == INFO_OUTPUT_FORMAT_CHANGED) { GstAmcFormat *format; gchar *format_string; GST_DEBUG_OBJECT (self, "Output format has changed"); format = (idx == INFO_OUTPUT_FORMAT_CHANGED) ? gst_amc_codec_get_output_format (self->codec, &err) : self->amc_format; if (err) { format = self->amc_format; GST_ELEMENT_WARNING_FROM_ERROR (self, err); } if (self->amc_format) { if (format != self->amc_format) gst_amc_format_free (self->amc_format); self->amc_format = NULL; } if (!format) goto format_error; format_string = gst_amc_format_to_string (format, &err); if (err) { gst_amc_format_free (format); goto format_error; } GST_DEBUG_OBJECT (self, "Got new output format: %s", format_string); g_free (format_string); if (!gst_amc_video_enc_set_src_caps (self, format)) { gst_amc_format_free (format); goto format_error; } gst_amc_format_free (format); if (idx >= 0) goto process_buffer; goto retry; } switch (idx) { case INFO_OUTPUT_BUFFERS_CHANGED: /* Handled internally */ g_assert_not_reached (); break; case INFO_TRY_AGAIN_LATER: GST_DEBUG_OBJECT (self, "Dequeueing output buffer timed out"); goto retry; break; case G_MININT: GST_ERROR_OBJECT (self, "Failure dequeueing input buffer"); goto dequeue_error; break; default: g_assert_not_reached (); break; } goto retry; } process_buffer: GST_DEBUG_OBJECT (self, "Got output buffer at index %d: size %d time %" G_GINT64_FORMAT " flags 0x%08x", idx, buffer_info.size, buffer_info.presentation_time_us, buffer_info.flags); frame = _find_nearest_frame (self, gst_util_uint64_scale (buffer_info.presentation_time_us, GST_USECOND, 1)); is_eos = ! !(buffer_info.flags & BUFFER_FLAG_END_OF_STREAM); buf = gst_amc_codec_get_output_buffer (self->codec, idx, &err); if (!buf) goto failed_to_get_output_buffer; flow_ret = gst_amc_video_enc_handle_output_frame (self, buf, &buffer_info, frame); gst_amc_buffer_free (buf); buf = NULL; if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) { if (self->flushing) { g_clear_error (&err); goto flushing; } goto failed_release; } if (is_eos || flow_ret == GST_FLOW_EOS) { GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); if (self->draining) { GST_DEBUG_OBJECT (self, "Drained"); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); } else if (flow_ret == GST_FLOW_OK) { GST_DEBUG_OBJECT (self, "Component signalled EOS"); flow_ret = GST_FLOW_EOS; } g_mutex_unlock (&self->drain_lock); GST_VIDEO_ENCODER_STREAM_LOCK (self); } else { GST_DEBUG_OBJECT (self, "Finished frame: %s", gst_flow_get_name (flow_ret)); } self->downstream_flow_ret = flow_ret; if (flow_ret != GST_FLOW_OK) goto flow_error; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); return; dequeue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } format_error: { if (err) GST_ELEMENT_ERROR_FROM_ERROR (self, err); else GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("Failed to handle format")); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_release: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } flushing: { GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_FLUSHING; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); return; } flow_error: { if (flow_ret == GST_FLOW_EOS) { GST_DEBUG_OBJECT (self, "EOS"); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); } else if (flow_ret == GST_FLOW_NOT_LINKED || flow_ret < GST_FLOW_EOS) { GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Internal data stream error."), ("stream stopped, reason %s", gst_flow_get_name (flow_ret))); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); } GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_to_get_output_buffer: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_ENCODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_ENCODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_NOT_NEGOTIATED; GST_VIDEO_ENCODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } }
static GstAmcFormat * create_amc_format (GstAmcVideoEnc * encoder, GstVideoCodecState * input_state, GstCaps * src_caps) { GstAmcVideoEncClass *klass; GstStructure *s; const gchar *name; const gchar *mime = NULL; const gchar *profile_string = NULL; const gchar *level_string = NULL; struct { const gchar *key; gint id; } amc_profile = { NULL, -1}; struct { const gchar *key; gint id; } amc_level = { NULL, -1}; gint color_format; gint stride, slice_height; GstAmcFormat *format = NULL; GstVideoInfo *info = &input_state->info; GError *err = NULL; klass = GST_AMC_VIDEO_ENC_GET_CLASS (encoder); s = gst_caps_get_structure (src_caps, 0); if (!s) return NULL; name = gst_structure_get_name (s); profile_string = gst_structure_get_string (s, "profile"); level_string = gst_structure_get_string (s, "level"); if (strcmp (name, "video/mpeg") == 0) { gint mpegversion; if (!gst_structure_get_int (s, "mpegversion", &mpegversion)) return NULL; if (mpegversion == 4) { mime = "video/mp4v-es"; if (profile_string) { amc_profile.key = "profile"; /* named profile ? */ amc_profile.id = gst_amc_mpeg4_profile_from_string (profile_string); } if (level_string) { amc_level.key = "level"; /* named level ? */ amc_level.id = gst_amc_mpeg4_level_from_string (level_string); } } else if ( /* mpegversion == 1 || */ mpegversion == 2) mime = "video/mpeg2"; } else if (strcmp (name, "video/x-h263") == 0) { mime = "video/3gpp"; } else if (strcmp (name, "video/x-h264") == 0) { mime = "video/avc"; if (profile_string) { amc_profile.key = "profile"; /* named profile ? */ amc_profile.id = gst_amc_avc_profile_from_string (profile_string); } if (level_string) { amc_level.key = "level"; /* named level ? */ amc_level.id = gst_amc_avc_level_from_string (level_string); } } else if (strcmp (name, "video/x-vp8") == 0) { mime = "video/x-vnd.on2.vp8"; } else { GST_ERROR_OBJECT (encoder, "Failed to convert caps(%s/...) to any mime", name); return NULL; } format = gst_amc_format_new_video (mime, info->width, info->height, &err); if (!format) { GST_ERROR_OBJECT (encoder, "Failed to create a \"%s,%dx%d\" MediaFormat", mime, info->width, info->height); GST_ELEMENT_ERROR_FROM_ERROR (encoder, err); return NULL; } color_format = gst_amc_video_format_to_color_format (klass->codec_info, mime, info->finfo->format); if (color_format == -1) goto video_format_failed_to_convert; gst_amc_format_set_int (format, "bitrate", encoder->bitrate, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); gst_amc_format_set_int (format, "color-format", color_format, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); stride = GST_ROUND_UP_4 (info->width); /* safe (?) */ gst_amc_format_set_int (format, "stride", stride, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); slice_height = info->height; gst_amc_format_set_int (format, "slice-height", slice_height, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); if (profile_string) { if (amc_profile.id == -1) goto unsupported_profile; /* FIXME: Set to any value in AVCProfile* leads to * codec configuration fail */ /* gst_amc_format_set_int (format, amc_profile.key, 0x40); */ } if (level_string) { if (amc_level.id == -1) goto unsupported_level; /* gst_amc_format_set_int (format, amc_level.key, amc_level.id); */ } gst_amc_format_set_int (format, "i-frame-interval", encoder->i_frame_int, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); if (info->fps_d) gst_amc_format_set_float (format, "frame-rate", ((gfloat) info->fps_n) / info->fps_d, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (encoder, err); encoder->format = info->finfo->format; if (!gst_amc_color_format_info_set (&encoder->color_format_info, klass->codec_info, mime, color_format, info->width, info->height, stride, slice_height, 0, 0, 0, 0)) goto color_format_info_failed_to_set; GST_DEBUG_OBJECT (encoder, "Color format info: {color_format=%d, width=%d, height=%d, " "stride=%d, slice-height=%d, crop-left=%d, crop-top=%d, " "crop-right=%d, crop-bottom=%d, frame-size=%d}", encoder->color_format_info.color_format, encoder->color_format_info.width, encoder->color_format_info.height, encoder->color_format_info.stride, encoder->color_format_info.slice_height, encoder->color_format_info.crop_left, encoder->color_format_info.crop_top, encoder->color_format_info.crop_right, encoder->color_format_info.crop_bottom, encoder->color_format_info.frame_size); return format; video_format_failed_to_convert: GST_ERROR_OBJECT (encoder, "Failed to convert video format"); gst_amc_format_free (format); return NULL; color_format_info_failed_to_set: GST_ERROR_OBJECT (encoder, "Failed to set up GstAmcColorFormatInfo"); gst_amc_format_free (format); return NULL; unsupported_profile: GST_ERROR_OBJECT (encoder, "Unsupport profile '%s'", profile_string); gst_amc_format_free (format); return NULL; unsupported_level: GST_ERROR_OBJECT (encoder, "Unsupport level '%s'", level_string); gst_amc_format_free (format); return NULL; }
static GstFlowReturn gst_amc_video_enc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame) { GstAmcVideoEnc *self; gint idx; GstAmcBuffer *buf; GstAmcBufferInfo buffer_info; GstClockTime timestamp, duration, timestamp_offset = 0; BufferIdentification *id; GError *err = NULL; self = GST_AMC_VIDEO_ENC (encoder); GST_DEBUG_OBJECT (self, "Handling frame"); if (!self->started) { GST_ERROR_OBJECT (self, "Codec not started yet"); gst_video_codec_frame_unref (frame); return GST_FLOW_NOT_NEGOTIATED; } if (self->flushing) goto flushing; if (self->downstream_flow_ret != GST_FLOW_OK) goto downstream_error; timestamp = frame->pts; duration = frame->duration; again: /* Make sure to release the base class stream lock, otherwise * _loop() can't call _finish_frame() and we might block forever * because no input buffers are released */ GST_VIDEO_ENCODER_STREAM_UNLOCK (self); /* Wait at most 100ms here, some codecs don't fail dequeueing if * the codec is flushing, causing deadlocks during shutdown */ idx = gst_amc_codec_dequeue_input_buffer (self->codec, 100000, &err); GST_VIDEO_ENCODER_STREAM_LOCK (self); if (idx < 0) { if (self->flushing || self->downstream_flow_ret == GST_FLOW_FLUSHING) { g_clear_error (&err); goto flushing; } switch (idx) { case INFO_TRY_AGAIN_LATER: GST_DEBUG_OBJECT (self, "Dequeueing input buffer timed out"); goto again; /* next try */ break; case G_MININT: GST_ERROR_OBJECT (self, "Failed to dequeue input buffer"); goto dequeue_error; default: g_assert_not_reached (); break; } goto again; } if (self->flushing) { memset (&buffer_info, 0, sizeof (buffer_info)); gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, NULL); goto flushing; } if (self->downstream_flow_ret != GST_FLOW_OK) { memset (&buffer_info, 0, sizeof (buffer_info)); gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, &err); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); goto downstream_error; } /* Now handle the frame */ /* Copy the buffer content in chunks of size as requested * by the port */ buf = gst_amc_codec_get_input_buffer (self->codec, idx, &err); if (!buf) goto failed_to_get_input_buffer; memset (&buffer_info, 0, sizeof (buffer_info)); buffer_info.offset = 0; buffer_info.size = MIN (self->color_format_info.frame_size, buf->size); gst_amc_buffer_set_position_and_limit (buf, NULL, buffer_info.offset, buffer_info.size); if (!gst_amc_video_enc_fill_buffer (self, frame->input_buffer, buf, &buffer_info)) { memset (&buffer_info, 0, sizeof (buffer_info)); gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, &err); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_amc_buffer_free (buf); buf = NULL; goto buffer_fill_error; } gst_amc_buffer_free (buf); buf = NULL; if (timestamp != GST_CLOCK_TIME_NONE) { buffer_info.presentation_time_us = gst_util_uint64_scale (timestamp + timestamp_offset, 1, GST_USECOND); self->last_upstream_ts = timestamp + timestamp_offset; } if (duration != GST_CLOCK_TIME_NONE) self->last_upstream_ts += duration; id = buffer_identification_new (timestamp + timestamp_offset); if (GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) buffer_info.flags |= BUFFER_FLAG_SYNC_FRAME; gst_video_codec_frame_set_user_data (frame, id, (GDestroyNotify) buffer_identification_free); GST_DEBUG_OBJECT (self, "Queueing buffer %d: size %d time %" G_GINT64_FORMAT " flags 0x%08x", idx, buffer_info.size, buffer_info.presentation_time_us, buffer_info.flags); if (!gst_amc_codec_queue_input_buffer (self->codec, idx, &buffer_info, &err)) { if (self->flushing) { g_clear_error (&err); goto flushing; } goto queue_error; } self->drained = FALSE; gst_video_codec_frame_unref (frame); return self->downstream_flow_ret; downstream_error: { GST_ERROR_OBJECT (self, "Downstream returned %s", gst_flow_get_name (self->downstream_flow_ret)); gst_video_codec_frame_unref (frame); return self->downstream_flow_ret; } failed_to_get_input_buffer: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } buffer_fill_error: { GST_ELEMENT_ERROR (self, RESOURCE, WRITE, (NULL), ("Failed to write input into the amc buffer(write %dB to a %dB buffer)", self->color_format_info.frame_size, buf->size)); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } dequeue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } queue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } flushing: { GST_DEBUG_OBJECT (self, "Flushing -- returning FLUSHING"); gst_video_codec_frame_unref (frame); return GST_FLOW_FLUSHING; } }
static gboolean gst_amc_video_enc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state) { GstAmcVideoEnc *self; GstAmcFormat *format = NULL; GstCaps *allowed_caps = NULL; gboolean is_format_change = FALSE; gboolean needs_disable = FALSE; gchar *format_string; gboolean r = FALSE; GError *err = NULL; self = GST_AMC_VIDEO_ENC (encoder); GST_DEBUG_OBJECT (self, "Setting new caps %" GST_PTR_FORMAT, state->caps); /* Check if the caps change is a real format change or if only irrelevant * parts of the caps have changed or nothing at all. */ is_format_change |= self->color_format_info.width != state->info.width; is_format_change |= self->color_format_info.height != state->info.height; needs_disable = self->started; /* If the component is not started and a real format change happens * we have to restart the component. If no real format change * happened we can just exit here. */ if (needs_disable && !is_format_change) { /* Framerate or something minor changed */ if (self->input_state) gst_video_codec_state_unref (self->input_state); self->input_state = gst_video_codec_state_ref (state); GST_DEBUG_OBJECT (self, "Already running and caps did not change the format"); return TRUE; } if (needs_disable && is_format_change) { gst_amc_video_enc_drain (self); GST_VIDEO_ENCODER_STREAM_UNLOCK (self); gst_amc_video_enc_stop (GST_VIDEO_ENCODER (self)); GST_VIDEO_ENCODER_STREAM_LOCK (self); gst_amc_video_enc_close (GST_VIDEO_ENCODER (self)); if (!gst_amc_video_enc_open (GST_VIDEO_ENCODER (self))) { GST_ERROR_OBJECT (self, "Failed to open codec again"); return FALSE; } if (!gst_amc_video_enc_start (GST_VIDEO_ENCODER (self))) { GST_ERROR_OBJECT (self, "Failed to start codec again"); } } /* srcpad task is not running at this point */ if (self->input_state) gst_video_codec_state_unref (self->input_state); self->input_state = NULL; GST_DEBUG_OBJECT (self, "picking an output format ..."); allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder)); if (!allowed_caps) { GST_DEBUG_OBJECT (self, "... but no peer, using template caps"); allowed_caps = gst_pad_get_pad_template_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder)); } GST_DEBUG_OBJECT (self, "chose caps %" GST_PTR_FORMAT, allowed_caps); allowed_caps = gst_caps_truncate (allowed_caps); format = create_amc_format (self, state, allowed_caps); if (!format) goto quit; format_string = gst_amc_format_to_string (format, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); GST_DEBUG_OBJECT (self, "Configuring codec with format: %s", GST_STR_NULL (format_string)); g_free (format_string); if (!gst_amc_codec_configure (self->codec, format, 1, &err)) { GST_ERROR_OBJECT (self, "Failed to configure codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); goto quit; } if (!gst_amc_codec_start (self->codec, &err)) { GST_ERROR_OBJECT (self, "Failed to start codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); goto quit; } self->amc_format = format; format = NULL; self->input_state = gst_video_codec_state_ref (state); self->started = TRUE; /* Start the srcpad loop again */ self->flushing = FALSE; self->downstream_flow_ret = GST_FLOW_OK; gst_pad_start_task (GST_VIDEO_ENCODER_SRC_PAD (self), (GstTaskFunction) gst_amc_video_enc_loop, encoder, NULL); r = TRUE; quit: if (allowed_caps) gst_object_unref (allowed_caps); if (format) gst_amc_format_free (format); return r; }
static gboolean gst_amc_video_dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state) { GstAmcVideoDec *self; GstAmcFormat *format; const gchar *mime; gboolean is_format_change = FALSE; gboolean needs_disable = FALSE; gchar *format_string; guint8 *codec_data = NULL; gsize codec_data_size = 0; GError *err = NULL; self = GST_AMC_VIDEO_DEC (decoder); GST_DEBUG_OBJECT (self, "Setting new caps %" GST_PTR_FORMAT, state->caps); /* Check if the caps change is a real format change or if only irrelevant * parts of the caps have changed or nothing at all. */ is_format_change |= self->color_format_info.width != state->info.width; is_format_change |= self->color_format_info.height != state->info.height; if (state->codec_data) { GstMapInfo cminfo; gst_buffer_map (state->codec_data, &cminfo, GST_MAP_READ); codec_data = g_memdup (cminfo.data, cminfo.size); codec_data_size = cminfo.size; is_format_change |= (!self->codec_data || self->codec_data_size != codec_data_size || memcmp (self->codec_data, codec_data, codec_data_size) != 0); gst_buffer_unmap (state->codec_data, &cminfo); } else if (self->codec_data) { is_format_change |= TRUE; } needs_disable = self->started; /* If the component is not started and a real format change happens * we have to restart the component. If no real format change * happened we can just exit here. */ if (needs_disable && !is_format_change) { g_free (codec_data); codec_data = NULL; codec_data_size = 0; /* Framerate or something minor changed */ self->input_state_changed = TRUE; if (self->input_state) gst_video_codec_state_unref (self->input_state); self->input_state = gst_video_codec_state_ref (state); GST_DEBUG_OBJECT (self, "Already running and caps did not change the format"); return TRUE; } if (needs_disable && is_format_change) { gst_amc_video_dec_drain (self); GST_VIDEO_DECODER_STREAM_UNLOCK (self); gst_amc_video_dec_stop (GST_VIDEO_DECODER (self)); GST_VIDEO_DECODER_STREAM_LOCK (self); gst_amc_video_dec_close (GST_VIDEO_DECODER (self)); if (!gst_amc_video_dec_open (GST_VIDEO_DECODER (self))) { GST_ERROR_OBJECT (self, "Failed to open codec again"); return FALSE; } if (!gst_amc_video_dec_start (GST_VIDEO_DECODER (self))) { GST_ERROR_OBJECT (self, "Failed to start codec again"); } } /* srcpad task is not running at this point */ if (self->input_state) gst_video_codec_state_unref (self->input_state); self->input_state = NULL; g_free (self->codec_data); self->codec_data = codec_data; self->codec_data_size = codec_data_size; mime = caps_to_mime (state->caps); if (!mime) { GST_ERROR_OBJECT (self, "Failed to convert caps to mime"); return FALSE; } format = gst_amc_format_new_video (mime, state->info.width, state->info.height, &err); if (!format) { GST_ERROR_OBJECT (self, "Failed to create video format"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } /* FIXME: This buffer needs to be valid until the codec is stopped again */ if (self->codec_data) { gst_amc_format_set_buffer (format, "csd-0", self->codec_data, self->codec_data_size, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); } format_string = gst_amc_format_to_string (format, &err); if (err) GST_ELEMENT_WARNING_FROM_ERROR (self, err); GST_DEBUG_OBJECT (self, "Configuring codec with format: %s", GST_STR_NULL (format_string)); g_free (format_string); if (!gst_amc_codec_configure (self->codec, format, 0, &err)) { GST_ERROR_OBJECT (self, "Failed to configure codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } gst_amc_format_free (format); if (!gst_amc_codec_start (self->codec, &err)) { GST_ERROR_OBJECT (self, "Failed to start codec"); GST_ELEMENT_ERROR_FROM_ERROR (self, err); return FALSE; } self->started = TRUE; self->input_state = gst_video_codec_state_ref (state); self->input_state_changed = TRUE; /* Start the srcpad loop again */ self->flushing = FALSE; self->downstream_flow_ret = GST_FLOW_OK; gst_pad_start_task (GST_VIDEO_DECODER_SRC_PAD (self), (GstTaskFunction) gst_amc_video_dec_loop, decoder, NULL); return TRUE; }
static void gst_amc_video_dec_loop (GstAmcVideoDec * self) { GstVideoCodecFrame *frame; GstFlowReturn flow_ret = GST_FLOW_OK; GstClockTimeDiff deadline; gboolean is_eos; GstAmcBuffer *buf; GstAmcBufferInfo buffer_info; gint idx; GError *err = NULL; GST_VIDEO_DECODER_STREAM_LOCK (self); retry: /*if (self->input_state_changed) { idx = INFO_OUTPUT_FORMAT_CHANGED; } else { */ GST_DEBUG_OBJECT (self, "Waiting for available output buffer"); GST_VIDEO_DECODER_STREAM_UNLOCK (self); /* Wait at most 100ms here, some codecs don't fail dequeueing if * the codec is flushing, causing deadlocks during shutdown */ idx = gst_amc_codec_dequeue_output_buffer (self->codec, &buffer_info, 100000, &err); GST_VIDEO_DECODER_STREAM_LOCK (self); /*} */ if (idx < 0) { if (self->flushing) { g_clear_error (&err); goto flushing; } switch (idx) { case INFO_OUTPUT_BUFFERS_CHANGED: /* Handled internally */ g_assert_not_reached (); break; case INFO_OUTPUT_FORMAT_CHANGED:{ GstAmcFormat *format; gchar *format_string; GST_DEBUG_OBJECT (self, "Output format has changed"); format = gst_amc_codec_get_output_format (self->codec, &err); if (!format) goto format_error; format_string = gst_amc_format_to_string (format, &err); if (!format) { gst_amc_format_free (format); goto format_error; } GST_DEBUG_OBJECT (self, "Got new output format: %s", format_string); g_free (format_string); if (!gst_amc_video_dec_set_src_caps (self, format)) { gst_amc_format_free (format); goto format_error; } gst_amc_format_free (format); goto retry; } case INFO_TRY_AGAIN_LATER: GST_DEBUG_OBJECT (self, "Dequeueing output buffer timed out"); goto retry; case G_MININT: GST_ERROR_OBJECT (self, "Failure dequeueing output buffer"); goto dequeue_error; default: g_assert_not_reached (); break; } goto retry; } GST_DEBUG_OBJECT (self, "Got output buffer at index %d: offset %d size %d time %" G_GINT64_FORMAT " flags 0x%08x", idx, buffer_info.offset, buffer_info.size, buffer_info.presentation_time_us, buffer_info.flags); frame = _find_nearest_frame (self, gst_util_uint64_scale (buffer_info.presentation_time_us, GST_USECOND, 1)); is_eos = ! !(buffer_info.flags & BUFFER_FLAG_END_OF_STREAM); buf = gst_amc_codec_get_output_buffer (self->codec, idx, &err); if (!buf) goto failed_to_get_output_buffer; if (frame && (deadline = gst_video_decoder_get_max_decode_time (GST_VIDEO_DECODER (self), frame)) < 0) { GST_WARNING_OBJECT (self, "Frame is too late, dropping (deadline %" GST_TIME_FORMAT ")", GST_TIME_ARGS (-deadline)); flow_ret = gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame); } else if (!frame && buffer_info.size > 0) { GstBuffer *outbuf; /* This sometimes happens at EOS or if the input is not properly framed, * let's handle it gracefully by allocating a new buffer for the current * caps and filling it */ GST_ERROR_OBJECT (self, "No corresponding frame found"); outbuf = gst_video_decoder_allocate_output_buffer (GST_VIDEO_DECODER (self)); if (!gst_amc_video_dec_fill_buffer (self, buf, &buffer_info, outbuf)) { gst_buffer_unref (outbuf); if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) GST_ERROR_OBJECT (self, "Failed to release output buffer index %d", idx); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_amc_buffer_free (buf); buf = NULL; goto invalid_buffer; } GST_BUFFER_PTS (outbuf) = gst_util_uint64_scale (buffer_info.presentation_time_us, GST_USECOND, 1); flow_ret = gst_pad_push (GST_VIDEO_DECODER_SRC_PAD (self), outbuf); } else if (buffer_info.size > 0) { if ((flow_ret = gst_video_decoder_allocate_output_frame (GST_VIDEO_DECODER (self), frame)) != GST_FLOW_OK) { GST_ERROR_OBJECT (self, "Failed to allocate buffer"); if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) GST_ERROR_OBJECT (self, "Failed to release output buffer index %d", idx); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_amc_buffer_free (buf); buf = NULL; goto flow_error; } if (!gst_amc_video_dec_fill_buffer (self, buf, &buffer_info, frame->output_buffer)) { gst_buffer_replace (&frame->output_buffer, NULL); gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame); if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) GST_ERROR_OBJECT (self, "Failed to release output buffer index %d", idx); if (err && !self->flushing) GST_ELEMENT_WARNING_FROM_ERROR (self, err); g_clear_error (&err); gst_amc_buffer_free (buf); buf = NULL; goto invalid_buffer; } flow_ret = gst_video_decoder_finish_frame (GST_VIDEO_DECODER (self), frame); } else if (frame != NULL) { flow_ret = gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame); } gst_amc_buffer_free (buf); buf = NULL; if (!gst_amc_codec_release_output_buffer (self->codec, idx, &err)) { if (self->flushing) { g_clear_error (&err); goto flushing; } goto failed_release; } if (is_eos || flow_ret == GST_FLOW_EOS) { GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); if (self->draining) { GST_DEBUG_OBJECT (self, "Drained"); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); } else if (flow_ret == GST_FLOW_OK) { GST_DEBUG_OBJECT (self, "Component signalled EOS"); flow_ret = GST_FLOW_EOS; } g_mutex_unlock (&self->drain_lock); GST_VIDEO_DECODER_STREAM_LOCK (self); } else { GST_DEBUG_OBJECT (self, "Finished frame: %s", gst_flow_get_name (flow_ret)); } self->downstream_flow_ret = flow_ret; if (flow_ret != GST_FLOW_OK) goto flow_error; GST_VIDEO_DECODER_STREAM_UNLOCK (self); return; dequeue_error: { GST_ELEMENT_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } format_error: { if (err) GST_ELEMENT_ERROR_FROM_ERROR (self, err); else GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("Failed to handle format")); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_release: { GST_VIDEO_DECODER_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } flushing: { GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_FLUSHING; GST_VIDEO_DECODER_STREAM_UNLOCK (self); return; } flow_error: { if (flow_ret == GST_FLOW_EOS) { GST_DEBUG_OBJECT (self, "EOS"); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); } else if (flow_ret < GST_FLOW_EOS) { GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Internal data stream error."), ("stream stopped, reason %s", gst_flow_get_name (flow_ret))); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); } else if (flow_ret == GST_FLOW_FLUSHING) { GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); } GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } failed_to_get_output_buffer: { GST_VIDEO_DECODER_ERROR_FROM_ERROR (self, err); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_ERROR; GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } invalid_buffer: { GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), ("Invalid sized input buffer")); gst_pad_push_event (GST_VIDEO_DECODER_SRC_PAD (self), gst_event_new_eos ()); gst_pad_pause_task (GST_VIDEO_DECODER_SRC_PAD (self)); self->downstream_flow_ret = GST_FLOW_NOT_NEGOTIATED; GST_VIDEO_DECODER_STREAM_UNLOCK (self); g_mutex_lock (&self->drain_lock); self->draining = FALSE; g_cond_broadcast (&self->drain_cond); g_mutex_unlock (&self->drain_lock); return; } }