mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 17:50:36 +00:00
f790863755
With MSVC, this gives the following warning: warning C4305: 'function': truncation from 'double' to 'gfloat' Apparently, MSVC does not figure out what type to use for constants based on the assignment. This warning is very spammy, so let's try to fix it.
702 lines
19 KiB
C
702 lines
19 KiB
C
/* -*- c-basic-offset: 2 -*-
|
|
* GStreamer
|
|
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
|
*
|
|
* 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:element-speed
|
|
*
|
|
* Plays an audio stream at a different speed (by resampling the audio).
|
|
*
|
|
* Do not use this element. Either use the 'pitch' element, or do a seek with
|
|
* a non-1.0 rate parameter, this will have the same effect as using the speed
|
|
* element (but relies on the decoder/demuxer to handle this correctly, also
|
|
* requires a fairly up-to-date gst-plugins-base, as of February 2007).
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 filesrc location=test.ogg ! decodebin ! audioconvert ! speed speed=1.5 ! audioconvert ! audioresample ! autoaudiosink
|
|
* ]| Plays an .ogg file at 1.5x speed.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <gst/gst.h>
|
|
#include <gst/audio/audio.h>
|
|
|
|
#include "gstspeed.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (speed_debug);
|
|
#define GST_CAT_DEFAULT speed_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_SPEED
|
|
};
|
|
|
|
/* assumption here: sizeof (gfloat) = 4 */
|
|
#define GST_SPEED_AUDIO_CAPS \
|
|
"audio/x-raw, " \
|
|
"format = {" GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (S16) "}, " \
|
|
"rate = (int) [ 1, MAX ], " \
|
|
"channels = (int) [ 1, MAX ]"
|
|
|
|
static GstStaticPadTemplate gst_speed_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_SPEED_AUDIO_CAPS)
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_speed_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_SPEED_AUDIO_CAPS)
|
|
);
|
|
|
|
static void speed_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void speed_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec);
|
|
|
|
static gboolean speed_parse_caps (GstSpeed * filter, const GstCaps * caps);
|
|
|
|
static GstFlowReturn speed_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buf);
|
|
|
|
static GstStateChangeReturn speed_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
static gboolean speed_sink_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
static gboolean speed_src_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
|
|
G_DEFINE_TYPE (GstSpeed, gst_speed, GST_TYPE_ELEMENT);
|
|
|
|
static gboolean
|
|
speed_setcaps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstSpeed *filter;
|
|
gboolean ret;
|
|
|
|
filter = GST_SPEED (gst_pad_get_parent (pad));
|
|
|
|
ret = speed_parse_caps (filter, caps);
|
|
|
|
gst_object_unref (filter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
speed_parse_caps (GstSpeed * filter, const GstCaps * caps)
|
|
{
|
|
g_return_val_if_fail (filter != NULL, FALSE);
|
|
g_return_val_if_fail (caps != NULL, FALSE);
|
|
|
|
if (!gst_audio_info_from_caps (&filter->info, caps))
|
|
return FALSE;
|
|
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
speed_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GstSpeed *filter;
|
|
gboolean ret = FALSE;
|
|
|
|
filter = GST_SPEED (parent);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:{
|
|
gdouble rate;
|
|
GstFormat format;
|
|
GstSeekFlags flags;
|
|
GstSeekType start_type, stop_type;
|
|
gint64 start, stop;
|
|
|
|
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
|
|
&stop_type, &stop);
|
|
gst_event_unref (event);
|
|
|
|
if (format != GST_FORMAT_TIME) {
|
|
GST_DEBUG_OBJECT (filter, "only support seeks in TIME format");
|
|
break;
|
|
}
|
|
|
|
if (start_type != GST_SEEK_TYPE_NONE && start != -1) {
|
|
start *= filter->speed;
|
|
}
|
|
|
|
if (stop_type != GST_SEEK_TYPE_NONE && stop != -1) {
|
|
stop *= filter->speed;
|
|
}
|
|
|
|
event = gst_event_new_seek (rate, format, flags, start_type, start,
|
|
stop_type, stop);
|
|
|
|
GST_LOG ("sending seek event: %" GST_PTR_FORMAT,
|
|
gst_event_get_structure (event));
|
|
|
|
ret = gst_pad_send_event (GST_PAD_PEER (filter->sinkpad), event);
|
|
break;
|
|
}
|
|
default:
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_speed_convert (GstSpeed * filter, GstFormat src_format, gint64 src_value,
|
|
GstFormat * dest_format, gint64 * dest_value)
|
|
{
|
|
gboolean ret = TRUE;
|
|
guint scale = 1;
|
|
|
|
if (src_format == *dest_format) {
|
|
*dest_value = src_value;
|
|
return TRUE;
|
|
}
|
|
|
|
switch (src_format) {
|
|
case GST_FORMAT_BYTES:
|
|
switch (*dest_format) {
|
|
case GST_FORMAT_DEFAULT:
|
|
if (GST_AUDIO_INFO_BPF (&filter->info) == 0) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
*dest_value = src_value / GST_AUDIO_INFO_BPF (&filter->info);
|
|
break;
|
|
case GST_FORMAT_TIME:
|
|
{
|
|
gint byterate =
|
|
GST_AUDIO_INFO_BPF (&filter->info) *
|
|
GST_AUDIO_INFO_RATE (&filter->info);
|
|
|
|
if (byterate == 0) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
*dest_value = src_value * GST_SECOND / byterate;
|
|
break;
|
|
}
|
|
default:
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
case GST_FORMAT_DEFAULT:
|
|
switch (*dest_format) {
|
|
case GST_FORMAT_BYTES:
|
|
*dest_value = src_value * GST_AUDIO_INFO_BPF (&filter->info);
|
|
break;
|
|
case GST_FORMAT_TIME:
|
|
if (GST_AUDIO_INFO_RATE (&filter->info) == 0) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
*dest_value =
|
|
src_value * GST_SECOND / GST_AUDIO_INFO_RATE (&filter->info);
|
|
break;
|
|
default:
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
case GST_FORMAT_TIME:
|
|
switch (*dest_format) {
|
|
case GST_FORMAT_BYTES:
|
|
scale = GST_AUDIO_INFO_BPF (&filter->info);
|
|
/* fallthrough */
|
|
case GST_FORMAT_DEFAULT:
|
|
*dest_value =
|
|
src_value * scale * GST_AUDIO_INFO_RATE (&filter->info) /
|
|
GST_SECOND;
|
|
break;
|
|
default:
|
|
ret = FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
ret = FALSE;
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static gboolean
|
|
speed_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
|
|
{
|
|
gboolean ret = TRUE;
|
|
GstSpeed *filter;
|
|
|
|
filter = GST_SPEED (parent);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_POSITION:
|
|
{
|
|
GstFormat format;
|
|
GstFormat rformat = GST_FORMAT_TIME;
|
|
gint64 cur;
|
|
GstFormat conv_format = GST_FORMAT_TIME;
|
|
|
|
/* save requested format */
|
|
gst_query_parse_position (query, &format, NULL);
|
|
|
|
/* query peer for current position in time */
|
|
gst_query_set_position (query, GST_FORMAT_TIME, -1);
|
|
|
|
if (!gst_pad_peer_query_position (filter->sinkpad, rformat, &cur)) {
|
|
GST_LOG_OBJECT (filter, "TIME query on peer pad failed, trying BYTES");
|
|
rformat = GST_FORMAT_BYTES;
|
|
if (!gst_pad_peer_query_position (filter->sinkpad, rformat, &cur)) {
|
|
GST_LOG_OBJECT (filter, "BYTES query on peer pad failed too");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (rformat == GST_FORMAT_BYTES)
|
|
GST_LOG_OBJECT (filter,
|
|
"peer pad returned current=%" G_GINT64_FORMAT " bytes", cur);
|
|
else if (rformat == GST_FORMAT_TIME)
|
|
GST_LOG_OBJECT (filter, "peer pad returned time=%" G_GINT64_FORMAT,
|
|
cur);
|
|
|
|
/* convert to time format */
|
|
if (!gst_speed_convert (filter, rformat, cur, &conv_format, &cur)) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* adjust for speed factor */
|
|
cur /= filter->speed;
|
|
|
|
/* convert to time format */
|
|
if (!gst_speed_convert (filter, conv_format, cur, &format, &cur)) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
gst_query_set_position (query, format, cur);
|
|
|
|
GST_LOG_OBJECT (filter,
|
|
"position query: we return %" G_GUINT64_FORMAT " (format %u)", cur,
|
|
format);
|
|
|
|
break;
|
|
}
|
|
case GST_QUERY_DURATION:
|
|
{
|
|
GstFormat format;
|
|
GstFormat rformat = GST_FORMAT_TIME;
|
|
gint64 end;
|
|
GstFormat conv_format = GST_FORMAT_TIME;
|
|
|
|
/* save requested format */
|
|
gst_query_parse_duration (query, &format, NULL);
|
|
|
|
/* query peer for total length in time */
|
|
gst_query_set_duration (query, GST_FORMAT_TIME, -1);
|
|
|
|
if (!gst_pad_peer_query_duration (filter->sinkpad, rformat, &end)) {
|
|
GST_LOG_OBJECT (filter, "TIME query on peer pad failed, trying BYTES");
|
|
rformat = GST_FORMAT_BYTES;
|
|
if (!gst_pad_peer_query_duration (filter->sinkpad, rformat, &end)) {
|
|
GST_LOG_OBJECT (filter, "BYTES query on peer pad failed too");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (rformat == GST_FORMAT_BYTES)
|
|
GST_LOG_OBJECT (filter,
|
|
"peer pad returned total=%" G_GINT64_FORMAT " bytes", end);
|
|
else if (rformat == GST_FORMAT_TIME)
|
|
GST_LOG_OBJECT (filter, "peer pad returned time=%" G_GINT64_FORMAT,
|
|
end);
|
|
|
|
/* convert to time format */
|
|
if (!gst_speed_convert (filter, rformat, end, &conv_format, &end)) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* adjust for speed factor */
|
|
end /= filter->speed;
|
|
|
|
/* convert to time format */
|
|
if (!gst_speed_convert (filter, conv_format, end, &format, &end)) {
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
gst_query_set_duration (query, format, end);
|
|
|
|
GST_LOG_OBJECT (filter,
|
|
"duration query: we return %" G_GUINT64_FORMAT " (format %u)", end,
|
|
format);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
|
|
error:
|
|
|
|
gst_object_unref (filter);
|
|
GST_DEBUG ("error handling query");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_speed_class_init (GstSpeedClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
|
|
|
gobject_class->set_property = speed_set_property;
|
|
gobject_class->get_property = speed_get_property;
|
|
gstelement_class->change_state = speed_change_state;
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SPEED,
|
|
g_param_spec_float ("speed", "speed", "speed",
|
|
0.1f, 40.0, 1.0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "Speed",
|
|
"Filter/Effect/Audio",
|
|
"Set speed/pitch on audio/raw streams (resampler)",
|
|
"Andy Wingo <apwingo@eos.ncsu.edu>, "
|
|
"Tim-Philipp Müller <tim@centricular.net>");
|
|
|
|
gst_element_class_add_static_pad_template (gstelement_class,
|
|
&gst_speed_src_template);
|
|
gst_element_class_add_static_pad_template (gstelement_class,
|
|
&gst_speed_sink_template);
|
|
}
|
|
|
|
static void
|
|
gst_speed_init (GstSpeed * filter)
|
|
{
|
|
filter->sinkpad =
|
|
gst_pad_new_from_static_template (&gst_speed_sink_template, "sink");
|
|
gst_pad_set_chain_function (filter->sinkpad, speed_chain);
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
|
|
gst_pad_set_event_function (filter->sinkpad, speed_sink_event);
|
|
GST_PAD_SET_PROXY_CAPS (filter->sinkpad);
|
|
|
|
filter->srcpad =
|
|
gst_pad_new_from_static_template (&gst_speed_src_template, "src");
|
|
gst_pad_set_query_function (filter->srcpad, speed_src_query);
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
|
|
gst_pad_set_event_function (filter->srcpad, speed_src_event);
|
|
GST_PAD_SET_PROXY_CAPS (filter->srcpad);
|
|
|
|
filter->offset = 0;
|
|
filter->timestamp = 0;
|
|
}
|
|
|
|
static inline guint
|
|
speed_chain_int16 (GstSpeed * filter, GstBuffer * in_buf, GstBuffer * out_buf,
|
|
guint c, guint in_samples)
|
|
{
|
|
gint16 *in_data, *out_data;
|
|
gfloat interp, lower, i_float;
|
|
guint i, j;
|
|
GstMapInfo in_info, out_info;
|
|
|
|
gst_buffer_map (in_buf, &in_info, GST_MAP_READ);
|
|
gst_buffer_map (out_buf, &out_info, GST_MAP_WRITE);
|
|
|
|
in_data = (gint16 *) in_info.data + c;
|
|
out_data = (gint16 *) out_info.data + c;
|
|
|
|
lower = in_data[0];
|
|
i_float = 0.5 * (filter->speed - 1.0);
|
|
i = (guint) ceil (i_float);
|
|
j = 0;
|
|
|
|
while (i < in_samples) {
|
|
interp = i_float - floor (i_float);
|
|
|
|
out_data[j * GST_AUDIO_INFO_CHANNELS (&filter->info)] =
|
|
lower * (1 - interp) +
|
|
in_data[i * GST_AUDIO_INFO_CHANNELS (&filter->info)] * interp;
|
|
|
|
lower = in_data[i * GST_AUDIO_INFO_CHANNELS (&filter->info)];
|
|
|
|
i_float += filter->speed;
|
|
i = (guint) ceil (i_float);
|
|
|
|
++j;
|
|
}
|
|
|
|
gst_buffer_unmap (in_buf, &in_info);
|
|
gst_buffer_unmap (out_buf, &out_info);
|
|
return j;
|
|
}
|
|
|
|
static inline guint
|
|
speed_chain_float32 (GstSpeed * filter, GstBuffer * in_buf, GstBuffer * out_buf,
|
|
guint c, guint in_samples)
|
|
{
|
|
gfloat *in_data, *out_data;
|
|
gfloat interp, lower, i_float;
|
|
guint i, j;
|
|
GstMapInfo in_info, out_info;
|
|
|
|
gst_buffer_map (in_buf, &in_info, GST_MAP_WRITE);
|
|
gst_buffer_map (out_buf, &out_info, GST_MAP_WRITE);
|
|
|
|
in_data = (gfloat *) in_info.data + c;
|
|
out_data = (gfloat *) out_info.data + c;
|
|
|
|
lower = in_data[0];
|
|
i_float = 0.5 * (filter->speed - 1.0);
|
|
i = (guint) ceil (i_float);
|
|
j = 0;
|
|
|
|
while (i < in_samples) {
|
|
interp = i_float - floor (i_float);
|
|
|
|
out_data[j * GST_AUDIO_INFO_CHANNELS (&filter->info)] =
|
|
lower * (1 - interp) +
|
|
in_data[i * GST_AUDIO_INFO_CHANNELS (&filter->info)] * interp;
|
|
|
|
lower = in_data[i * GST_AUDIO_INFO_CHANNELS (&filter->info)];
|
|
|
|
i_float += filter->speed;
|
|
i = (guint) ceil (i_float);
|
|
|
|
++j;
|
|
}
|
|
gst_buffer_unmap (in_buf, &in_info);
|
|
gst_buffer_unmap (out_buf, &out_info);
|
|
return j;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
speed_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GstSpeed *filter = GST_SPEED (parent);
|
|
gboolean ret = FALSE;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEGMENT:{
|
|
gdouble rate;
|
|
GstFormat format;
|
|
gint64 start_value, stop_value, base;
|
|
const GstSegment *segment;
|
|
GstSegment seg;
|
|
|
|
gst_event_parse_segment (event, &segment);
|
|
|
|
rate = segment->rate;
|
|
format = segment->format;
|
|
start_value = segment->start;
|
|
stop_value = segment->stop;
|
|
base = segment->base;
|
|
|
|
gst_event_unref (event);
|
|
|
|
if (format != GST_FORMAT_TIME) {
|
|
GST_WARNING_OBJECT (filter, "newsegment event not in TIME format!");
|
|
break;
|
|
}
|
|
|
|
g_assert (filter->speed > 0);
|
|
|
|
if (start_value >= 0)
|
|
start_value /= filter->speed;
|
|
if (stop_value >= 0)
|
|
stop_value /= filter->speed;
|
|
base /= filter->speed;
|
|
|
|
/* this would only really be correct if we clipped incoming data */
|
|
filter->timestamp = start_value;
|
|
|
|
/* set to NONE so it gets reset later based on the timestamp when we have
|
|
* the samplerate */
|
|
filter->offset = GST_BUFFER_OFFSET_NONE;
|
|
|
|
gst_segment_init (&seg, GST_FORMAT_TIME);
|
|
seg.rate = rate;
|
|
seg.start = start_value;
|
|
seg.stop = stop_value;
|
|
seg.time = segment->time;
|
|
ret = gst_pad_push_event (filter->srcpad, gst_event_new_segment (&seg));
|
|
|
|
break;
|
|
}
|
|
case GST_EVENT_CAPS:
|
|
{
|
|
GstCaps *caps;
|
|
|
|
gst_event_parse_caps (event, &caps);
|
|
ret = speed_setcaps (pad, caps);
|
|
if (!ret) {
|
|
gst_event_unref (event);
|
|
return ret;
|
|
}
|
|
}
|
|
/* Fallthrough so that the caps event gets forwarded */
|
|
default:
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
speed_chain (GstPad * pad, GstObject * parent, GstBuffer * in_buf)
|
|
{
|
|
GstBuffer *out_buf;
|
|
GstSpeed *filter = GST_SPEED (parent);
|
|
guint c, in_samples, out_samples, out_size;
|
|
GstFlowReturn flow;
|
|
gsize size;
|
|
|
|
if (G_UNLIKELY (filter->offset == GST_BUFFER_OFFSET_NONE)) {
|
|
filter->offset = gst_util_uint64_scale_int (filter->timestamp,
|
|
GST_AUDIO_INFO_RATE (&filter->info), GST_SECOND);
|
|
}
|
|
|
|
/* buffersize has to be aligned to a frame */
|
|
out_size = ceil ((gfloat) gst_buffer_get_size (in_buf) / filter->speed);
|
|
out_size = ((out_size + GST_AUDIO_INFO_BPF (&filter->info) - 1) /
|
|
GST_AUDIO_INFO_BPF (&filter->info)) * GST_AUDIO_INFO_BPF (&filter->info);
|
|
|
|
out_buf = gst_buffer_new_and_alloc (out_size);
|
|
|
|
in_samples = gst_buffer_get_size (in_buf) /
|
|
GST_AUDIO_INFO_BPF (&filter->info);
|
|
|
|
out_samples = 0;
|
|
|
|
for (c = 0; c < GST_AUDIO_INFO_CHANNELS (&filter->info); ++c) {
|
|
if (GST_AUDIO_INFO_IS_INTEGER (&filter->info))
|
|
out_samples = speed_chain_int16 (filter, in_buf, out_buf, c, in_samples);
|
|
else
|
|
out_samples =
|
|
speed_chain_float32 (filter, in_buf, out_buf, c, in_samples);
|
|
}
|
|
|
|
size = out_samples * GST_AUDIO_INFO_BPF (&filter->info);
|
|
gst_buffer_set_size (out_buf, size);
|
|
|
|
GST_BUFFER_OFFSET (out_buf) = filter->offset;
|
|
GST_BUFFER_TIMESTAMP (out_buf) = filter->timestamp;
|
|
|
|
filter->offset += size / GST_AUDIO_INFO_BPF (&filter->info);
|
|
filter->timestamp = gst_util_uint64_scale_int (filter->offset, GST_SECOND,
|
|
GST_AUDIO_INFO_RATE (&filter->info));
|
|
|
|
/* make sure it's at least nominally a perfect stream */
|
|
GST_BUFFER_DURATION (out_buf) =
|
|
filter->timestamp - GST_BUFFER_TIMESTAMP (out_buf);
|
|
flow = gst_pad_push (filter->srcpad, out_buf);
|
|
|
|
if (G_UNLIKELY (flow != GST_FLOW_OK))
|
|
GST_DEBUG_OBJECT (filter, "flow: %s", gst_flow_get_name (flow));
|
|
|
|
gst_buffer_unref (in_buf);
|
|
return flow;
|
|
}
|
|
|
|
static void
|
|
speed_set_property (GObject * object, guint prop_id, const GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstSpeed *filter = GST_SPEED (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_SPEED:
|
|
filter->speed = g_value_get_float (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
speed_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstSpeed *filter = GST_SPEED (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_SPEED:
|
|
g_value_set_float (value, filter->speed);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
speed_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstSpeed *speed = GST_SPEED (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
speed->offset = GST_BUFFER_OFFSET_NONE;
|
|
speed->timestamp = 0;
|
|
gst_audio_info_init (&speed->info);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_ELEMENT_CLASS (gst_speed_parent_class)->change_state (element,
|
|
transition);
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (speed_debug, "speed", 0, "speed element");
|
|
|
|
return gst_element_register (plugin, "speed", GST_RANK_NONE, GST_TYPE_SPEED);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
speed,
|
|
"Set speed/pitch on audio/raw streams (resampler)",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|