| /* GStreamer |
| * Copyright (C) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>. |
| * Copyright (C) 2011 Nokia Corporation. All rights reserved. |
| * Contact: Stefan Kost <stefan.kost@nokia.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:gstaudioencoder |
| * @title: GstAudioEncoder |
| * @short_description: Base class for audio encoders |
| * @see_also: #GstBaseTransform |
| * |
| * This base class is for audio encoders turning raw audio samples into |
| * encoded audio data. |
| * |
| * GstAudioEncoder and subclass should cooperate as follows. |
| * |
| * ## Configuration |
| * |
| * * Initially, GstAudioEncoder calls @start when the encoder element |
| * is activated, which allows subclass to perform any global setup. |
| * |
| * * GstAudioEncoder calls @set_format to inform subclass of the format |
| * of input audio data that it is about to receive. Subclass should |
| * setup for encoding and configure various base class parameters |
| * appropriately, notably those directing desired input data handling. |
| * While unlikely, it might be called more than once, if changing input |
| * parameters require reconfiguration. |
| * |
| * * GstAudioEncoder calls @stop at end of all processing. |
| * |
| * As of configuration stage, and throughout processing, GstAudioEncoder |
| * maintains various parameters that provide required context, |
| * e.g. describing the format of input audio data. |
| * Conversely, subclass can and should configure these context parameters |
| * to inform base class of its expectation w.r.t. buffer handling. |
| * |
| * ## Data processing |
| * |
| * * Base class gathers input sample data (as directed by the context's |
| * frame_samples and frame_max) and provides this to subclass' @handle_frame. |
| * * If codec processing results in encoded data, subclass should call |
| * gst_audio_encoder_finish_frame() to have encoded data pushed |
| * downstream. Alternatively, it might also call |
| * gst_audio_encoder_finish_frame() (with a NULL buffer and some number of |
| * dropped samples) to indicate dropped (non-encoded) samples. |
| * * Just prior to actually pushing a buffer downstream, |
| * it is passed to @pre_push. |
| * * During the parsing process GstAudioEncoderClass will handle both |
| * srcpad and sinkpad events. Sink events will be passed to subclass |
| * if @event callback has been provided. |
| * |
| * ## Shutdown phase |
| * |
| * * GstAudioEncoder class calls @stop to inform the subclass that data |
| * parsing will be stopped. |
| * |
| * Subclass is responsible for providing pad template caps for |
| * source and sink pads. The pads need to be named "sink" and "src". It also |
| * needs to set the fixed caps on srcpad, when the format is ensured. This |
| * is typically when base class calls subclass' @set_format function, though |
| * it might be delayed until calling @gst_audio_encoder_finish_frame. |
| * |
| * In summary, above process should have subclass concentrating on |
| * codec data processing while leaving other matters to base class, |
| * such as most notably timestamp handling. While it may exert more control |
| * in this area (see e.g. @pre_push), it is very much not recommended. |
| * |
| * In particular, base class will either favor tracking upstream timestamps |
| * (at the possible expense of jitter) or aim to arrange for a perfect stream of |
| * output timestamps, depending on #GstAudioEncoder:perfect-timestamp. |
| * However, in the latter case, the input may not be so perfect or ideal, which |
| * is handled as follows. An input timestamp is compared with the expected |
| * timestamp as dictated by input sample stream and if the deviation is less |
| * than #GstAudioEncoder:tolerance, the deviation is discarded. |
| * Otherwise, it is considered a discontuinity and subsequent output timestamp |
| * is resynced to the new position after performing configured discontinuity |
| * processing. In the non-perfect-timestamp case, an upstream variation |
| * exceeding tolerance only leads to marking DISCONT on subsequent outgoing |
| * (while timestamps are adjusted to upstream regardless of variation). |
| * While DISCONT is also marked in the perfect-timestamp case, this one |
| * optionally (see #GstAudioEncoder:hard-resync) |
| * performs some additional steps, such as clipping of (early) input samples |
| * or draining all currently remaining input data, depending on the direction |
| * of the discontuinity. |
| * |
| * If perfect timestamps are arranged, it is also possible to request baseclass |
| * (usually set by subclass) to provide additional buffer metadata (in OFFSET |
| * and OFFSET_END) fields according to granule defined semantics currently |
| * needed by oggmux. Specifically, OFFSET is set to granulepos (= sample count |
| * including buffer) and OFFSET_END to corresponding timestamp (as determined |
| * by same sample count and sample rate). |
| * |
| * Things that subclass need to take care of: |
| * |
| * * Provide pad templates |
| * * Set source pad caps when appropriate |
| * * Inform base class of buffer processing needs using context's |
| * frame_samples and frame_bytes. |
| * * Set user-configurable properties to sane defaults for format and |
| * implementing codec at hand, e.g. those controlling timestamp behaviour |
| * and discontinuity processing. |
| * * Accept data in @handle_frame and provide encoded results to |
| * gst_audio_encoder_finish_frame(). |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "gstaudioencoder.h" |
| #include "gstaudioutilsprivate.h" |
| #include <gst/base/gstadapter.h> |
| #include <gst/audio/audio.h> |
| #include <gst/pbutils/descriptions.h> |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_audio_encoder_debug); |
| #define GST_CAT_DEFAULT gst_audio_encoder_debug |
| |
| #define GST_AUDIO_ENCODER_GET_PRIVATE(obj) \ |
| (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_AUDIO_ENCODER, \ |
| GstAudioEncoderPrivate)) |
| |
| enum |
| { |
| PROP_0, |
| PROP_PERFECT_TS, |
| PROP_GRANULE, |
| PROP_HARD_RESYNC, |
| PROP_TOLERANCE |
| }; |
| |
| #define DEFAULT_PERFECT_TS FALSE |
| #define DEFAULT_GRANULE FALSE |
| #define DEFAULT_HARD_RESYNC FALSE |
| #define DEFAULT_TOLERANCE 40000000 |
| #define DEFAULT_HARD_MIN FALSE |
| #define DEFAULT_DRAINABLE TRUE |
| |
| typedef struct _GstAudioEncoderContext |
| { |
| /* input */ |
| /* last negotiated input caps */ |
| GstCaps *input_caps; |
| /* last negotiated input info */ |
| GstAudioInfo info; |
| |
| /* output */ |
| GstCaps *caps; |
| GstCaps *allocation_caps; |
| gboolean output_caps_changed; |
| gint frame_samples_min, frame_samples_max; |
| gint frame_max; |
| gint lookahead; |
| /* MT-protected (with LOCK) */ |
| GstClockTime min_latency; |
| GstClockTime max_latency; |
| |
| GList *headers; |
| gboolean new_headers; |
| |
| GstAllocator *allocator; |
| GstAllocationParams params; |
| } GstAudioEncoderContext; |
| |
| struct _GstAudioEncoderPrivate |
| { |
| /* activation status */ |
| gboolean active; |
| |
| /* input base/first ts as basis for output ts; |
| * kept nearly constant for perfect_ts, |
| * otherwise resyncs to upstream ts */ |
| GstClockTime base_ts; |
| /* corresponding base granulepos */ |
| gint64 base_gp; |
| /* input samples processed and sent downstream so far (w.r.t. base_ts) */ |
| guint64 samples; |
| |
| /* currently collected sample data */ |
| GstAdapter *adapter; |
| /* offset in adapter up to which already supplied to encoder */ |
| gint offset; |
| /* mark outgoing discont */ |
| gboolean discont; |
| /* to guess duration of drained data */ |
| GstClockTime last_duration; |
| |
| /* subclass provided data in processing round */ |
| gboolean got_data; |
| /* subclass gave all it could already */ |
| gboolean drained; |
| /* subclass currently being forcibly drained */ |
| gboolean force; |
| /* need to handle changed input caps */ |
| gboolean do_caps; |
| |
| /* output bps estimatation */ |
| /* global in samples seen */ |
| guint64 samples_in; |
| /* global bytes sent out */ |
| guint64 bytes_out; |
| |
| /* context storage */ |
| GstAudioEncoderContext ctx; |
| |
| /* properties */ |
| gint64 tolerance; |
| gboolean perfect_ts; |
| gboolean hard_resync; |
| gboolean granule; |
| gboolean hard_min; |
| gboolean drainable; |
| |
| /* upstream stream tags (global tags are passed through as-is) */ |
| GstTagList *upstream_tags; |
| |
| /* subclass tags */ |
| GstTagList *tags; |
| GstTagMergeMode tags_merge_mode; |
| |
| gboolean tags_changed; |
| |
| /* pending serialized sink events, will be sent from finish_frame() */ |
| GList *pending_events; |
| }; |
| |
| |
| static GstElementClass *parent_class = NULL; |
| |
| static void gst_audio_encoder_class_init (GstAudioEncoderClass * klass); |
| static void gst_audio_encoder_init (GstAudioEncoder * parse, |
| GstAudioEncoderClass * klass); |
| |
| GType |
| gst_audio_encoder_get_type (void) |
| { |
| static GType audio_encoder_type = 0; |
| |
| if (!audio_encoder_type) { |
| static const GTypeInfo audio_encoder_info = { |
| sizeof (GstAudioEncoderClass), |
| (GBaseInitFunc) NULL, |
| (GBaseFinalizeFunc) NULL, |
| (GClassInitFunc) gst_audio_encoder_class_init, |
| NULL, |
| NULL, |
| sizeof (GstAudioEncoder), |
| 0, |
| (GInstanceInitFunc) gst_audio_encoder_init, |
| }; |
| const GInterfaceInfo preset_interface_info = { |
| NULL, /* interface_init */ |
| NULL, /* interface_finalize */ |
| NULL /* interface_data */ |
| }; |
| |
| audio_encoder_type = g_type_register_static (GST_TYPE_ELEMENT, |
| "GstAudioEncoder", &audio_encoder_info, G_TYPE_FLAG_ABSTRACT); |
| |
| g_type_add_interface_static (audio_encoder_type, GST_TYPE_PRESET, |
| &preset_interface_info); |
| } |
| return audio_encoder_type; |
| } |
| |
| static void gst_audio_encoder_finalize (GObject * object); |
| static void gst_audio_encoder_reset (GstAudioEncoder * enc, gboolean full); |
| |
| static void gst_audio_encoder_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec); |
| static void gst_audio_encoder_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec); |
| |
| static gboolean gst_audio_encoder_sink_activate_mode (GstPad * pad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| |
| static GstCaps *gst_audio_encoder_getcaps_default (GstAudioEncoder * enc, |
| GstCaps * filter); |
| |
| static gboolean gst_audio_encoder_sink_event_default (GstAudioEncoder * enc, |
| GstEvent * event); |
| static gboolean gst_audio_encoder_src_event_default (GstAudioEncoder * enc, |
| GstEvent * event); |
| static gboolean gst_audio_encoder_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_audio_encoder_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_audio_encoder_sink_setcaps (GstAudioEncoder * enc, |
| GstCaps * caps); |
| static GstFlowReturn gst_audio_encoder_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static gboolean gst_audio_encoder_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_audio_encoder_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static GstStateChangeReturn gst_audio_encoder_change_state (GstElement * |
| element, GstStateChange transition); |
| |
| static gboolean gst_audio_encoder_decide_allocation_default (GstAudioEncoder * |
| enc, GstQuery * query); |
| static gboolean gst_audio_encoder_propose_allocation_default (GstAudioEncoder * |
| enc, GstQuery * query); |
| static gboolean gst_audio_encoder_negotiate_default (GstAudioEncoder * enc); |
| static gboolean gst_audio_encoder_negotiate_unlocked (GstAudioEncoder * enc); |
| |
| static gboolean gst_audio_encoder_transform_meta_default (GstAudioEncoder * |
| encoder, GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf); |
| |
| static gboolean gst_audio_encoder_sink_query_default (GstAudioEncoder * encoder, |
| GstQuery * query); |
| static gboolean gst_audio_encoder_src_query_default (GstAudioEncoder * encoder, |
| GstQuery * query); |
| |
| static void |
| gst_audio_encoder_class_init (GstAudioEncoderClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| gstelement_class = GST_ELEMENT_CLASS (klass); |
| parent_class = g_type_class_peek_parent (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_audio_encoder_debug, "audioencoder", 0, |
| "audio encoder base class"); |
| |
| g_type_class_add_private (klass, sizeof (GstAudioEncoderPrivate)); |
| |
| gobject_class->set_property = gst_audio_encoder_set_property; |
| gobject_class->get_property = gst_audio_encoder_get_property; |
| |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_audio_encoder_finalize); |
| |
| /* properties */ |
| g_object_class_install_property (gobject_class, PROP_PERFECT_TS, |
| g_param_spec_boolean ("perfect-timestamp", "Perfect Timestamps", |
| "Favour perfect timestamps over tracking upstream timestamps", |
| DEFAULT_PERFECT_TS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_GRANULE, |
| g_param_spec_boolean ("mark-granule", "Granule Marking", |
| "Apply granule semantics to buffer metadata (implies perfect-timestamp)", |
| DEFAULT_GRANULE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_HARD_RESYNC, |
| g_param_spec_boolean ("hard-resync", "Hard Resync", |
| "Perform clipping and sample flushing upon discontinuity", |
| DEFAULT_HARD_RESYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_TOLERANCE, |
| g_param_spec_int64 ("tolerance", "Tolerance", |
| "Consider discontinuity if timestamp jitter/imperfection exceeds tolerance (ns)", |
| 0, G_MAXINT64, DEFAULT_TOLERANCE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_change_state); |
| |
| klass->getcaps = gst_audio_encoder_getcaps_default; |
| klass->sink_event = gst_audio_encoder_sink_event_default; |
| klass->src_event = gst_audio_encoder_src_event_default; |
| klass->sink_query = gst_audio_encoder_sink_query_default; |
| klass->src_query = gst_audio_encoder_src_query_default; |
| klass->propose_allocation = gst_audio_encoder_propose_allocation_default; |
| klass->decide_allocation = gst_audio_encoder_decide_allocation_default; |
| klass->negotiate = gst_audio_encoder_negotiate_default; |
| klass->transform_meta = gst_audio_encoder_transform_meta_default; |
| } |
| |
| static void |
| gst_audio_encoder_init (GstAudioEncoder * enc, GstAudioEncoderClass * bclass) |
| { |
| GstPadTemplate *pad_template; |
| |
| GST_DEBUG_OBJECT (enc, "gst_audio_encoder_init"); |
| |
| enc->priv = GST_AUDIO_ENCODER_GET_PRIVATE (enc); |
| |
| /* only push mode supported */ |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink"); |
| g_return_if_fail (pad_template != NULL); |
| enc->sinkpad = gst_pad_new_from_template (pad_template, "sink"); |
| gst_pad_set_event_function (enc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_event)); |
| gst_pad_set_query_function (enc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_query)); |
| gst_pad_set_chain_function (enc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_chain)); |
| gst_pad_set_activatemode_function (enc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_activate_mode)); |
| gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad); |
| |
| GST_DEBUG_OBJECT (enc, "sinkpad created"); |
| |
| /* and we don't mind upstream traveling stuff that much ... */ |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); |
| g_return_if_fail (pad_template != NULL); |
| enc->srcpad = gst_pad_new_from_template (pad_template, "src"); |
| gst_pad_set_event_function (enc->srcpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_src_event)); |
| gst_pad_set_query_function (enc->srcpad, |
| GST_DEBUG_FUNCPTR (gst_audio_encoder_src_query)); |
| gst_pad_use_fixed_caps (enc->srcpad); |
| gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad); |
| GST_DEBUG_OBJECT (enc, "src created"); |
| |
| enc->priv->adapter = gst_adapter_new (); |
| |
| g_rec_mutex_init (&enc->stream_lock); |
| |
| /* property default */ |
| enc->priv->granule = DEFAULT_GRANULE; |
| enc->priv->perfect_ts = DEFAULT_PERFECT_TS; |
| enc->priv->hard_resync = DEFAULT_HARD_RESYNC; |
| enc->priv->tolerance = DEFAULT_TOLERANCE; |
| enc->priv->hard_min = DEFAULT_HARD_MIN; |
| enc->priv->drainable = DEFAULT_DRAINABLE; |
| |
| /* init state */ |
| enc->priv->ctx.min_latency = 0; |
| enc->priv->ctx.max_latency = 0; |
| gst_audio_encoder_reset (enc, TRUE); |
| GST_DEBUG_OBJECT (enc, "init ok"); |
| } |
| |
| static void |
| gst_audio_encoder_reset (GstAudioEncoder * enc, gboolean full) |
| { |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| |
| GST_LOG_OBJECT (enc, "reset full %d", full); |
| |
| if (full) { |
| enc->priv->active = FALSE; |
| GST_OBJECT_LOCK (enc); |
| enc->priv->samples_in = 0; |
| enc->priv->bytes_out = 0; |
| GST_OBJECT_UNLOCK (enc); |
| |
| g_list_foreach (enc->priv->ctx.headers, (GFunc) gst_buffer_unref, NULL); |
| g_list_free (enc->priv->ctx.headers); |
| enc->priv->ctx.headers = NULL; |
| enc->priv->ctx.new_headers = FALSE; |
| |
| if (enc->priv->ctx.allocator) |
| gst_object_unref (enc->priv->ctx.allocator); |
| enc->priv->ctx.allocator = NULL; |
| |
| GST_OBJECT_LOCK (enc); |
| gst_caps_replace (&enc->priv->ctx.input_caps, NULL); |
| gst_caps_replace (&enc->priv->ctx.caps, NULL); |
| gst_caps_replace (&enc->priv->ctx.allocation_caps, NULL); |
| |
| memset (&enc->priv->ctx, 0, sizeof (enc->priv->ctx)); |
| gst_audio_info_init (&enc->priv->ctx.info); |
| GST_OBJECT_UNLOCK (enc); |
| |
| if (enc->priv->upstream_tags) { |
| gst_tag_list_unref (enc->priv->upstream_tags); |
| enc->priv->upstream_tags = NULL; |
| } |
| if (enc->priv->tags) |
| gst_tag_list_unref (enc->priv->tags); |
| enc->priv->tags = NULL; |
| enc->priv->tags_merge_mode = GST_TAG_MERGE_APPEND; |
| enc->priv->tags_changed = FALSE; |
| |
| g_list_foreach (enc->priv->pending_events, (GFunc) gst_event_unref, NULL); |
| g_list_free (enc->priv->pending_events); |
| enc->priv->pending_events = NULL; |
| } |
| |
| gst_segment_init (&enc->input_segment, GST_FORMAT_TIME); |
| gst_segment_init (&enc->output_segment, GST_FORMAT_TIME); |
| |
| gst_adapter_clear (enc->priv->adapter); |
| enc->priv->got_data = FALSE; |
| enc->priv->drained = TRUE; |
| enc->priv->offset = 0; |
| enc->priv->base_ts = GST_CLOCK_TIME_NONE; |
| enc->priv->base_gp = -1; |
| enc->priv->samples = 0; |
| enc->priv->discont = FALSE; |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| } |
| |
| static void |
| gst_audio_encoder_finalize (GObject * object) |
| { |
| GstAudioEncoder *enc = GST_AUDIO_ENCODER (object); |
| |
| g_object_unref (enc->priv->adapter); |
| |
| g_rec_mutex_clear (&enc->stream_lock); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static GstStateChangeReturn |
| gst_audio_encoder_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; |
| GstAudioEncoder *enc = GST_AUDIO_ENCODER (element); |
| GstAudioEncoderClass *klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| if (klass->open) { |
| if (!klass->open (enc)) |
| goto open_failed; |
| } |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| if (klass->close) { |
| if (!klass->close (enc)) |
| goto close_failed; |
| } |
| default: |
| break; |
| } |
| |
| return ret; |
| |
| open_failed: |
| { |
| GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL), ("Failed to open codec")); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| close_failed: |
| { |
| GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL), ("Failed to close codec")); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| } |
| |
| static gboolean |
| gst_audio_encoder_push_event (GstAudioEncoder * enc, GstEvent * event) |
| { |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEGMENT:{ |
| GstSegment seg; |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| gst_event_copy_segment (event, &seg); |
| |
| GST_DEBUG_OBJECT (enc, "starting segment %" GST_SEGMENT_FORMAT, &seg); |
| |
| enc->output_segment = seg; |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return gst_pad_push_event (enc->srcpad, event); |
| } |
| |
| static inline void |
| gst_audio_encoder_push_pending_events (GstAudioEncoder * enc) |
| { |
| GstAudioEncoderPrivate *priv = enc->priv; |
| |
| if (priv->pending_events) { |
| GList *pending_events, *l; |
| |
| pending_events = priv->pending_events; |
| priv->pending_events = NULL; |
| |
| GST_DEBUG_OBJECT (enc, "Pushing pending events"); |
| for (l = pending_events; l; l = l->next) |
| gst_audio_encoder_push_event (enc, l->data); |
| g_list_free (pending_events); |
| } |
| } |
| |
| static GstEvent * |
| gst_audio_encoder_create_merged_tags_event (GstAudioEncoder * enc) |
| { |
| GstTagList *merged_tags; |
| |
| GST_LOG_OBJECT (enc, "upstream : %" GST_PTR_FORMAT, enc->priv->upstream_tags); |
| GST_LOG_OBJECT (enc, "encoder : %" GST_PTR_FORMAT, enc->priv->tags); |
| GST_LOG_OBJECT (enc, "mode : %d", enc->priv->tags_merge_mode); |
| |
| merged_tags = |
| gst_tag_list_merge (enc->priv->upstream_tags, enc->priv->tags, |
| enc->priv->tags_merge_mode); |
| |
| GST_DEBUG_OBJECT (enc, "merged : %" GST_PTR_FORMAT, merged_tags); |
| |
| if (merged_tags == NULL) |
| return NULL; |
| |
| if (gst_tag_list_is_empty (merged_tags)) { |
| gst_tag_list_unref (merged_tags); |
| return NULL; |
| } |
| |
| /* add codec info to pending tags */ |
| #if 0 |
| caps = gst_pad_get_current_caps (enc->srcpad); |
| gst_pb_utils_add_codec_description_to_tag_list (merged_tags, |
| GST_TAG_AUDIO_CODEC, caps); |
| #endif |
| |
| return gst_event_new_tag (merged_tags); |
| } |
| |
| static void |
| gst_audio_encoder_check_and_push_pending_tags (GstAudioEncoder * enc) |
| { |
| if (enc->priv->tags_changed) { |
| GstEvent *tags_event; |
| |
| tags_event = gst_audio_encoder_create_merged_tags_event (enc); |
| |
| if (tags_event != NULL) |
| gst_audio_encoder_push_event (enc, tags_event); |
| |
| enc->priv->tags_changed = FALSE; |
| } |
| } |
| |
| |
| static gboolean |
| gst_audio_encoder_transform_meta_default (GstAudioEncoder * |
| encoder, GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf) |
| { |
| const GstMetaInfo *info = meta->info; |
| const gchar *const *tags; |
| |
| tags = gst_meta_api_type_get_tags (info->api); |
| |
| if (!tags || (g_strv_length ((gchar **) tags) == 1 |
| && gst_meta_api_type_has_tag (info->api, |
| g_quark_from_string (GST_META_TAG_AUDIO_STR)))) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| typedef struct |
| { |
| GstAudioEncoder *encoder; |
| GstBuffer *outbuf; |
| } CopyMetaData; |
| |
| static gboolean |
| foreach_metadata (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data) |
| { |
| CopyMetaData *data = user_data; |
| GstAudioEncoder *encoder = data->encoder; |
| GstAudioEncoderClass *klass = GST_AUDIO_ENCODER_GET_CLASS (encoder); |
| GstBuffer *outbuf = data->outbuf; |
| const GstMetaInfo *info = (*meta)->info; |
| gboolean do_copy = FALSE; |
| |
| if (gst_meta_api_type_has_tag (info->api, _gst_meta_tag_memory)) { |
| /* never call the transform_meta with memory specific metadata */ |
| GST_DEBUG_OBJECT (encoder, "not copying memory specific metadata %s", |
| g_type_name (info->api)); |
| do_copy = FALSE; |
| } else if (klass->transform_meta) { |
| do_copy = klass->transform_meta (encoder, outbuf, *meta, inbuf); |
| GST_DEBUG_OBJECT (encoder, "transformed metadata %s: copy: %d", |
| g_type_name (info->api), do_copy); |
| } |
| |
| /* we only copy metadata when the subclass implemented a transform_meta |
| * function and when it returns %TRUE */ |
| if (do_copy && info->transform_func) { |
| GstMetaTransformCopy copy_data = { FALSE, 0, -1 }; |
| GST_DEBUG_OBJECT (encoder, "copy metadata %s", g_type_name (info->api)); |
| /* simply copy then */ |
| info->transform_func (outbuf, *meta, inbuf, |
| _gst_meta_transform_copy, ©_data); |
| } |
| return TRUE; |
| } |
| |
| /** |
| * gst_audio_encoder_finish_frame: |
| * @enc: a #GstAudioEncoder |
| * @buffer: encoded data |
| * @samples: number of samples (per channel) represented by encoded data |
| * |
| * Collects encoded data and pushes encoded data downstream. |
| * Source pad caps must be set when this is called. |
| * |
| * If @samples < 0, then best estimate is all samples provided to encoder |
| * (subclass) so far. @buf may be NULL, in which case next number of @samples |
| * are considered discarded, e.g. as a result of discontinuous transmission, |
| * and a discontinuity is marked. |
| * |
| * Note that samples received in gst_audio_encoder_handle_frame() |
| * may be invalidated by a call to this function. |
| * |
| * Returns: a #GstFlowReturn that should be escalated to caller (of caller) |
| */ |
| GstFlowReturn |
| gst_audio_encoder_finish_frame (GstAudioEncoder * enc, GstBuffer * buf, |
| gint samples) |
| { |
| GstAudioEncoderClass *klass; |
| GstAudioEncoderPrivate *priv; |
| GstAudioEncoderContext *ctx; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean needs_reconfigure = FALSE; |
| GstBuffer *inbuf = NULL; |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| priv = enc->priv; |
| ctx = &enc->priv->ctx; |
| |
| /* subclass should not hand us no data */ |
| g_return_val_if_fail (buf == NULL || gst_buffer_get_size (buf) > 0, |
| GST_FLOW_ERROR); |
| |
| /* subclass should know what it is producing by now */ |
| if (!ctx->caps) |
| goto no_caps; |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| |
| GST_LOG_OBJECT (enc, |
| "accepting %" G_GSIZE_FORMAT " bytes encoded data as %d samples", |
| buf ? gst_buffer_get_size (buf) : -1, samples); |
| |
| needs_reconfigure = gst_pad_check_reconfigure (enc->srcpad); |
| if (G_UNLIKELY (ctx->output_caps_changed || needs_reconfigure)) { |
| if (!gst_audio_encoder_negotiate_unlocked (enc)) { |
| gst_pad_mark_reconfigure (enc->srcpad); |
| if (GST_PAD_IS_FLUSHING (enc->srcpad)) |
| ret = GST_FLOW_FLUSHING; |
| else |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| if (buf) |
| gst_buffer_unref (buf); |
| goto exit; |
| } |
| } |
| |
| /* mark subclass still alive and providing */ |
| if (G_LIKELY (buf)) |
| priv->got_data = TRUE; |
| |
| gst_audio_encoder_push_pending_events (enc); |
| |
| /* send after pending events, which likely includes segment event */ |
| gst_audio_encoder_check_and_push_pending_tags (enc); |
| |
| /* remove corresponding samples from input */ |
| if (samples < 0) |
| samples = (enc->priv->offset / ctx->info.bpf); |
| |
| if (G_LIKELY (samples)) { |
| /* track upstream ts if so configured */ |
| if (!enc->priv->perfect_ts) { |
| guint64 ts, distance; |
| |
| ts = gst_adapter_prev_pts (priv->adapter, &distance); |
| g_assert (distance % ctx->info.bpf == 0); |
| distance /= ctx->info.bpf; |
| GST_LOG_OBJECT (enc, "%" G_GUINT64_FORMAT " samples past prev_ts %" |
| GST_TIME_FORMAT, distance, GST_TIME_ARGS (ts)); |
| GST_LOG_OBJECT (enc, "%" G_GUINT64_FORMAT " samples past base_ts %" |
| GST_TIME_FORMAT, priv->samples, GST_TIME_ARGS (priv->base_ts)); |
| /* when draining adapter might be empty and no ts to offer */ |
| if (GST_CLOCK_TIME_IS_VALID (ts) && ts != priv->base_ts) { |
| GstClockTimeDiff diff; |
| GstClockTime old_ts, next_ts; |
| |
| /* passed into another buffer; |
| * mild check for discontinuity and only mark if so */ |
| next_ts = ts + |
| gst_util_uint64_scale (distance, GST_SECOND, ctx->info.rate); |
| old_ts = priv->base_ts + |
| gst_util_uint64_scale (priv->samples, GST_SECOND, ctx->info.rate); |
| diff = GST_CLOCK_DIFF (next_ts, old_ts); |
| GST_LOG_OBJECT (enc, "ts diff %d ms", (gint) (diff / GST_MSECOND)); |
| /* only mark discontinuity if beyond tolerance */ |
| if (G_UNLIKELY (diff < -enc->priv->tolerance || |
| diff > enc->priv->tolerance)) { |
| GST_DEBUG_OBJECT (enc, "marked discont"); |
| priv->discont = TRUE; |
| } |
| if (diff > GST_SECOND / ctx->info.rate / 2 || |
| diff < -GST_SECOND / ctx->info.rate / 2) { |
| GST_LOG_OBJECT (enc, "new upstream ts %" GST_TIME_FORMAT |
| " at distance %" G_GUINT64_FORMAT, GST_TIME_ARGS (ts), distance); |
| /* re-sync to upstream ts */ |
| priv->base_ts = ts; |
| priv->samples = distance; |
| } else { |
| GST_LOG_OBJECT (enc, "new upstream ts only introduces jitter"); |
| } |
| } |
| } |
| /* advance sample view */ |
| if (G_UNLIKELY (samples * ctx->info.bpf > priv->offset)) { |
| guint avail = gst_adapter_available (priv->adapter); |
| |
| if (G_LIKELY (!priv->force)) { |
| /* we should have received EOS to enable force */ |
| goto overflow; |
| } else { |
| priv->offset = 0; |
| if (avail > 0 && samples * ctx->info.bpf >= avail) { |
| inbuf = gst_adapter_take_buffer_fast (priv->adapter, avail); |
| gst_adapter_clear (priv->adapter); |
| } else if (avail > 0) { |
| inbuf = |
| gst_adapter_take_buffer_fast (priv->adapter, |
| samples * ctx->info.bpf); |
| } |
| } |
| } else { |
| guint avail = gst_adapter_available (priv->adapter); |
| |
| if (avail > 0) { |
| inbuf = |
| gst_adapter_take_buffer_fast (priv->adapter, |
| samples * ctx->info.bpf); |
| } |
| priv->offset -= samples * ctx->info.bpf; |
| /* avoid subsequent stray prev_ts */ |
| if (G_UNLIKELY (gst_adapter_available (priv->adapter) == 0)) |
| gst_adapter_clear (priv->adapter); |
| } |
| /* sample count advanced below after buffer handling */ |
| } |
| |
| /* collect output */ |
| if (G_LIKELY (buf)) { |
| gsize size; |
| |
| /* Pushing headers first */ |
| if (G_UNLIKELY (priv->ctx.new_headers)) { |
| GList *tmp; |
| |
| GST_DEBUG_OBJECT (enc, "Sending headers"); |
| |
| for (tmp = priv->ctx.headers; tmp; tmp = tmp->next) { |
| GstBuffer *tmpbuf = gst_buffer_ref (tmp->data); |
| |
| tmpbuf = gst_buffer_make_writable (tmpbuf); |
| size = gst_buffer_get_size (tmpbuf); |
| |
| if (G_UNLIKELY (priv->discont)) { |
| GST_LOG_OBJECT (enc, "marking discont"); |
| GST_BUFFER_FLAG_SET (tmpbuf, GST_BUFFER_FLAG_DISCONT); |
| priv->discont = FALSE; |
| } |
| |
| /* Ogg codecs like Vorbis use offset/offset-end in a special |
| * way and both should be 0 for these codecs */ |
| if (priv->base_gp >= 0) { |
| GST_BUFFER_OFFSET (tmpbuf) = 0; |
| GST_BUFFER_OFFSET_END (tmpbuf) = 0; |
| } else { |
| GST_BUFFER_OFFSET (tmpbuf) = priv->bytes_out; |
| GST_BUFFER_OFFSET_END (tmpbuf) = priv->bytes_out + size; |
| } |
| |
| GST_OBJECT_LOCK (enc); |
| priv->bytes_out += size; |
| GST_OBJECT_UNLOCK (enc); |
| |
| ret = gst_pad_push (enc->srcpad, tmpbuf); |
| if (ret != GST_FLOW_OK) { |
| GST_WARNING_OBJECT (enc, "pushing header returned %s", |
| gst_flow_get_name (ret)); |
| goto exit; |
| } |
| } |
| priv->ctx.new_headers = FALSE; |
| } |
| |
| size = gst_buffer_get_size (buf); |
| |
| GST_LOG_OBJECT (enc, "taking %" G_GSIZE_FORMAT " bytes for output", size); |
| buf = gst_buffer_make_writable (buf); |
| |
| /* decorate */ |
| if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (priv->base_ts))) { |
| /* FIXME ? lookahead could lead to weird ts and duration ? |
| * (particularly if not in perfect mode) */ |
| /* mind sample rounding and produce perfect output */ |
| GST_BUFFER_TIMESTAMP (buf) = priv->base_ts + |
| gst_util_uint64_scale (priv->samples - ctx->lookahead, GST_SECOND, |
| ctx->info.rate); |
| GST_BUFFER_DTS (buf) = GST_BUFFER_TIMESTAMP (buf); |
| GST_DEBUG_OBJECT (enc, "out samples %d", samples); |
| if (G_LIKELY (samples > 0)) { |
| priv->samples += samples; |
| GST_BUFFER_DURATION (buf) = priv->base_ts + |
| gst_util_uint64_scale (priv->samples - ctx->lookahead, GST_SECOND, |
| ctx->info.rate) - GST_BUFFER_TIMESTAMP (buf); |
| priv->last_duration = GST_BUFFER_DURATION (buf); |
| } else { |
| /* duration forecast in case of handling remainder; |
| * the last one is probably like the previous one ... */ |
| GST_BUFFER_DURATION (buf) = priv->last_duration; |
| } |
| if (priv->base_gp >= 0) { |
| /* pamper oggmux */ |
| /* FIXME: in longer run, muxer should take care of this ... */ |
| /* offset_end = granulepos for ogg muxer */ |
| GST_BUFFER_OFFSET_END (buf) = priv->base_gp + priv->samples - |
| enc->priv->ctx.lookahead; |
| /* offset = timestamp corresponding to granulepos for ogg muxer */ |
| GST_BUFFER_OFFSET (buf) = |
| GST_FRAMES_TO_CLOCK_TIME (GST_BUFFER_OFFSET_END (buf), |
| ctx->info.rate); |
| } else { |
| GST_BUFFER_OFFSET (buf) = priv->bytes_out; |
| GST_BUFFER_OFFSET_END (buf) = priv->bytes_out + size; |
| } |
| } |
| |
| if (klass->transform_meta) { |
| if (G_LIKELY (inbuf)) { |
| CopyMetaData data; |
| |
| data.encoder = enc; |
| data.outbuf = buf; |
| gst_buffer_foreach_meta (inbuf, foreach_metadata, &data); |
| } else { |
| GST_WARNING_OBJECT (enc, |
| "Can't copy metadata because input buffer disappeared"); |
| } |
| } |
| |
| GST_OBJECT_LOCK (enc); |
| priv->bytes_out += size; |
| GST_OBJECT_UNLOCK (enc); |
| |
| if (G_UNLIKELY (priv->discont)) { |
| GST_LOG_OBJECT (enc, "marking discont"); |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
| priv->discont = FALSE; |
| } |
| |
| if (klass->pre_push) { |
| /* last chance for subclass to do some dirty stuff */ |
| ret = klass->pre_push (enc, &buf); |
| if (ret != GST_FLOW_OK || !buf) { |
| GST_DEBUG_OBJECT (enc, "subclass returned %s, buf %p", |
| gst_flow_get_name (ret), buf); |
| |
| if (buf) |
| gst_buffer_unref (buf); |
| goto exit; |
| } |
| } |
| |
| GST_LOG_OBJECT (enc, |
| "pushing buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT |
| ", duration %" GST_TIME_FORMAT, size, |
| GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); |
| |
| ret = gst_pad_push (enc->srcpad, buf); |
| GST_LOG_OBJECT (enc, "buffer pushed: %s", gst_flow_get_name (ret)); |
| } else { |
| /* merely advance samples, most work for that already done above */ |
| priv->samples += samples; |
| } |
| |
| exit: |
| if (inbuf) |
| gst_buffer_unref (inbuf); |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_caps: |
| { |
| GST_ELEMENT_ERROR (enc, STREAM, ENCODE, ("no caps set"), (NULL)); |
| if (buf) |
| gst_buffer_unref (buf); |
| return GST_FLOW_ERROR; |
| } |
| overflow: |
| { |
| GST_ELEMENT_ERROR (enc, STREAM, ENCODE, |
| ("received more encoded samples %d than provided %d as inputs", |
| samples, priv->offset / ctx->info.bpf), (NULL)); |
| if (buf) |
| gst_buffer_unref (buf); |
| ret = GST_FLOW_ERROR; |
| /* no way we can let this pass */ |
| g_assert_not_reached (); |
| /* really no way */ |
| goto exit; |
| } |
| } |
| |
| /* adapter tracking idea: |
| * - start of adapter corresponds with what has already been encoded |
| * (i.e. really returned by encoder subclass) |
| * - start + offset is what needs to be fed to subclass next */ |
| static GstFlowReturn |
| gst_audio_encoder_push_buffers (GstAudioEncoder * enc, gboolean force) |
| { |
| GstAudioEncoderClass *klass; |
| GstAudioEncoderPrivate *priv; |
| GstAudioEncoderContext *ctx; |
| gint av, need; |
| GstBuffer *buf; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| g_return_val_if_fail (klass->handle_frame != NULL, GST_FLOW_ERROR); |
| |
| priv = enc->priv; |
| ctx = &enc->priv->ctx; |
| |
| while (ret == GST_FLOW_OK) { |
| |
| buf = NULL; |
| av = gst_adapter_available (priv->adapter); |
| |
| g_assert (priv->offset <= av); |
| av -= priv->offset; |
| |
| need = |
| ctx->frame_samples_min > |
| 0 ? ctx->frame_samples_min * ctx->info.bpf : av; |
| GST_LOG_OBJECT (enc, "available: %d, needed: %d, force: %d", av, need, |
| force); |
| |
| if ((need > av) || !av) { |
| if (G_UNLIKELY (force)) { |
| priv->force = TRUE; |
| need = av; |
| } else { |
| break; |
| } |
| } else { |
| priv->force = FALSE; |
| } |
| |
| if (ctx->frame_samples_max > 0) |
| need = MIN (av, ctx->frame_samples_max * ctx->info.bpf); |
| |
| if (ctx->frame_samples_min == ctx->frame_samples_max) { |
| /* if we have some extra metadata, |
| * provide for integer multiple of frames to allow for better granularity |
| * of processing */ |
| if (ctx->frame_samples_min > 0 && need) { |
| if (ctx->frame_max > 1) |
| need = need * MIN ((av / need), ctx->frame_max); |
| else if (ctx->frame_max == 0) |
| need = need * (av / need); |
| } |
| } |
| |
| priv->got_data = FALSE; |
| if (G_LIKELY (need)) { |
| const guint8 *data; |
| |
| data = gst_adapter_map (priv->adapter, priv->offset + need); |
| buf = |
| gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, |
| (gpointer) data, priv->offset + need, priv->offset, need, NULL, NULL); |
| } else if (!priv->drainable) { |
| GST_DEBUG_OBJECT (enc, "non-drainable and no more data"); |
| goto finish; |
| } |
| |
| GST_LOG_OBJECT (enc, "providing subclass with %d bytes at offset %d", |
| need, priv->offset); |
| |
| /* mark this already as consumed, |
| * which it should be when subclass gives us data in exchange for samples */ |
| priv->offset += need; |
| GST_OBJECT_LOCK (enc); |
| priv->samples_in += need / ctx->info.bpf; |
| GST_OBJECT_UNLOCK (enc); |
| |
| /* subclass might not want to be bothered with leftover data, |
| * so take care of that here if so, otherwise pass along */ |
| if (G_UNLIKELY (priv->force && priv->hard_min && buf)) { |
| GST_DEBUG_OBJECT (enc, "bypassing subclass with leftover"); |
| ret = gst_audio_encoder_finish_frame (enc, NULL, -1); |
| } else { |
| ret = klass->handle_frame (enc, buf); |
| } |
| |
| if (G_LIKELY (buf)) { |
| gst_buffer_unref (buf); |
| gst_adapter_unmap (priv->adapter); |
| } |
| |
| finish: |
| /* no data to feed, no leftover provided, then bail out */ |
| if (G_UNLIKELY (!buf && !priv->got_data)) { |
| priv->drained = TRUE; |
| GST_LOG_OBJECT (enc, "no more data drained from subclass"); |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_audio_encoder_drain (GstAudioEncoder * enc) |
| { |
| GST_DEBUG_OBJECT (enc, "draining"); |
| if (enc->priv->drained) |
| return GST_FLOW_OK; |
| else { |
| GST_DEBUG_OBJECT (enc, "... really"); |
| return gst_audio_encoder_push_buffers (enc, TRUE); |
| } |
| } |
| |
| static void |
| gst_audio_encoder_set_base_gp (GstAudioEncoder * enc) |
| { |
| GstClockTime ts; |
| |
| if (!enc->priv->granule) |
| return; |
| |
| /* use running time for granule */ |
| /* incoming data is clipped, so a valid input should yield a valid output */ |
| ts = gst_segment_to_running_time (&enc->input_segment, GST_FORMAT_TIME, |
| enc->priv->base_ts); |
| if (GST_CLOCK_TIME_IS_VALID (ts)) { |
| enc->priv->base_gp = |
| GST_CLOCK_TIME_TO_FRAMES (enc->priv->base_ts, enc->priv->ctx.info.rate); |
| GST_DEBUG_OBJECT (enc, "new base gp %" G_GINT64_FORMAT, enc->priv->base_gp); |
| } else { |
| /* should reasonably have a valid base, |
| * otherwise start at 0 if we did not already start there earlier */ |
| if (enc->priv->base_gp < 0) { |
| enc->priv->base_gp = 0; |
| GST_DEBUG_OBJECT (enc, "new base gp %" G_GINT64_FORMAT, |
| enc->priv->base_gp); |
| } |
| } |
| } |
| |
| static GstFlowReturn |
| gst_audio_encoder_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstAudioEncoder *enc; |
| GstAudioEncoderPrivate *priv; |
| GstAudioEncoderContext *ctx; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean discont; |
| gsize size; |
| |
| enc = GST_AUDIO_ENCODER (parent); |
| |
| priv = enc->priv; |
| ctx = &enc->priv->ctx; |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| |
| if (G_UNLIKELY (priv->do_caps)) { |
| GstCaps *caps = gst_pad_get_current_caps (enc->sinkpad); |
| if (!caps) |
| goto not_negotiated; |
| if (!gst_audio_encoder_sink_setcaps (enc, caps)) { |
| gst_caps_unref (caps); |
| goto not_negotiated; |
| } |
| gst_caps_unref (caps); |
| priv->do_caps = FALSE; |
| } |
| |
| /* should know what is coming by now */ |
| if (!ctx->info.bpf) |
| goto not_negotiated; |
| |
| size = gst_buffer_get_size (buffer); |
| |
| GST_LOG_OBJECT (enc, |
| "received buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT |
| ", duration %" GST_TIME_FORMAT, size, |
| GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); |
| |
| /* input shoud be whole number of sample frames */ |
| if (size % ctx->info.bpf) |
| goto wrong_buffer; |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| { |
| GstClockTime duration; |
| GstClockTimeDiff diff; |
| |
| /* verify buffer duration */ |
| duration = gst_util_uint64_scale (size, GST_SECOND, |
| ctx->info.rate * ctx->info.bpf); |
| diff = GST_CLOCK_DIFF (duration, GST_BUFFER_DURATION (buffer)); |
| if (GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && |
| (diff > GST_SECOND / ctx->info.rate / 2 || |
| diff < -GST_SECOND / ctx->info.rate / 2)) { |
| GST_DEBUG_OBJECT (enc, "incoming buffer had incorrect duration %" |
| GST_TIME_FORMAT ", expected duration %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), |
| GST_TIME_ARGS (duration)); |
| } |
| } |
| #endif |
| |
| discont = GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT); |
| if (G_UNLIKELY (discont)) { |
| GST_LOG_OBJECT (buffer, "marked discont"); |
| enc->priv->discont = discont; |
| } |
| |
| /* clip to segment */ |
| buffer = gst_audio_buffer_clip (buffer, &enc->input_segment, ctx->info.rate, |
| ctx->info.bpf); |
| if (G_UNLIKELY (!buffer)) { |
| GST_DEBUG_OBJECT (buffer, "no data after clipping to segment"); |
| goto done; |
| } |
| |
| size = gst_buffer_get_size (buffer); |
| |
| GST_LOG_OBJECT (enc, |
| "buffer after segment clipping has size %" G_GSIZE_FORMAT " with ts %" |
| GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT, size, |
| GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); |
| |
| if (!GST_CLOCK_TIME_IS_VALID (priv->base_ts)) { |
| priv->base_ts = GST_BUFFER_TIMESTAMP (buffer); |
| GST_DEBUG_OBJECT (enc, "new base ts %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (priv->base_ts)); |
| gst_audio_encoder_set_base_gp (enc); |
| } |
| |
| /* check for continuity; |
| * checked elsewhere in non-perfect case */ |
| if (enc->priv->perfect_ts) { |
| GstClockTimeDiff diff = 0; |
| GstClockTime next_ts = 0; |
| |
| if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (priv->base_ts)) { |
| guint64 samples; |
| |
| samples = priv->samples + |
| gst_adapter_available (priv->adapter) / ctx->info.bpf; |
| next_ts = priv->base_ts + |
| gst_util_uint64_scale (samples, GST_SECOND, ctx->info.rate); |
| GST_LOG_OBJECT (enc, "buffer is %" G_GUINT64_FORMAT |
| " samples past base_ts %" GST_TIME_FORMAT |
| ", expected ts %" GST_TIME_FORMAT, samples, |
| GST_TIME_ARGS (priv->base_ts), GST_TIME_ARGS (next_ts)); |
| diff = GST_CLOCK_DIFF (next_ts, GST_BUFFER_TIMESTAMP (buffer)); |
| GST_LOG_OBJECT (enc, "ts diff %d ms", (gint) (diff / GST_MSECOND)); |
| /* if within tolerance, |
| * discard buffer ts and carry on producing perfect stream, |
| * otherwise clip or resync to ts */ |
| if (G_UNLIKELY (diff < -enc->priv->tolerance || |
| diff > enc->priv->tolerance)) { |
| GST_DEBUG_OBJECT (enc, "marked discont"); |
| discont = TRUE; |
| } |
| } |
| |
| /* do some fancy tweaking in hard resync case */ |
| if (discont && enc->priv->hard_resync) { |
| if (diff < 0) { |
| guint64 diff_bytes; |
| |
| GST_WARNING_OBJECT (enc, "Buffer is older than expected ts %" |
| GST_TIME_FORMAT ". Clipping buffer", GST_TIME_ARGS (next_ts)); |
| |
| diff_bytes = |
| GST_CLOCK_TIME_TO_FRAMES (-diff, ctx->info.rate) * ctx->info.bpf; |
| if (diff_bytes >= size) { |
| gst_buffer_unref (buffer); |
| goto done; |
| } |
| buffer = gst_buffer_make_writable (buffer); |
| gst_buffer_resize (buffer, diff_bytes, size - diff_bytes); |
| |
| GST_BUFFER_TIMESTAMP (buffer) += diff; |
| /* care even less about duration after this */ |
| } else { |
| /* drain stuff prior to resync */ |
| gst_audio_encoder_drain (enc); |
| } |
| } |
| if (discont) { |
| /* now re-sync ts */ |
| GstClockTime shift = |
| gst_util_uint64_scale (gst_adapter_available (priv->adapter), |
| GST_SECOND, ctx->info.rate * ctx->info.bpf); |
| |
| if (G_UNLIKELY (shift > GST_BUFFER_TIMESTAMP (buffer))) { |
| /* ERROR */ |
| goto wrong_time; |
| } |
| /* arrange for newly added samples to come out with the ts |
| * of the incoming buffer that adds these */ |
| priv->base_ts = GST_BUFFER_TIMESTAMP (buffer) - shift; |
| priv->samples = 0; |
| gst_audio_encoder_set_base_gp (enc); |
| priv->discont |= discont; |
| } |
| } |
| |
| gst_adapter_push (enc->priv->adapter, buffer); |
| /* new stuff, so we can push subclass again */ |
| enc->priv->drained = FALSE; |
| |
| ret = gst_audio_encoder_push_buffers (enc, FALSE); |
| |
| done: |
| GST_LOG_OBJECT (enc, "chain leaving"); |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return ret; |
| |
| /* ERRORS */ |
| not_negotiated: |
| { |
| GST_ELEMENT_ERROR (enc, CORE, NEGOTIATION, (NULL), |
| ("encoder not initialized")); |
| gst_buffer_unref (buffer); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto done; |
| } |
| wrong_buffer: |
| { |
| GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), |
| ("buffer size %" G_GSIZE_FORMAT " not a multiple of %d", |
| gst_buffer_get_size (buffer), ctx->info.bpf)); |
| gst_buffer_unref (buffer); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| wrong_time: |
| { |
| GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), |
| ("buffer going too far back in time")); |
| gst_buffer_unref (buffer); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_audio_encoder_sink_setcaps (GstAudioEncoder * enc, GstCaps * caps) |
| { |
| GstAudioEncoderClass *klass; |
| GstAudioEncoderContext *ctx; |
| GstAudioInfo state; |
| gboolean res = TRUE; |
| guint old_rate; |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| /* subclass must do something here ... */ |
| g_return_val_if_fail (klass->set_format != NULL, FALSE); |
| |
| ctx = &enc->priv->ctx; |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| |
| GST_DEBUG_OBJECT (enc, "caps: %" GST_PTR_FORMAT, caps); |
| |
| if (!gst_caps_is_fixed (caps)) |
| goto refuse_caps; |
| |
| if (enc->priv->ctx.input_caps |
| && gst_caps_is_equal (enc->priv->ctx.input_caps, caps)) |
| goto same_caps; |
| |
| if (!gst_audio_info_from_caps (&state, caps)) |
| goto refuse_caps; |
| |
| if (enc->priv->ctx.input_caps && gst_audio_info_is_equal (&state, &ctx->info)) |
| goto same_caps; |
| |
| /* adjust ts tracking to new sample rate */ |
| old_rate = GST_AUDIO_INFO_RATE (&ctx->info); |
| if (GST_CLOCK_TIME_IS_VALID (enc->priv->base_ts) && old_rate) { |
| enc->priv->base_ts += |
| GST_FRAMES_TO_CLOCK_TIME (enc->priv->samples, old_rate); |
| enc->priv->samples = 0; |
| } |
| |
| /* drain any pending old data stuff */ |
| gst_audio_encoder_drain (enc); |
| |
| /* context defaults */ |
| /* FIXME 2.0: This is quite unexpected behaviour. We should never |
| * just reset *settings* of a subclass inside the base class */ |
| enc->priv->ctx.frame_samples_min = 0; |
| enc->priv->ctx.frame_samples_max = 0; |
| enc->priv->ctx.frame_max = 0; |
| enc->priv->ctx.lookahead = 0; |
| |
| if (klass->set_format) |
| res = klass->set_format (enc, &state); |
| |
| if (res) { |
| GST_OBJECT_LOCK (enc); |
| ctx->info = state; |
| gst_caps_replace (&enc->priv->ctx.input_caps, caps); |
| GST_OBJECT_UNLOCK (enc); |
| } else { |
| /* invalidate state to ensure no casual carrying on */ |
| GST_DEBUG_OBJECT (enc, "subclass did not accept format"); |
| gst_audio_info_init (&state); |
| goto exit; |
| } |
| |
| exit: |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return res; |
| |
| same_caps: |
| { |
| GST_DEBUG_OBJECT (enc, "new audio format identical to configured format"); |
| goto exit; |
| } |
| |
| /* ERRORS */ |
| refuse_caps: |
| { |
| GST_WARNING_OBJECT (enc, "rejected caps %" GST_PTR_FORMAT, caps); |
| goto exit; |
| } |
| } |
| |
| |
| /** |
| * gst_audio_encoder_proxy_getcaps: |
| * @enc: a #GstAudioEncoder |
| * @caps: (allow-none): initial caps |
| * @filter: (allow-none): filter caps |
| * |
| * Returns caps that express @caps (or sink template caps if @caps == NULL) |
| * restricted to channel/rate combinations supported by downstream elements |
| * (e.g. muxers). |
| * |
| * Returns: (transfer full): a #GstCaps owned by caller |
| */ |
| GstCaps * |
| gst_audio_encoder_proxy_getcaps (GstAudioEncoder * enc, GstCaps * caps, |
| GstCaps * filter) |
| { |
| return __gst_audio_element_proxy_getcaps (GST_ELEMENT_CAST (enc), |
| GST_AUDIO_ENCODER_SINK_PAD (enc), GST_AUDIO_ENCODER_SRC_PAD (enc), |
| caps, filter); |
| } |
| |
| static GstCaps * |
| gst_audio_encoder_getcaps_default (GstAudioEncoder * enc, GstCaps * filter) |
| { |
| GstCaps *caps; |
| |
| caps = gst_audio_encoder_proxy_getcaps (enc, NULL, filter); |
| GST_LOG_OBJECT (enc, "returning caps %" GST_PTR_FORMAT, caps); |
| |
| return caps; |
| } |
| |
| static GList * |
| _flush_events (GstPad * pad, GList * events) |
| { |
| GList *tmp; |
| |
| for (tmp = events; tmp; tmp = tmp->next) { |
| if (GST_EVENT_TYPE (tmp->data) != GST_EVENT_EOS && |
| GST_EVENT_TYPE (tmp->data) != GST_EVENT_SEGMENT && |
| GST_EVENT_IS_STICKY (tmp->data)) { |
| gst_pad_store_sticky_event (pad, GST_EVENT_CAST (tmp->data)); |
| } |
| gst_event_unref (tmp->data); |
| } |
| g_list_free (events); |
| |
| return NULL; |
| } |
| |
| static gboolean |
| gst_audio_encoder_sink_event_default (GstAudioEncoder * enc, GstEvent * event) |
| { |
| GstAudioEncoderClass *klass; |
| gboolean res; |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEGMENT: |
| { |
| GstSegment seg; |
| |
| gst_event_copy_segment (event, &seg); |
| |
| if (seg.format == GST_FORMAT_TIME) { |
| GST_DEBUG_OBJECT (enc, "received TIME SEGMENT %" GST_SEGMENT_FORMAT, |
| &seg); |
| } else { |
| GST_DEBUG_OBJECT (enc, "received SEGMENT %" GST_SEGMENT_FORMAT, &seg); |
| GST_DEBUG_OBJECT (enc, "unsupported format; ignoring"); |
| res = TRUE; |
| break; |
| } |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| /* finish current segment */ |
| gst_audio_encoder_drain (enc); |
| /* reset partially for new segment */ |
| gst_audio_encoder_reset (enc, FALSE); |
| /* and follow along with segment */ |
| enc->input_segment = seg; |
| |
| enc->priv->pending_events = |
| g_list_append (enc->priv->pending_events, event); |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| res = TRUE; |
| break; |
| } |
| |
| case GST_EVENT_FLUSH_START: |
| res = gst_audio_encoder_push_event (enc, event); |
| break; |
| |
| case GST_EVENT_FLUSH_STOP: |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| /* discard any pending stuff */ |
| /* TODO route through drain ?? */ |
| if (!enc->priv->drained && klass->flush) |
| klass->flush (enc); |
| /* and get (re)set for the sequel */ |
| gst_audio_encoder_reset (enc, FALSE); |
| |
| enc->priv->pending_events = _flush_events (enc->srcpad, |
| enc->priv->pending_events); |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| res = gst_audio_encoder_push_event (enc, event); |
| break; |
| |
| case GST_EVENT_EOS: |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| gst_audio_encoder_drain (enc); |
| |
| /* check for pending events and tags */ |
| gst_audio_encoder_push_pending_events (enc); |
| gst_audio_encoder_check_and_push_pending_tags (enc); |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| /* forward immediately because no buffer or serialized event |
| * will come after EOS and nothing could trigger another |
| * _finish_frame() call. */ |
| res = gst_audio_encoder_push_event (enc, event); |
| break; |
| |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| enc->priv->do_caps = TRUE; |
| res = TRUE; |
| gst_event_unref (event); |
| break; |
| } |
| |
| case GST_EVENT_STREAM_START: |
| { |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| /* Flush upstream tags after a STREAM_START */ |
| GST_DEBUG_OBJECT (enc, "received STREAM_START. Clearing taglist"); |
| if (enc->priv->upstream_tags) { |
| gst_tag_list_unref (enc->priv->upstream_tags); |
| enc->priv->upstream_tags = NULL; |
| enc->priv->tags_changed = TRUE; |
| } |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| res = gst_audio_encoder_push_event (enc, event); |
| break; |
| } |
| |
| case GST_EVENT_TAG: |
| { |
| GstTagList *tags; |
| |
| gst_event_parse_tag (event, &tags); |
| |
| if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_STREAM) { |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| if (enc->priv->upstream_tags != tags) { |
| tags = gst_tag_list_copy (tags); |
| |
| /* FIXME: make generic based on GST_TAG_FLAG_ENCODED */ |
| gst_tag_list_remove_tag (tags, GST_TAG_CODEC); |
| gst_tag_list_remove_tag (tags, GST_TAG_AUDIO_CODEC); |
| gst_tag_list_remove_tag (tags, GST_TAG_VIDEO_CODEC); |
| gst_tag_list_remove_tag (tags, GST_TAG_SUBTITLE_CODEC); |
| gst_tag_list_remove_tag (tags, GST_TAG_CONTAINER_FORMAT); |
| gst_tag_list_remove_tag (tags, GST_TAG_BITRATE); |
| gst_tag_list_remove_tag (tags, GST_TAG_NOMINAL_BITRATE); |
| gst_tag_list_remove_tag (tags, GST_TAG_MAXIMUM_BITRATE); |
| gst_tag_list_remove_tag (tags, GST_TAG_MINIMUM_BITRATE); |
| gst_tag_list_remove_tag (tags, GST_TAG_ENCODER); |
| gst_tag_list_remove_tag (tags, GST_TAG_ENCODER_VERSION); |
| |
| if (enc->priv->upstream_tags) |
| gst_tag_list_unref (enc->priv->upstream_tags); |
| enc->priv->upstream_tags = tags; |
| GST_INFO_OBJECT (enc, "upstream stream tags: %" GST_PTR_FORMAT, tags); |
| } |
| gst_event_unref (event); |
| event = gst_audio_encoder_create_merged_tags_event (enc); |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| /* No tags, go out of here instead of fall through */ |
| if (!event) { |
| res = TRUE; |
| break; |
| } |
| } |
| /* fall through */ |
| } |
| default: |
| /* Forward non-serialized events immediately. */ |
| if (!GST_EVENT_IS_SERIALIZED (event)) { |
| res = |
| gst_pad_event_default (enc->sinkpad, GST_OBJECT_CAST (enc), event); |
| } else { |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| enc->priv->pending_events = |
| g_list_append (enc->priv->pending_events, event); |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| res = TRUE; |
| } |
| break; |
| } |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_encoder_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstAudioEncoder *enc; |
| GstAudioEncoderClass *klass; |
| gboolean ret; |
| |
| enc = GST_AUDIO_ENCODER (parent); |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| GST_DEBUG_OBJECT (enc, "received event %d, %s", GST_EVENT_TYPE (event), |
| GST_EVENT_TYPE_NAME (event)); |
| |
| if (klass->sink_event) |
| ret = klass->sink_event (enc, event); |
| else { |
| gst_event_unref (event); |
| ret = FALSE; |
| } |
| |
| GST_DEBUG_OBJECT (enc, "event result %d", ret); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_audio_encoder_sink_query_default (GstAudioEncoder * enc, GstQuery * query) |
| { |
| GstPad *pad = GST_AUDIO_ENCODER_SINK_PAD (enc); |
| gboolean res = FALSE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_FORMATS: |
| { |
| gst_query_set_formats (query, 3, |
| GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT); |
| res = TRUE; |
| break; |
| } |
| 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); |
| GST_OBJECT_LOCK (enc); |
| res = gst_audio_info_convert (&enc->priv->ctx.info, |
| src_fmt, src_val, dest_fmt, &dest_val); |
| GST_OBJECT_UNLOCK (enc); |
| if (!res) |
| goto error; |
| gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); |
| res = TRUE; |
| break; |
| } |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter, *caps; |
| GstAudioEncoderClass *klass; |
| |
| gst_query_parse_caps (query, &filter); |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| if (klass->getcaps) { |
| caps = klass->getcaps (enc, filter); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| res = TRUE; |
| } |
| break; |
| } |
| case GST_QUERY_ALLOCATION: |
| { |
| GstAudioEncoderClass *klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| if (klass->propose_allocation) |
| res = klass->propose_allocation (enc, query); |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, GST_OBJECT (enc), query); |
| break; |
| } |
| |
| error: |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_encoder_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| GstAudioEncoder *encoder; |
| GstAudioEncoderClass *encoder_class; |
| gboolean ret = FALSE; |
| |
| encoder = GST_AUDIO_ENCODER (parent); |
| encoder_class = GST_AUDIO_ENCODER_GET_CLASS (encoder); |
| |
| GST_DEBUG_OBJECT (encoder, "received query %d, %s", GST_QUERY_TYPE (query), |
| GST_QUERY_TYPE_NAME (query)); |
| |
| if (encoder_class->sink_query) |
| ret = encoder_class->sink_query (encoder, query); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_audio_encoder_src_event_default (GstAudioEncoder * enc, GstEvent * event) |
| { |
| gboolean res; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| default: |
| res = gst_pad_event_default (enc->srcpad, GST_OBJECT_CAST (enc), event); |
| break; |
| } |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_encoder_src_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstAudioEncoder *enc; |
| GstAudioEncoderClass *klass; |
| gboolean ret; |
| |
| enc = GST_AUDIO_ENCODER (parent); |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| GST_DEBUG_OBJECT (enc, "received event %d, %s", GST_EVENT_TYPE (event), |
| GST_EVENT_TYPE_NAME (event)); |
| |
| if (klass->src_event) |
| ret = klass->src_event (enc, event); |
| else { |
| gst_event_unref (event); |
| ret = FALSE; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_audio_encoder_decide_allocation_default (GstAudioEncoder * enc, |
| GstQuery * query) |
| { |
| GstAllocator *allocator = NULL; |
| GstAllocationParams params; |
| gboolean update_allocator; |
| |
| /* we got configuration from our peer or the decide_allocation method, |
| * parse them */ |
| if (gst_query_get_n_allocation_params (query) > 0) { |
| /* try the allocator */ |
| gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); |
| update_allocator = TRUE; |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| update_allocator = FALSE; |
| } |
| |
| if (update_allocator) |
| gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); |
| else |
| gst_query_add_allocation_param (query, allocator, ¶ms); |
| if (allocator) |
| gst_object_unref (allocator); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_audio_encoder_propose_allocation_default (GstAudioEncoder * enc, |
| GstQuery * query) |
| { |
| return TRUE; |
| } |
| |
| /* FIXME ? are any of these queries (other than latency) an encoder's business |
| * also, the conversion stuff might seem to make sense, but seems to not mind |
| * segment stuff etc at all |
| * Supposedly that's backward compatibility ... */ |
| static gboolean |
| gst_audio_encoder_src_query_default (GstAudioEncoder * enc, GstQuery * query) |
| { |
| GstPad *pad = GST_AUDIO_ENCODER_SRC_PAD (enc); |
| gboolean res = FALSE; |
| |
| GST_LOG_OBJECT (enc, "handling query: %" GST_PTR_FORMAT, query); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| { |
| GstFormat fmt, req_fmt; |
| gint64 pos, val; |
| |
| if ((res = gst_pad_peer_query (enc->sinkpad, query))) { |
| GST_LOG_OBJECT (enc, "returning peer response"); |
| break; |
| } |
| |
| gst_query_parse_position (query, &req_fmt, NULL); |
| |
| /* Refuse BYTES format queries. If it made sense to |
| * * answer them, upstream would have already */ |
| if (req_fmt == GST_FORMAT_BYTES) { |
| GST_LOG_OBJECT (enc, "Ignoring BYTES position query"); |
| break; |
| } |
| |
| fmt = GST_FORMAT_TIME; |
| if (!(res = gst_pad_peer_query_position (enc->sinkpad, fmt, &pos))) |
| break; |
| |
| if ((res = |
| gst_pad_peer_query_convert (enc->sinkpad, fmt, pos, req_fmt, |
| &val))) { |
| gst_query_set_position (query, req_fmt, val); |
| } |
| break; |
| } |
| case GST_QUERY_DURATION: |
| { |
| GstFormat fmt, req_fmt; |
| gint64 dur, val; |
| |
| if ((res = gst_pad_peer_query (enc->sinkpad, query))) { |
| GST_LOG_OBJECT (enc, "returning peer response"); |
| break; |
| } |
| |
| gst_query_parse_duration (query, &req_fmt, NULL); |
| |
| /* Refuse BYTES format queries. If it made sense to |
| * * answer them, upstream would have already */ |
| if (req_fmt == GST_FORMAT_BYTES) { |
| GST_LOG_OBJECT (enc, "Ignoring BYTES position query"); |
| break; |
| } |
| |
| fmt = GST_FORMAT_TIME; |
| if (!(res = gst_pad_peer_query_duration (enc->sinkpad, fmt, &dur))) |
| break; |
| |
| if ((res = |
| gst_pad_peer_query_convert (enc->sinkpad, fmt, dur, req_fmt, |
| &val))) { |
| gst_query_set_duration (query, req_fmt, val); |
| } |
| break; |
| } |
| case GST_QUERY_FORMATS: |
| { |
| gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); |
| res = TRUE; |
| break; |
| } |
| 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); |
| GST_OBJECT_LOCK (enc); |
| res = __gst_audio_encoded_audio_convert (&enc->priv->ctx.info, |
| enc->priv->bytes_out, enc->priv->samples_in, src_fmt, src_val, |
| &dest_fmt, &dest_val); |
| GST_OBJECT_UNLOCK (enc); |
| if (!res) |
| break; |
| gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); |
| break; |
| } |
| case GST_QUERY_LATENCY: |
| { |
| if ((res = gst_pad_peer_query (enc->sinkpad, query))) { |
| gboolean live; |
| GstClockTime min_latency, max_latency; |
| |
| gst_query_parse_latency (query, &live, &min_latency, &max_latency); |
| GST_DEBUG_OBJECT (enc, "Peer latency: live %d, min %" |
| GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live, |
| GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); |
| |
| GST_OBJECT_LOCK (enc); |
| /* add our latency */ |
| min_latency += enc->priv->ctx.min_latency; |
| if (max_latency == -1 || enc->priv->ctx.max_latency == -1) |
| max_latency = -1; |
| else |
| max_latency += enc->priv->ctx.max_latency; |
| GST_OBJECT_UNLOCK (enc); |
| |
| gst_query_set_latency (query, live, min_latency, max_latency); |
| } |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, GST_OBJECT (enc), query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_encoder_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstAudioEncoder *encoder; |
| GstAudioEncoderClass *encoder_class; |
| gboolean ret = FALSE; |
| |
| encoder = GST_AUDIO_ENCODER (parent); |
| encoder_class = GST_AUDIO_ENCODER_GET_CLASS (encoder); |
| |
| GST_DEBUG_OBJECT (encoder, "received query %d, %s", GST_QUERY_TYPE (query), |
| GST_QUERY_TYPE_NAME (query)); |
| |
| if (encoder_class->src_query) |
| ret = encoder_class->src_query (encoder, query); |
| |
| return ret; |
| } |
| |
| |
| static void |
| gst_audio_encoder_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstAudioEncoder *enc; |
| |
| enc = GST_AUDIO_ENCODER (object); |
| |
| switch (prop_id) { |
| case PROP_PERFECT_TS: |
| if (enc->priv->granule && !g_value_get_boolean (value)) |
| GST_WARNING_OBJECT (enc, "perfect-timestamp can not be set FALSE " |
| "while granule handling is enabled"); |
| else |
| enc->priv->perfect_ts = g_value_get_boolean (value); |
| break; |
| case PROP_HARD_RESYNC: |
| enc->priv->hard_resync = g_value_get_boolean (value); |
| break; |
| case PROP_TOLERANCE: |
| enc->priv->tolerance = g_value_get_int64 (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_audio_encoder_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstAudioEncoder *enc; |
| |
| enc = GST_AUDIO_ENCODER (object); |
| |
| switch (prop_id) { |
| case PROP_PERFECT_TS: |
| g_value_set_boolean (value, enc->priv->perfect_ts); |
| break; |
| case PROP_GRANULE: |
| g_value_set_boolean (value, enc->priv->granule); |
| break; |
| case PROP_HARD_RESYNC: |
| g_value_set_boolean (value, enc->priv->hard_resync); |
| break; |
| case PROP_TOLERANCE: |
| g_value_set_int64 (value, enc->priv->tolerance); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_audio_encoder_activate (GstAudioEncoder * enc, gboolean active) |
| { |
| GstAudioEncoderClass *klass; |
| gboolean result = TRUE; |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| g_return_val_if_fail (!enc->priv->granule || enc->priv->perfect_ts, FALSE); |
| |
| GST_DEBUG_OBJECT (enc, "activate %d", active); |
| |
| if (active) { |
| /* arrange clean state */ |
| gst_audio_encoder_reset (enc, TRUE); |
| |
| if (!enc->priv->active && klass->start) |
| result = klass->start (enc); |
| } else { |
| /* We must make sure streaming has finished before resetting things |
| * and calling the ::stop vfunc */ |
| GST_PAD_STREAM_LOCK (enc->sinkpad); |
| GST_PAD_STREAM_UNLOCK (enc->sinkpad); |
| |
| if (enc->priv->active && klass->stop) |
| result = klass->stop (enc); |
| |
| /* clean up */ |
| gst_audio_encoder_reset (enc, TRUE); |
| } |
| GST_DEBUG_OBJECT (enc, "activate return: %d", result); |
| return result; |
| } |
| |
| |
| static gboolean |
| gst_audio_encoder_sink_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean result = TRUE; |
| GstAudioEncoder *enc; |
| |
| enc = GST_AUDIO_ENCODER (parent); |
| |
| GST_DEBUG_OBJECT (enc, "sink activate push %d", active); |
| |
| result = gst_audio_encoder_activate (enc, active); |
| |
| if (result) |
| enc->priv->active = active; |
| |
| GST_DEBUG_OBJECT (enc, "sink activate push return: %d", result); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_get_audio_info: |
| * @enc: a #GstAudioEncoder |
| * |
| * Returns: a #GstAudioInfo describing the input audio format |
| */ |
| GstAudioInfo * |
| gst_audio_encoder_get_audio_info (GstAudioEncoder * enc) |
| { |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), NULL); |
| |
| return &enc->priv->ctx.info; |
| } |
| |
| /** |
| * gst_audio_encoder_set_frame_samples_min: |
| * @enc: a #GstAudioEncoder |
| * @num: number of samples per frame |
| * |
| * Sets number of samples (per channel) subclass needs to be handed, |
| * at least or will be handed all available if 0. |
| * |
| * If an exact number of samples is required, gst_audio_encoder_set_frame_samples_max() |
| * must be called with the same number. |
| * |
| * Note: This value will be reset to 0 every time before |
| * GstAudioEncoder::set_format() is called. |
| */ |
| void |
| gst_audio_encoder_set_frame_samples_min (GstAudioEncoder * enc, gint num) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| enc->priv->ctx.frame_samples_min = num; |
| GST_LOG_OBJECT (enc, "set to %d", num); |
| } |
| |
| /** |
| * gst_audio_encoder_get_frame_samples_min: |
| * @enc: a #GstAudioEncoder |
| * |
| * Returns: currently minimum requested samples per frame |
| */ |
| gint |
| gst_audio_encoder_get_frame_samples_min (GstAudioEncoder * enc) |
| { |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| return enc->priv->ctx.frame_samples_min; |
| } |
| |
| /** |
| * gst_audio_encoder_set_frame_samples_max: |
| * @enc: a #GstAudioEncoder |
| * @num: number of samples per frame |
| * |
| * Sets number of samples (per channel) subclass needs to be handed, |
| * at most or will be handed all available if 0. |
| * |
| * If an exact number of samples is required, gst_audio_encoder_set_frame_samples_min() |
| * must be called with the same number. |
| * |
| * Note: This value will be reset to 0 every time before |
| * GstAudioEncoder::set_format() is called. |
| */ |
| void |
| gst_audio_encoder_set_frame_samples_max (GstAudioEncoder * enc, gint num) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| enc->priv->ctx.frame_samples_max = num; |
| GST_LOG_OBJECT (enc, "set to %d", num); |
| } |
| |
| /** |
| * gst_audio_encoder_get_frame_samples_max: |
| * @enc: a #GstAudioEncoder |
| * |
| * Returns: currently maximum requested samples per frame |
| */ |
| gint |
| gst_audio_encoder_get_frame_samples_max (GstAudioEncoder * enc) |
| { |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| return enc->priv->ctx.frame_samples_max; |
| } |
| |
| /** |
| * gst_audio_encoder_set_frame_max: |
| * @enc: a #GstAudioEncoder |
| * @num: number of frames |
| * |
| * Sets max number of frames accepted at once (assumed minimally 1). |
| * Requires @frame_samples_min and @frame_samples_max to be the equal. |
| * |
| * Note: This value will be reset to 0 every time before |
| * GstAudioEncoder::set_format() is called. |
| */ |
| void |
| gst_audio_encoder_set_frame_max (GstAudioEncoder * enc, gint num) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| enc->priv->ctx.frame_max = num; |
| GST_LOG_OBJECT (enc, "set to %d", num); |
| } |
| |
| /** |
| * gst_audio_encoder_get_frame_max: |
| * @enc: a #GstAudioEncoder |
| * |
| * Returns: currently configured maximum handled frames |
| */ |
| gint |
| gst_audio_encoder_get_frame_max (GstAudioEncoder * enc) |
| { |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| return enc->priv->ctx.frame_max; |
| } |
| |
| /** |
| * gst_audio_encoder_set_lookahead: |
| * @enc: a #GstAudioEncoder |
| * @num: lookahead |
| * |
| * Sets encoder lookahead (in units of input rate samples) |
| * |
| * Note: This value will be reset to 0 every time before |
| * GstAudioEncoder::set_format() is called. |
| */ |
| void |
| gst_audio_encoder_set_lookahead (GstAudioEncoder * enc, gint num) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| enc->priv->ctx.lookahead = num; |
| GST_LOG_OBJECT (enc, "set to %d", num); |
| } |
| |
| /** |
| * gst_audio_encoder_get_lookahead: |
| * @enc: a #GstAudioEncoder |
| * |
| * Returns: currently configured encoder lookahead |
| */ |
| gint |
| gst_audio_encoder_get_lookahead (GstAudioEncoder * enc) |
| { |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| return enc->priv->ctx.lookahead; |
| } |
| |
| /** |
| * gst_audio_encoder_set_latency: |
| * @enc: a #GstAudioEncoder |
| * @min: minimum latency |
| * @max: maximum latency |
| * |
| * Sets encoder latency. |
| */ |
| void |
| gst_audio_encoder_set_latency (GstAudioEncoder * enc, |
| GstClockTime min, GstClockTime max) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| g_return_if_fail (GST_CLOCK_TIME_IS_VALID (min)); |
| g_return_if_fail (min <= max); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->ctx.min_latency = min; |
| enc->priv->ctx.max_latency = max; |
| GST_OBJECT_UNLOCK (enc); |
| |
| GST_LOG_OBJECT (enc, "set to %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT, |
| GST_TIME_ARGS (min), GST_TIME_ARGS (max)); |
| |
| /* post latency message on the bus */ |
| gst_element_post_message (GST_ELEMENT (enc), |
| gst_message_new_latency (GST_OBJECT (enc))); |
| } |
| |
| /** |
| * gst_audio_encoder_get_latency: |
| * @enc: a #GstAudioEncoder |
| * @min: (out) (allow-none): a pointer to storage to hold minimum latency |
| * @max: (out) (allow-none): a pointer to storage to hold maximum latency |
| * |
| * Sets the variables pointed to by @min and @max to the currently configured |
| * latency. |
| */ |
| void |
| gst_audio_encoder_get_latency (GstAudioEncoder * enc, |
| GstClockTime * min, GstClockTime * max) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_OBJECT_LOCK (enc); |
| if (min) |
| *min = enc->priv->ctx.min_latency; |
| if (max) |
| *max = enc->priv->ctx.max_latency; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_set_headers: |
| * @enc: a #GstAudioEncoder |
| * @headers: (transfer full) (element-type Gst.Buffer): a list of |
| * #GstBuffer containing the codec header |
| * |
| * Set the codec headers to be sent downstream whenever requested. |
| */ |
| void |
| gst_audio_encoder_set_headers (GstAudioEncoder * enc, GList * headers) |
| { |
| GST_DEBUG_OBJECT (enc, "new headers %p", headers); |
| |
| if (enc->priv->ctx.headers) { |
| g_list_foreach (enc->priv->ctx.headers, (GFunc) gst_buffer_unref, NULL); |
| g_list_free (enc->priv->ctx.headers); |
| } |
| enc->priv->ctx.headers = headers; |
| enc->priv->ctx.new_headers = TRUE; |
| } |
| |
| /** |
| * gst_audio_encoder_set_allocation_caps: |
| * @enc: a #GstAudioEncoder |
| * @allocation_caps: (allow-none): a #GstCaps or %NULL |
| * |
| * Sets a caps in allocation query which are different from the set |
| * pad's caps. Use this function before calling |
| * gst_audio_encoder_negotiate(). Setting to %NULL the allocation |
| * query will use the caps from the pad. |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_audio_encoder_set_allocation_caps (GstAudioEncoder * enc, |
| GstCaps * allocation_caps) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| gst_caps_replace (&enc->priv->ctx.allocation_caps, allocation_caps); |
| } |
| |
| /** |
| * gst_audio_encoder_set_mark_granule: |
| * @enc: a #GstAudioEncoder |
| * @enabled: new state |
| * |
| * Enable or disable encoder granule handling. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_mark_granule (GstAudioEncoder * enc, gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_LOG_OBJECT (enc, "enabled: %d", enabled); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->granule = enabled; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_get_mark_granule: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries if the encoder will handle granule marking. |
| * |
| * Returns: TRUE if granule marking is enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_encoder_get_mark_granule (GstAudioEncoder * enc) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), FALSE); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->granule; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_set_perfect_timestamp: |
| * @enc: a #GstAudioEncoder |
| * @enabled: new state |
| * |
| * Enable or disable encoder perfect output timestamp preference. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_perfect_timestamp (GstAudioEncoder * enc, |
| gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_LOG_OBJECT (enc, "enabled: %d", enabled); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->perfect_ts = enabled; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_get_perfect_timestamp: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries encoder perfect timestamp behaviour. |
| * |
| * Returns: TRUE if perfect timestamp setting enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_encoder_get_perfect_timestamp (GstAudioEncoder * enc) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), FALSE); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->perfect_ts; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_set_hard_sync: |
| * @enc: a #GstAudioEncoder |
| * @enabled: new state |
| * |
| * Sets encoder hard resync handling. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_hard_resync (GstAudioEncoder * enc, gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_LOG_OBJECT (enc, "enabled: %d", enabled); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->hard_resync = enabled; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_get_hard_sync: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries encoder's hard resync setting. |
| * |
| * Returns: TRUE if hard resync is enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_encoder_get_hard_resync (GstAudioEncoder * enc) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), FALSE); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->hard_resync; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_set_tolerance: |
| * @enc: a #GstAudioEncoder |
| * @tolerance: new tolerance |
| * |
| * Configures encoder audio jitter tolerance threshold. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_tolerance (GstAudioEncoder * enc, GstClockTime tolerance) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->tolerance = tolerance; |
| GST_OBJECT_UNLOCK (enc); |
| |
| GST_LOG_OBJECT (enc, "set to %" GST_TIME_FORMAT, GST_TIME_ARGS (tolerance)); |
| } |
| |
| /** |
| * gst_audio_encoder_get_tolerance: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries current audio jitter tolerance threshold. |
| * |
| * Returns: encoder audio jitter tolerance threshold. |
| * |
| * MT safe. |
| */ |
| GstClockTime |
| gst_audio_encoder_get_tolerance (GstAudioEncoder * enc) |
| { |
| GstClockTime result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->tolerance; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_set_hard_min: |
| * @enc: a #GstAudioEncoder |
| * @enabled: new state |
| * |
| * Configures encoder hard minimum handling. If enabled, subclass |
| * will never be handed less samples than it configured, which otherwise |
| * might occur near end-of-data handling. Instead, the leftover samples |
| * will simply be discarded. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_hard_min (GstAudioEncoder * enc, gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->hard_min = enabled; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_get_hard_min: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries encoder hard minimum handling. |
| * |
| * Returns: TRUE if hard minimum handling is enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_encoder_get_hard_min (GstAudioEncoder * enc) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->hard_min; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_set_drainable: |
| * @enc: a #GstAudioEncoder |
| * @enabled: new state |
| * |
| * Configures encoder drain handling. If drainable, subclass might |
| * be handed a NULL buffer to have it return any leftover encoded data. |
| * Otherwise, it is not considered so capable and will only ever be passed |
| * real data. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_set_drainable (GstAudioEncoder * enc, gboolean enabled) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| GST_OBJECT_LOCK (enc); |
| enc->priv->drainable = enabled; |
| GST_OBJECT_UNLOCK (enc); |
| } |
| |
| /** |
| * gst_audio_encoder_get_drainable: |
| * @enc: a #GstAudioEncoder |
| * |
| * Queries encoder drain handling. |
| * |
| * Returns: TRUE if drainable handling is enabled. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_encoder_get_drainable (GstAudioEncoder * enc) |
| { |
| gboolean result; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), 0); |
| |
| GST_OBJECT_LOCK (enc); |
| result = enc->priv->drainable; |
| GST_OBJECT_UNLOCK (enc); |
| |
| return result; |
| } |
| |
| /** |
| * gst_audio_encoder_merge_tags: |
| * @enc: a #GstAudioEncoder |
| * @tags: (allow-none): a #GstTagList to merge, or NULL to unset |
| * previously-set tags |
| * @mode: the #GstTagMergeMode to use, usually #GST_TAG_MERGE_REPLACE |
| * |
| * Sets the audio encoder tags and how they should be merged with any |
| * upstream stream tags. This will override any tags previously-set |
| * with gst_audio_encoder_merge_tags(). |
| * |
| * Note that this is provided for convenience, and the subclass is |
| * not required to use this and can still do tag handling on its own. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_encoder_merge_tags (GstAudioEncoder * enc, |
| const GstTagList * tags, GstTagMergeMode mode) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| g_return_if_fail (tags == NULL || GST_IS_TAG_LIST (tags)); |
| g_return_if_fail (tags == NULL || mode != GST_TAG_MERGE_UNDEFINED); |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| if (enc->priv->tags != tags) { |
| if (enc->priv->tags) { |
| gst_tag_list_unref (enc->priv->tags); |
| enc->priv->tags = NULL; |
| enc->priv->tags_merge_mode = GST_TAG_MERGE_APPEND; |
| } |
| if (tags) { |
| enc->priv->tags = gst_tag_list_ref ((GstTagList *) tags); |
| enc->priv->tags_merge_mode = mode; |
| } |
| |
| GST_DEBUG_OBJECT (enc, "setting encoder tags to %" GST_PTR_FORMAT, tags); |
| enc->priv->tags_changed = TRUE; |
| } |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| } |
| |
| static gboolean |
| gst_audio_encoder_negotiate_default (GstAudioEncoder * enc) |
| { |
| GstAudioEncoderClass *klass; |
| gboolean res = TRUE; |
| GstQuery *query = NULL; |
| GstAllocator *allocator; |
| GstAllocationParams params; |
| GstCaps *caps, *prevcaps; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), FALSE); |
| g_return_val_if_fail (GST_IS_CAPS (enc->priv->ctx.caps), FALSE); |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| caps = enc->priv->ctx.caps; |
| if (enc->priv->ctx.allocation_caps == NULL) |
| enc->priv->ctx.allocation_caps = gst_caps_ref (caps); |
| |
| GST_DEBUG_OBJECT (enc, "Setting srcpad caps %" GST_PTR_FORMAT, caps); |
| |
| if (enc->priv->pending_events) { |
| GList **pending_events, *l; |
| |
| pending_events = &enc->priv->pending_events; |
| |
| GST_DEBUG_OBJECT (enc, "Pushing pending events"); |
| for (l = *pending_events; l;) { |
| GstEvent *event = GST_EVENT (l->data); |
| GList *tmp; |
| |
| if (GST_EVENT_TYPE (event) < GST_EVENT_CAPS) { |
| gst_audio_encoder_push_event (enc, l->data); |
| tmp = l; |
| l = l->next; |
| *pending_events = g_list_delete_link (*pending_events, tmp); |
| } else { |
| l = l->next; |
| } |
| } |
| } |
| |
| prevcaps = gst_pad_get_current_caps (enc->srcpad); |
| if (!prevcaps || !gst_caps_is_equal (prevcaps, caps)) |
| res = gst_pad_set_caps (enc->srcpad, caps); |
| if (prevcaps) |
| gst_caps_unref (prevcaps); |
| |
| if (!res) |
| goto done; |
| enc->priv->ctx.output_caps_changed = FALSE; |
| |
| query = gst_query_new_allocation (enc->priv->ctx.allocation_caps, TRUE); |
| if (!gst_pad_peer_query (enc->srcpad, query)) { |
| GST_DEBUG_OBJECT (enc, "didn't get downstream ALLOCATION hints"); |
| } |
| |
| g_assert (klass->decide_allocation != NULL); |
| res = klass->decide_allocation (enc, query); |
| |
| GST_DEBUG_OBJECT (enc, "ALLOCATION (%d) params: %" GST_PTR_FORMAT, res, |
| query); |
| |
| if (!res) |
| goto no_decide_allocation; |
| |
| /* we got configuration from our peer or the decide_allocation method, |
| * parse them */ |
| if (gst_query_get_n_allocation_params (query) > 0) { |
| gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| } |
| |
| if (enc->priv->ctx.allocator) |
| gst_object_unref (enc->priv->ctx.allocator); |
| enc->priv->ctx.allocator = allocator; |
| enc->priv->ctx.params = params; |
| |
| done: |
| if (query) |
| gst_query_unref (query); |
| |
| return res; |
| |
| /* ERRORS */ |
| no_decide_allocation: |
| { |
| GST_WARNING_OBJECT (enc, "Subclass failed to decide allocation"); |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_audio_encoder_negotiate_unlocked (GstAudioEncoder * enc) |
| { |
| GstAudioEncoderClass *klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| gboolean ret = TRUE; |
| |
| if (G_LIKELY (klass->negotiate)) |
| ret = klass->negotiate (enc); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_audio_encoder_negotiate: |
| * @enc: a #GstAudioEncoder |
| * |
| * Negotiate with downstream elements to currently configured #GstCaps. |
| * Unmark GST_PAD_FLAG_NEED_RECONFIGURE in any case. But mark it again if |
| * negotiate fails. |
| * |
| * Returns: %TRUE if the negotiation succeeded, else %FALSE. |
| */ |
| gboolean |
| gst_audio_encoder_negotiate (GstAudioEncoder * enc) |
| { |
| GstAudioEncoderClass *klass; |
| gboolean ret = TRUE; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_ENCODER (enc), FALSE); |
| |
| klass = GST_AUDIO_ENCODER_GET_CLASS (enc); |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| gst_pad_check_reconfigure (enc->srcpad); |
| if (klass->negotiate) { |
| ret = klass->negotiate (enc); |
| if (!ret) |
| gst_pad_mark_reconfigure (enc->srcpad); |
| } |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_audio_encoder_set_output_format: |
| * @enc: a #GstAudioEncoder |
| * @caps: (transfer none): #GstCaps |
| * |
| * Configure output caps on the srcpad of @enc. |
| * |
| * Returns: %TRUE on success. |
| */ |
| gboolean |
| gst_audio_encoder_set_output_format (GstAudioEncoder * enc, GstCaps * caps) |
| { |
| gboolean res = TRUE; |
| GstCaps *templ_caps; |
| |
| GST_DEBUG_OBJECT (enc, "Setting srcpad caps %" GST_PTR_FORMAT, caps); |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| if (!gst_caps_is_fixed (caps)) |
| goto refuse_caps; |
| |
| /* Only allow caps that are a subset of the template caps */ |
| templ_caps = gst_pad_get_pad_template_caps (enc->srcpad); |
| if (!gst_caps_is_subset (caps, templ_caps)) { |
| gst_caps_unref (templ_caps); |
| goto refuse_caps; |
| } |
| gst_caps_unref (templ_caps); |
| |
| gst_caps_replace (&enc->priv->ctx.caps, caps); |
| enc->priv->ctx.output_caps_changed = TRUE; |
| |
| done: |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return res; |
| |
| /* ERRORS */ |
| refuse_caps: |
| { |
| GST_WARNING_OBJECT (enc, "refused caps %" GST_PTR_FORMAT, caps); |
| res = FALSE; |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_encoder_allocate_output_buffer: |
| * @enc: a #GstAudioEncoder |
| * @size: size of the buffer |
| * |
| * Helper function that allocates a buffer to hold an encoded audio frame |
| * for @enc's current output format. |
| * |
| * Returns: (transfer full): allocated buffer |
| */ |
| GstBuffer * |
| gst_audio_encoder_allocate_output_buffer (GstAudioEncoder * enc, gsize size) |
| { |
| GstBuffer *buffer = NULL; |
| gboolean needs_reconfigure = FALSE; |
| |
| g_return_val_if_fail (size > 0, NULL); |
| |
| GST_DEBUG ("alloc src buffer"); |
| |
| GST_AUDIO_ENCODER_STREAM_LOCK (enc); |
| |
| needs_reconfigure = gst_pad_check_reconfigure (enc->srcpad); |
| if (G_UNLIKELY (enc->priv->ctx.output_caps_changed || (enc->priv->ctx.caps |
| && needs_reconfigure))) { |
| if (!gst_audio_encoder_negotiate_unlocked (enc)) { |
| GST_INFO_OBJECT (enc, "Failed to negotiate, fallback allocation"); |
| gst_pad_mark_reconfigure (enc->srcpad); |
| goto fallback; |
| } |
| } |
| |
| buffer = |
| gst_buffer_new_allocate (enc->priv->ctx.allocator, size, |
| &enc->priv->ctx.params); |
| if (!buffer) { |
| GST_INFO_OBJECT (enc, "couldn't allocate output buffer"); |
| goto fallback; |
| } |
| |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return buffer; |
| |
| fallback: |
| buffer = gst_buffer_new_allocate (NULL, size, NULL); |
| GST_AUDIO_ENCODER_STREAM_UNLOCK (enc); |
| |
| return buffer; |
| } |
| |
| /** |
| * gst_audio_encoder_get_allocator: |
| * @enc: a #GstAudioEncoder |
| * @allocator: (out) (allow-none) (transfer full): the #GstAllocator |
| * used |
| * @params: (out) (allow-none) (transfer full): the |
| * #GstAllocatorParams of @allocator |
| * |
| * Lets #GstAudioEncoder sub-classes to know the memory @allocator |
| * used by the base class and its @params. |
| * |
| * Unref the @allocator after use it. |
| */ |
| void |
| gst_audio_encoder_get_allocator (GstAudioEncoder * enc, |
| GstAllocator ** allocator, GstAllocationParams * params) |
| { |
| g_return_if_fail (GST_IS_AUDIO_ENCODER (enc)); |
| |
| if (allocator) |
| *allocator = enc->priv->ctx.allocator ? |
| gst_object_ref (enc->priv->ctx.allocator) : NULL; |
| |
| if (params) |
| *params = enc->priv->ctx.params; |
| } |