mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
fe2ae2c0f7
That is, whenever we go through start/stop we have to ensure that on the next opportunity the buffers are reallocated again. Otherwise the buffers might be NULL because the element was reused with the same configuration as before (i.e. set_caps() wouldn't have reinited the buffers). https://bugzilla.gnome.org/show_bug.cgi?id=775898
906 lines
28 KiB
C
906 lines
28 KiB
C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2008 Rov Juvano <rovjuvano@users.sourceforge.net>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-scaletempo
|
|
*
|
|
* Scale tempo while maintaining pitch
|
|
* (WSOLA-like technique with cross correlation)
|
|
* Inspired by SoundTouch library by Olli Parviainen
|
|
*
|
|
* Use Sceletempo to apply playback rates without the chipmunk effect.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipelines</title>
|
|
* <para>
|
|
* |[
|
|
* filesrc location=media.ext ! decodebin name=d \
|
|
* d. ! queue ! audioconvert ! audioresample ! scaletempo ! audioconvert ! audioresample ! autoaudiosink \
|
|
* d. ! queue ! videoconvert ! autovideosink
|
|
* ]|
|
|
* OR
|
|
* |[
|
|
* playbin uri=... audio_sink="scaletempo ! audioconvert ! audioresample ! autoaudiosink"
|
|
* ]|
|
|
* When an application sends a seek event with rate != 1.0, Scaletempo applies
|
|
* the rate change by scaling the tempo without scaling the pitch.
|
|
*
|
|
* Scaletempo works by producing audio in constant sized chunks
|
|
* (#GstScaletempo:stride) but consuming chunks proportional to the playback
|
|
* rate.
|
|
*
|
|
* Scaletempo then smooths the output by blending the end of one stride with
|
|
* the next (#GstScaletempo:overlap).
|
|
*
|
|
* Scaletempo smooths the overlap further by searching within the input buffer
|
|
* for the best overlap position. Scaletempo uses a statistical cross
|
|
* correlation (roughly a dot-product). Scaletempo consumes most of its CPU
|
|
* cycles here. One can use the #GstScaletempo:search propery to tune how far
|
|
* the algoritm looks.
|
|
* </para>
|
|
* </refsect2>
|
|
*/
|
|
|
|
/*
|
|
* Note: frame = audio key unit (i.e. one sample for each channel)
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/base/gstbasetransform.h>
|
|
#include <gst/audio/audio.h>
|
|
#include <string.h> /* for memset */
|
|
|
|
#include "gstscaletempo.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_scaletempo_debug);
|
|
#define GST_CAT_DEFAULT gst_scaletempo_debug
|
|
|
|
/* Filter signals and args */
|
|
enum
|
|
{
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_RATE,
|
|
PROP_STRIDE,
|
|
PROP_OVERLAP,
|
|
PROP_SEARCH,
|
|
};
|
|
|
|
#define SUPPORTED_CAPS \
|
|
GST_STATIC_CAPS ( \
|
|
GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32)) "; " \
|
|
GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F64)) "; " \
|
|
GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (S16)) \
|
|
)
|
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
SUPPORTED_CAPS);
|
|
|
|
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
SUPPORTED_CAPS);
|
|
|
|
#define DEBUG_INIT(bla) GST_DEBUG_CATEGORY_INIT (gst_scaletempo_debug, "scaletempo", 0, "scaletempo element");
|
|
|
|
#define gst_scaletempo_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstScaletempo, gst_scaletempo,
|
|
GST_TYPE_BASE_TRANSFORM, DEBUG_INIT (0));
|
|
|
|
#define CREATE_BEST_OVERLAP_OFFSET_FLOAT_FUNC(type) \
|
|
static guint \
|
|
best_overlap_offset_##type (GstScaletempo * st) \
|
|
{ \
|
|
g##type *pw, *po, *ppc, *search_start; \
|
|
g##type best_corr = G_MININT; \
|
|
guint best_off = 0; \
|
|
gint i, off; \
|
|
\
|
|
pw = st->table_window; \
|
|
po = st->buf_overlap; \
|
|
po += st->samples_per_frame; \
|
|
ppc = st->buf_pre_corr; \
|
|
for (i = st->samples_per_frame; i < st->samples_overlap; i++) { \
|
|
*ppc++ = *pw++ * *po++; \
|
|
} \
|
|
\
|
|
search_start = (g##type *) st->buf_queue + st->samples_per_frame; \
|
|
for (off = 0; off < st->frames_search; off++) { \
|
|
g##type corr = 0; \
|
|
g##type *ps = search_start; \
|
|
ppc = st->buf_pre_corr; \
|
|
for (i = st->samples_per_frame; i < st->samples_overlap; i++) { \
|
|
corr += *ppc++ * *ps++; \
|
|
} \
|
|
if (corr > best_corr) { \
|
|
best_corr = corr; \
|
|
best_off = off; \
|
|
} \
|
|
search_start += st->samples_per_frame; \
|
|
} \
|
|
\
|
|
return best_off * st->bytes_per_frame; \
|
|
}
|
|
|
|
CREATE_BEST_OVERLAP_OFFSET_FLOAT_FUNC (float);
|
|
CREATE_BEST_OVERLAP_OFFSET_FLOAT_FUNC (double);
|
|
|
|
/* buffer padding for loop optimization: sizeof(gint32) * (loop_size - 1) */
|
|
#define UNROLL_PADDING (4*3)
|
|
static guint
|
|
best_overlap_offset_s16 (GstScaletempo * st)
|
|
{
|
|
gint32 *pw, *ppc;
|
|
gint16 *po, *search_start;
|
|
gint64 best_corr = G_MININT64;
|
|
guint best_off = 0;
|
|
guint off;
|
|
glong i;
|
|
|
|
pw = st->table_window;
|
|
po = st->buf_overlap;
|
|
po += st->samples_per_frame;
|
|
ppc = st->buf_pre_corr;
|
|
for (i = st->samples_per_frame; i < st->samples_overlap; i++) {
|
|
*ppc++ = (*pw++ * *po++) >> 15;
|
|
}
|
|
|
|
search_start = (gint16 *) st->buf_queue + st->samples_per_frame;
|
|
for (off = 0; off < st->frames_search; off++) {
|
|
gint64 corr = 0;
|
|
gint16 *ps = search_start;
|
|
ppc = st->buf_pre_corr;
|
|
ppc += st->samples_overlap - st->samples_per_frame;
|
|
ps += st->samples_overlap - st->samples_per_frame;
|
|
i = -((glong) st->samples_overlap - (glong) st->samples_per_frame);
|
|
do {
|
|
corr += ppc[i + 0] * ps[i + 0];
|
|
corr += ppc[i + 1] * ps[i + 1];
|
|
corr += ppc[i + 2] * ps[i + 2];
|
|
corr += ppc[i + 3] * ps[i + 3];
|
|
i += 4;
|
|
} while (i < 0);
|
|
if (corr > best_corr) {
|
|
best_corr = corr;
|
|
best_off = off;
|
|
}
|
|
search_start += st->samples_per_frame;
|
|
}
|
|
|
|
return best_off * st->bytes_per_frame;
|
|
}
|
|
|
|
#define CREATE_OUTPUT_OVERLAP_FLOAT_FUNC(type) \
|
|
static void \
|
|
output_overlap_##type (GstScaletempo * st, gpointer buf_out, guint bytes_off) \
|
|
{ \
|
|
g##type *pout = buf_out; \
|
|
g##type *pb = st->table_blend; \
|
|
g##type *po = st->buf_overlap; \
|
|
g##type *pin = (g##type *) (st->buf_queue + bytes_off); \
|
|
gint i; \
|
|
for (i = 0; i < st->samples_overlap; i++) { \
|
|
*pout++ = *po - *pb++ * (*po - *pin++); \
|
|
po++; \
|
|
} \
|
|
}
|
|
|
|
CREATE_OUTPUT_OVERLAP_FLOAT_FUNC (float);
|
|
CREATE_OUTPUT_OVERLAP_FLOAT_FUNC (double);
|
|
|
|
static void
|
|
output_overlap_s16 (GstScaletempo * st, gpointer buf_out, guint bytes_off)
|
|
{
|
|
gint16 *pout = buf_out;
|
|
gint32 *pb = st->table_blend;
|
|
gint16 *po = st->buf_overlap;
|
|
gint16 *pin = (gint16 *) (st->buf_queue + bytes_off);
|
|
gint i;
|
|
for (i = 0; i < st->samples_overlap; i++) {
|
|
*pout++ = *po - ((*pb++ * (*po - *pin++)) >> 16);
|
|
po++;
|
|
}
|
|
}
|
|
|
|
static guint
|
|
fill_queue (GstScaletempo * st, GstBuffer * buf_in, guint offset)
|
|
{
|
|
guint bytes_in = gst_buffer_get_size (buf_in) - offset;
|
|
guint offset_unchanged = offset;
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buf_in, &map, GST_MAP_READ);
|
|
if (st->bytes_to_slide > 0) {
|
|
if (st->bytes_to_slide < st->bytes_queued) {
|
|
guint bytes_in_move = st->bytes_queued - st->bytes_to_slide;
|
|
memmove (st->buf_queue, st->buf_queue + st->bytes_to_slide,
|
|
bytes_in_move);
|
|
st->bytes_to_slide = 0;
|
|
st->bytes_queued = bytes_in_move;
|
|
} else {
|
|
guint bytes_in_skip;
|
|
st->bytes_to_slide -= st->bytes_queued;
|
|
bytes_in_skip = MIN (st->bytes_to_slide, bytes_in);
|
|
st->bytes_queued = 0;
|
|
st->bytes_to_slide -= bytes_in_skip;
|
|
offset += bytes_in_skip;
|
|
bytes_in -= bytes_in_skip;
|
|
}
|
|
}
|
|
|
|
if (bytes_in > 0) {
|
|
guint bytes_in_copy =
|
|
MIN (st->bytes_queue_max - st->bytes_queued, bytes_in);
|
|
memcpy (st->buf_queue + st->bytes_queued, map.data + offset, bytes_in_copy);
|
|
st->bytes_queued += bytes_in_copy;
|
|
offset += bytes_in_copy;
|
|
}
|
|
gst_buffer_unmap (buf_in, &map);
|
|
|
|
return offset - offset_unchanged;
|
|
}
|
|
|
|
static void
|
|
reinit_buffers (GstScaletempo * st)
|
|
{
|
|
gint i, j;
|
|
guint frames_overlap;
|
|
guint new_size;
|
|
GstClockTime latency;
|
|
|
|
guint frames_stride = st->ms_stride * st->sample_rate / 1000.0;
|
|
st->bytes_stride = frames_stride * st->bytes_per_frame;
|
|
|
|
/* overlap */
|
|
frames_overlap = frames_stride * st->percent_overlap;
|
|
if (frames_overlap < 1) { /* if no overlap */
|
|
st->bytes_overlap = 0;
|
|
st->bytes_standing = st->bytes_stride;
|
|
st->samples_standing = st->bytes_standing / st->bytes_per_sample;
|
|
st->output_overlap = NULL;
|
|
} else {
|
|
guint prev_overlap = st->bytes_overlap;
|
|
st->bytes_overlap = frames_overlap * st->bytes_per_frame;
|
|
st->samples_overlap = frames_overlap * st->samples_per_frame;
|
|
st->bytes_standing = st->bytes_stride - st->bytes_overlap;
|
|
st->samples_standing = st->bytes_standing / st->bytes_per_sample;
|
|
st->buf_overlap = g_realloc (st->buf_overlap, st->bytes_overlap);
|
|
/* S16 uses gint32 blend table, floats/doubles use their respective type */
|
|
st->table_blend =
|
|
g_realloc (st->table_blend,
|
|
st->samples_overlap * (st->format ==
|
|
GST_AUDIO_FORMAT_S16 ? 4 : st->bytes_per_sample));
|
|
if (st->bytes_overlap > prev_overlap) {
|
|
memset ((guint8 *) st->buf_overlap + prev_overlap, 0,
|
|
st->bytes_overlap - prev_overlap);
|
|
}
|
|
if (st->format == GST_AUDIO_FORMAT_S16) {
|
|
gint32 *pb = st->table_blend;
|
|
gint64 blend = 0;
|
|
for (i = 0; i < frames_overlap; i++) {
|
|
gint32 v = blend / frames_overlap;
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pb++ = v;
|
|
}
|
|
blend += 65535; /* 2^16 */
|
|
}
|
|
st->output_overlap = output_overlap_s16;
|
|
} else if (st->format == GST_AUDIO_FORMAT_F32) {
|
|
gfloat *pb = st->table_blend;
|
|
gfloat t = (gfloat) frames_overlap;
|
|
for (i = 0; i < frames_overlap; i++) {
|
|
gfloat v = i / t;
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pb++ = v;
|
|
}
|
|
}
|
|
st->output_overlap = output_overlap_float;
|
|
} else {
|
|
gdouble *pb = st->table_blend;
|
|
gdouble t = (gdouble) frames_overlap;
|
|
for (i = 0; i < frames_overlap; i++) {
|
|
gdouble v = i / t;
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pb++ = v;
|
|
}
|
|
}
|
|
st->output_overlap = output_overlap_double;
|
|
}
|
|
}
|
|
|
|
/* best overlap */
|
|
st->frames_search =
|
|
(frames_overlap <= 1) ? 0 : st->ms_search * st->sample_rate / 1000.0;
|
|
if (st->frames_search < 1) { /* if no search */
|
|
st->best_overlap_offset = NULL;
|
|
} else {
|
|
/* S16 uses gint32 buffer, floats/doubles use their respective type */
|
|
guint bytes_pre_corr =
|
|
(st->samples_overlap - st->samples_per_frame) * (st->format ==
|
|
GST_AUDIO_FORMAT_S16 ? 4 : st->bytes_per_sample);
|
|
st->buf_pre_corr =
|
|
g_realloc (st->buf_pre_corr, bytes_pre_corr + UNROLL_PADDING);
|
|
st->table_window = g_realloc (st->table_window, bytes_pre_corr);
|
|
if (st->format == GST_AUDIO_FORMAT_S16) {
|
|
gint64 t = frames_overlap;
|
|
gint32 n = 8589934588LL / (t * t); /* 4 * (2^31 - 1) / t^2 */
|
|
gint32 *pw;
|
|
|
|
memset ((guint8 *) st->buf_pre_corr + bytes_pre_corr, 0, UNROLL_PADDING);
|
|
pw = st->table_window;
|
|
for (i = 1; i < frames_overlap; i++) {
|
|
gint32 v = (i * (t - i) * n) >> 15;
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pw++ = v;
|
|
}
|
|
}
|
|
st->best_overlap_offset = best_overlap_offset_s16;
|
|
} else if (st->format == GST_AUDIO_FORMAT_F32) {
|
|
gfloat *pw = st->table_window;
|
|
for (i = 1; i < frames_overlap; i++) {
|
|
gfloat v = i * (frames_overlap - i);
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pw++ = v;
|
|
}
|
|
}
|
|
st->best_overlap_offset = best_overlap_offset_float;
|
|
} else {
|
|
gdouble *pw = st->table_window;
|
|
for (i = 1; i < frames_overlap; i++) {
|
|
gdouble v = i * (frames_overlap - i);
|
|
for (j = 0; j < st->samples_per_frame; j++) {
|
|
*pw++ = v;
|
|
}
|
|
}
|
|
st->best_overlap_offset = best_overlap_offset_double;
|
|
}
|
|
}
|
|
|
|
new_size =
|
|
(st->frames_search + frames_stride +
|
|
frames_overlap) * st->bytes_per_frame;
|
|
if (st->bytes_queued > new_size) {
|
|
if (st->bytes_to_slide > st->bytes_queued) {
|
|
st->bytes_to_slide -= st->bytes_queued;
|
|
st->bytes_queued = 0;
|
|
} else {
|
|
guint new_queued = MIN (st->bytes_queued - st->bytes_to_slide, new_size);
|
|
memmove (st->buf_queue,
|
|
st->buf_queue + st->bytes_queued - new_queued, new_queued);
|
|
st->bytes_to_slide = 0;
|
|
st->bytes_queued = new_queued;
|
|
}
|
|
}
|
|
|
|
st->bytes_queue_max = new_size;
|
|
st->buf_queue = g_realloc (st->buf_queue, st->bytes_queue_max);
|
|
|
|
latency =
|
|
gst_util_uint64_scale (st->bytes_queue_max, GST_SECOND,
|
|
st->bytes_per_frame * st->sample_rate);
|
|
if (st->latency != latency) {
|
|
st->latency = latency;
|
|
gst_element_post_message (GST_ELEMENT (st),
|
|
gst_message_new_latency (GST_OBJECT (st)));
|
|
}
|
|
|
|
st->bytes_stride_scaled = st->bytes_stride * st->scale;
|
|
st->frames_stride_scaled = st->bytes_stride_scaled / st->bytes_per_frame;
|
|
|
|
GST_DEBUG
|
|
("%.3f scale, %.3f stride_in, %i stride_out, %i standing, %i overlap, %i search, %i queue, %s mode",
|
|
st->scale, st->frames_stride_scaled,
|
|
(gint) (st->bytes_stride / st->bytes_per_frame),
|
|
(gint) (st->bytes_standing / st->bytes_per_frame),
|
|
(gint) (st->bytes_overlap / st->bytes_per_frame), st->frames_search,
|
|
(gint) (st->bytes_queue_max / st->bytes_per_frame),
|
|
gst_audio_format_to_string (st->format));
|
|
|
|
st->reinit_buffers = FALSE;
|
|
}
|
|
|
|
static GstBuffer *
|
|
reverse_buffer (GstScaletempo * st, GstBuffer * inbuf)
|
|
{
|
|
GstBuffer *outbuf;
|
|
GstMapInfo imap, omap;
|
|
|
|
gst_buffer_map (inbuf, &imap, GST_MAP_READ);
|
|
outbuf = gst_buffer_new_and_alloc (imap.size);
|
|
gst_buffer_map (outbuf, &omap, GST_MAP_WRITE);
|
|
|
|
if (st->format == GST_AUDIO_FORMAT_F64) {
|
|
const gint64 *ip = (const gint64 *) imap.data;
|
|
gint64 *op = (gint64 *) (omap.data + omap.size - 8 * st->samples_per_frame);
|
|
guint i, n = imap.size / (8 * st->samples_per_frame);
|
|
guint j, c = st->samples_per_frame;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
for (j = 0; j < c; j++)
|
|
op[j] = ip[j];
|
|
op -= c;
|
|
ip += c;
|
|
}
|
|
} else {
|
|
const gint32 *ip = (const gint32 *) imap.data;
|
|
gint32 *op = (gint32 *) (omap.data + omap.size - 4 * st->samples_per_frame);
|
|
guint i, n = imap.size / (4 * st->samples_per_frame);
|
|
guint j, c = st->samples_per_frame;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
for (j = 0; j < c; j++)
|
|
op[j] = ip[j];
|
|
op -= c;
|
|
ip += c;
|
|
}
|
|
}
|
|
|
|
gst_buffer_unmap (inbuf, &imap);
|
|
gst_buffer_unmap (outbuf, &omap);
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
/* GstBaseTransform vmethod implementations */
|
|
static GstFlowReturn
|
|
gst_scaletempo_transform (GstBaseTransform * trans,
|
|
GstBuffer * inbuf, GstBuffer * outbuf)
|
|
{
|
|
GstScaletempo *st = GST_SCALETEMPO (trans);
|
|
gint8 *pout;
|
|
guint offset_in, bytes_out;
|
|
GstMapInfo omap;
|
|
GstClockTime timestamp;
|
|
GstBuffer *tmpbuf = NULL;
|
|
|
|
if (st->reverse)
|
|
tmpbuf = reverse_buffer (st, inbuf);
|
|
|
|
gst_buffer_map (outbuf, &omap, GST_MAP_WRITE);
|
|
pout = (gint8 *) omap.data;
|
|
bytes_out = omap.size;
|
|
|
|
offset_in = fill_queue (st, tmpbuf ? tmpbuf : inbuf, 0);
|
|
bytes_out = 0;
|
|
while (st->bytes_queued >= st->bytes_queue_max) {
|
|
guint bytes_off = 0;
|
|
gdouble frames_to_slide;
|
|
guint frames_to_stride_whole;
|
|
|
|
/* output stride */
|
|
if (st->output_overlap) {
|
|
if (st->best_overlap_offset) {
|
|
bytes_off = st->best_overlap_offset (st);
|
|
}
|
|
st->output_overlap (st, pout, bytes_off);
|
|
}
|
|
memcpy (pout + st->bytes_overlap,
|
|
st->buf_queue + bytes_off + st->bytes_overlap, st->bytes_standing);
|
|
pout += st->bytes_stride;
|
|
bytes_out += st->bytes_stride;
|
|
|
|
/* input stride */
|
|
memcpy (st->buf_overlap,
|
|
st->buf_queue + bytes_off + st->bytes_stride, st->bytes_overlap);
|
|
frames_to_slide = st->frames_stride_scaled + st->frames_stride_error;
|
|
frames_to_stride_whole = (gint) frames_to_slide;
|
|
st->bytes_to_slide = frames_to_stride_whole * st->bytes_per_frame;
|
|
st->frames_stride_error = frames_to_slide - frames_to_stride_whole;
|
|
|
|
offset_in += fill_queue (st, tmpbuf ? tmpbuf : inbuf, offset_in);
|
|
}
|
|
gst_buffer_unmap (outbuf, &omap);
|
|
|
|
if (st->reverse) {
|
|
timestamp = st->in_segment.stop - GST_BUFFER_TIMESTAMP (inbuf);
|
|
if (timestamp < st->latency)
|
|
timestamp = 0;
|
|
else
|
|
timestamp -= st->latency;
|
|
} else {
|
|
timestamp = GST_BUFFER_TIMESTAMP (inbuf) - st->in_segment.start;
|
|
if (timestamp < st->latency)
|
|
timestamp = 0;
|
|
else
|
|
timestamp -= st->latency;
|
|
}
|
|
GST_BUFFER_TIMESTAMP (outbuf) = timestamp / st->scale + st->in_segment.start;
|
|
GST_BUFFER_DURATION (outbuf) =
|
|
gst_util_uint64_scale (bytes_out, GST_SECOND,
|
|
st->bytes_per_frame * st->sample_rate);
|
|
gst_buffer_set_size (outbuf, bytes_out);
|
|
|
|
if (tmpbuf)
|
|
gst_buffer_unref (tmpbuf);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_scaletempo_submit_input_buffer (GstBaseTransform * trans,
|
|
gboolean is_discont, GstBuffer * input)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
if (scaletempo->in_segment.format == GST_FORMAT_TIME) {
|
|
input =
|
|
gst_audio_buffer_clip (input, &scaletempo->in_segment,
|
|
scaletempo->sample_rate, scaletempo->bytes_per_frame);
|
|
if (!input)
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (trans,
|
|
is_discont, input);
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_transform_size (GstBaseTransform * trans,
|
|
GstPadDirection direction,
|
|
GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize)
|
|
{
|
|
if (direction == GST_PAD_SINK) {
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
gint bytes_to_out;
|
|
|
|
if (scaletempo->reinit_buffers)
|
|
reinit_buffers (scaletempo);
|
|
|
|
bytes_to_out = size + scaletempo->bytes_queued - scaletempo->bytes_to_slide;
|
|
if (bytes_to_out < (gint) scaletempo->bytes_queue_max) {
|
|
*othersize = 0;
|
|
} else {
|
|
/* while (total_buffered - stride_length * n >= queue_max) n++ */
|
|
*othersize = scaletempo->bytes_stride * ((guint) (
|
|
(bytes_to_out - scaletempo->bytes_queue_max +
|
|
/* rounding protection */ scaletempo->bytes_per_frame)
|
|
/ scaletempo->bytes_stride_scaled) + 1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_sink_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
|
|
GstSegment segment;
|
|
|
|
gst_event_copy_segment (event, &segment);
|
|
|
|
if (segment.format != GST_FORMAT_TIME
|
|
|| scaletempo->scale != ABS (segment.rate)
|
|
|| ! !scaletempo->reverse != ! !(segment.rate < 0.0)) {
|
|
if (segment.format != GST_FORMAT_TIME || ABS (segment.rate - 1.0) < 1e-10) {
|
|
scaletempo->scale = 1.0;
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (scaletempo),
|
|
TRUE);
|
|
} else {
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (scaletempo),
|
|
FALSE);
|
|
scaletempo->scale = ABS (segment.rate);
|
|
scaletempo->reverse = segment.rate < 0.0;
|
|
scaletempo->bytes_stride_scaled =
|
|
scaletempo->bytes_stride * scaletempo->scale;
|
|
scaletempo->frames_stride_scaled =
|
|
scaletempo->bytes_stride_scaled / scaletempo->bytes_per_frame;
|
|
GST_DEBUG ("%.3f scale, %.3f stride_in, %i stride_out",
|
|
scaletempo->scale, scaletempo->frames_stride_scaled,
|
|
(gint) (scaletempo->bytes_stride / scaletempo->bytes_per_frame));
|
|
|
|
scaletempo->bytes_to_slide = 0;
|
|
}
|
|
}
|
|
|
|
scaletempo->in_segment = segment;
|
|
scaletempo->out_segment = segment;
|
|
|
|
if (scaletempo->scale != 1.0 || scaletempo->reverse) {
|
|
guint32 seqnum;
|
|
|
|
segment.applied_rate = segment.rate;
|
|
segment.rate = 1.0;
|
|
|
|
if (segment.stop != -1) {
|
|
segment.stop =
|
|
(segment.stop - segment.start) / ABS (segment.applied_rate) +
|
|
segment.start;
|
|
}
|
|
|
|
scaletempo->out_segment = segment;
|
|
|
|
seqnum = gst_event_get_seqnum (event);
|
|
gst_event_unref (event);
|
|
|
|
event = gst_event_new_segment (&segment);
|
|
gst_event_set_seqnum (event, seqnum);
|
|
|
|
return gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (trans), event);
|
|
}
|
|
} else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) {
|
|
gst_segment_init (&scaletempo->in_segment, GST_FORMAT_UNDEFINED);
|
|
gst_segment_init (&scaletempo->out_segment, GST_FORMAT_UNDEFINED);
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_set_caps (GstBaseTransform * trans,
|
|
GstCaps * incaps, GstCaps * outcaps)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
gint width, bps, nch, rate;
|
|
GstAudioInfo info;
|
|
GstAudioFormat format;
|
|
|
|
if (!gst_audio_info_from_caps (&info, incaps))
|
|
return FALSE;
|
|
|
|
nch = GST_AUDIO_INFO_CHANNELS (&info);
|
|
rate = GST_AUDIO_INFO_RATE (&info);
|
|
width = GST_AUDIO_INFO_WIDTH (&info);
|
|
format = GST_AUDIO_INFO_FORMAT (&info);
|
|
|
|
bps = width / 8;
|
|
|
|
GST_DEBUG ("caps: %" GST_PTR_FORMAT ", %d bps", incaps, bps);
|
|
|
|
if (rate != scaletempo->sample_rate
|
|
|| nch != scaletempo->samples_per_frame
|
|
|| bps != scaletempo->bytes_per_sample || format != scaletempo->format) {
|
|
scaletempo->sample_rate = rate;
|
|
scaletempo->samples_per_frame = nch;
|
|
scaletempo->bytes_per_sample = bps;
|
|
scaletempo->bytes_per_frame = nch * bps;
|
|
scaletempo->format = format;
|
|
scaletempo->reinit_buffers = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_start (GstBaseTransform * trans)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
gst_segment_init (&scaletempo->in_segment, GST_FORMAT_UNDEFINED);
|
|
gst_segment_init (&scaletempo->out_segment, GST_FORMAT_UNDEFINED);
|
|
scaletempo->reinit_buffers = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_stop (GstBaseTransform * trans)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
g_free (scaletempo->buf_queue);
|
|
scaletempo->buf_queue = NULL;
|
|
g_free (scaletempo->buf_overlap);
|
|
scaletempo->buf_overlap = NULL;
|
|
g_free (scaletempo->table_blend);
|
|
scaletempo->table_blend = NULL;
|
|
g_free (scaletempo->buf_pre_corr);
|
|
scaletempo->buf_pre_corr = NULL;
|
|
g_free (scaletempo->table_window);
|
|
scaletempo->table_window = NULL;
|
|
scaletempo->reinit_buffers = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_scaletempo_query (GstBaseTransform * trans, GstPadDirection direction,
|
|
GstQuery * query)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (trans);
|
|
|
|
if (direction == GST_PAD_SRC) {
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_LATENCY:{
|
|
GstPad *peer;
|
|
|
|
if ((peer = gst_pad_get_peer (GST_BASE_TRANSFORM_SINK_PAD (trans)))) {
|
|
if ((gst_pad_query (peer, query))) {
|
|
GstClockTime min, max;
|
|
gboolean live;
|
|
|
|
gst_query_parse_latency (query, &live, &min, &max);
|
|
|
|
GST_DEBUG_OBJECT (scaletempo, "Peer latency: min %"
|
|
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
|
|
|
|
/* add our own latency */
|
|
GST_DEBUG_OBJECT (scaletempo, "Our latency: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (scaletempo->latency));
|
|
min += scaletempo->latency;
|
|
if (max != GST_CLOCK_TIME_NONE)
|
|
max += scaletempo->latency;
|
|
|
|
GST_DEBUG_OBJECT (scaletempo, "Calculated total latency : min %"
|
|
GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (min), GST_TIME_ARGS (max));
|
|
gst_query_set_latency (query, live, min, max);
|
|
}
|
|
gst_object_unref (peer);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
default:{
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
|
|
query);
|
|
}
|
|
}
|
|
} else {
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
|
|
query);
|
|
}
|
|
}
|
|
|
|
/* GObject vmethod implementations */
|
|
static void
|
|
gst_scaletempo_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_RATE:
|
|
g_value_set_double (value, scaletempo->scale);
|
|
break;
|
|
case PROP_STRIDE:
|
|
g_value_set_uint (value, scaletempo->ms_stride);
|
|
break;
|
|
case PROP_OVERLAP:
|
|
g_value_set_double (value, scaletempo->percent_overlap);
|
|
break;
|
|
case PROP_SEARCH:
|
|
g_value_set_uint (value, scaletempo->ms_search);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_scaletempo_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstScaletempo *scaletempo = GST_SCALETEMPO (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_STRIDE:{
|
|
guint new_value = g_value_get_uint (value);
|
|
if (scaletempo->ms_stride != new_value) {
|
|
scaletempo->ms_stride = new_value;
|
|
scaletempo->reinit_buffers = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_OVERLAP:{
|
|
gdouble new_value = g_value_get_double (value);
|
|
if (scaletempo->percent_overlap != new_value) {
|
|
scaletempo->percent_overlap = new_value;
|
|
scaletempo->reinit_buffers = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case PROP_SEARCH:{
|
|
guint new_value = g_value_get_uint (value);
|
|
if (scaletempo->ms_search != new_value) {
|
|
scaletempo->ms_search = new_value;
|
|
scaletempo->reinit_buffers = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_scaletempo_class_init (GstScaletempoClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass);
|
|
|
|
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_scaletempo_get_property);
|
|
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_scaletempo_set_property);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_RATE,
|
|
g_param_spec_double ("rate", "Playback Rate", "Current playback rate",
|
|
G_MININT, G_MAXINT, 1.0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_STRIDE,
|
|
g_param_spec_uint ("stride", "Stride Length",
|
|
"Length in milliseconds to output each stride", 1, 5000, 30,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAP,
|
|
g_param_spec_double ("overlap", "Overlap Length",
|
|
"Percentage of stride to overlap", 0, 1, .2,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SEARCH,
|
|
g_param_spec_uint ("search", "Search Length",
|
|
"Length in milliseconds to search for best overlap position", 0, 500,
|
|
14, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
|
|
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
|
|
gst_element_class_set_static_metadata (gstelement_class, "Scaletempo",
|
|
"Filter/Effect/Rate",
|
|
"Sync audio tempo with playback rate",
|
|
"Rov Juvano <rovjuvano@users.sourceforge.net>");
|
|
|
|
basetransform_class->sink_event =
|
|
GST_DEBUG_FUNCPTR (gst_scaletempo_sink_event);
|
|
basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_scaletempo_set_caps);
|
|
basetransform_class->transform_size =
|
|
GST_DEBUG_FUNCPTR (gst_scaletempo_transform_size);
|
|
basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_scaletempo_transform);
|
|
basetransform_class->query = GST_DEBUG_FUNCPTR (gst_scaletempo_query);
|
|
basetransform_class->start = GST_DEBUG_FUNCPTR (gst_scaletempo_start);
|
|
basetransform_class->stop = GST_DEBUG_FUNCPTR (gst_scaletempo_stop);
|
|
basetransform_class->submit_input_buffer =
|
|
GST_DEBUG_FUNCPTR (gst_scaletempo_submit_input_buffer);
|
|
}
|
|
|
|
static void
|
|
gst_scaletempo_init (GstScaletempo * scaletempo)
|
|
{
|
|
/* defaults */
|
|
scaletempo->ms_stride = 30;
|
|
scaletempo->percent_overlap = .2;
|
|
scaletempo->ms_search = 14;
|
|
|
|
/* uninitialized */
|
|
scaletempo->scale = 0;
|
|
scaletempo->sample_rate = 0;
|
|
scaletempo->frames_stride_error = 0;
|
|
scaletempo->bytes_stride = 0;
|
|
scaletempo->bytes_queued = 0;
|
|
scaletempo->bytes_to_slide = 0;
|
|
gst_segment_init (&scaletempo->in_segment, GST_FORMAT_UNDEFINED);
|
|
gst_segment_init (&scaletempo->out_segment, GST_FORMAT_UNDEFINED);
|
|
}
|