/* GStreamer * Copyright (C) 2011 Mark Nauwelaerts . * Copyright (C) 2011 Nokia Corporation. All rights reserved. * Contact: Stefan Kost * * 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 #include #include #include #include GST_DEBUG_CATEGORY_STATIC (gst_audio_encoder_debug); #define GST_CAT_DEFAULT gst_audio_encoder_debug 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 gint private_offset = 0; /* cached quark to avoid contention on the global quark table lock */ #define META_TAG_AUDIO meta_tag_audio_quark static GQuark meta_tag_audio_quark; 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); private_offset = g_type_add_instance_private (audio_encoder_type, sizeof (GstAudioEncoderPrivate)); g_type_add_interface_static (audio_encoder_type, GST_TYPE_PRESET, &preset_interface_info); } return audio_encoder_type; } static inline GstAudioEncoderPrivate * gst_audio_encoder_get_instance_private (GstAudioEncoder * self) { return (G_STRUCT_MEMBER_P (self, private_offset)); } 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"); if (private_offset != 0) g_type_class_adjust_private_offset (klass, &private_offset); 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; meta_tag_audio_quark = g_quark_from_static_string (GST_META_TAG_AUDIO_STR); } 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_instance_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; const gchar *const supported_tags[] = { GST_META_TAG_AUDIO_STR, GST_META_TAG_AUDIO_CHANNELS_STR, NULL, }; tags = gst_meta_api_type_get_tags (info->api); if (!tags) return TRUE; while (*tags) { if (!g_strv_contains (supported_tags, *tags)) return FALSE; tags++; } return TRUE; } 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: (transfer full) (allow-none): 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 #GstAudioEncoderClass.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_PTS (buf) = priv->base_ts + gst_util_uint64_scale (priv->samples - ctx->lookahead, GST_SECOND, ctx->info.rate); GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (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_PTS (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_PTS (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_PTS (buffer)), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); /* input should 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 (enc, "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_PTS (buffer)), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); if (!GST_CLOCK_TIME_IS_VALID (priv->base_ts)) { priv->base_ts = GST_BUFFER_PTS (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_PTS_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_PTS (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_PTS (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_PTS (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_PTS (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; gst_event_unref (event); 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: (transfer none): 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 * #GstAudioEncoderClass.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 * #GstAudioEncoderClass.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 * #GstAudioEncoderClass.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 * #GstAudioEncoderClass.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)); g_return_if_fail (GST_CLOCK_TIME_IS_VALID (tolerance)); 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 * #GstAllocationParams 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; }