mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 18:05:37 +00:00
![Volker Rümelin](/assets/img/avatar_default.png)
Introduce the new function st_rate_frames_out() to calculate the exact number of audio output frames the resampling code can generate from a given number of audio input frames. When upsampling, this function returns the maximum number of output frames. This new function replaces the audio_frontend_frames_in() function, which calculated the average number of output frames rounded down to the nearest integer. The audio_frontend_frames_in() function was additionally used to limit the number of output frames to the resample buffer size. In audio_pcm_sw_read() the variable resample_buf.size replaces the open coded audio_frontend_frames_in() function. In audio_run_in() an additional MIN() function is necessary. After this patch the audio packet length calculation for audio recording is exact. Acked-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> Signed-off-by: Volker Rümelin <vr_qemu@t-online.de> Message-Id: <20230224190555.7409-12-vr_qemu@t-online.de>
546 lines
14 KiB
C
546 lines
14 KiB
C
/*
|
|
* QEMU Mixing engine
|
|
*
|
|
* Copyright (c) 2004-2005 Vassili Karpov (malc)
|
|
* Copyright (c) 1998 Fabrice Bellard
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/bswap.h"
|
|
#include "qemu/error-report.h"
|
|
#include "audio.h"
|
|
|
|
#define AUDIO_CAP "mixeng"
|
|
#include "audio_int.h"
|
|
|
|
/* 8 bit */
|
|
#define ENDIAN_CONVERSION natural
|
|
#define ENDIAN_CONVERT(v) (v)
|
|
|
|
/* Signed 8 bit */
|
|
#define BSIZE 8
|
|
#define ITYPE int
|
|
#define IN_MIN SCHAR_MIN
|
|
#define IN_MAX SCHAR_MAX
|
|
#define SIGNED
|
|
#define SHIFT 8
|
|
#include "mixeng_template.h"
|
|
#undef SIGNED
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
/* Unsigned 8 bit */
|
|
#define BSIZE 8
|
|
#define ITYPE uint
|
|
#define IN_MIN 0
|
|
#define IN_MAX UCHAR_MAX
|
|
#define SHIFT 8
|
|
#include "mixeng_template.h"
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
|
|
/* Signed 16 bit */
|
|
#define BSIZE 16
|
|
#define ITYPE int
|
|
#define IN_MIN SHRT_MIN
|
|
#define IN_MAX SHRT_MAX
|
|
#define SIGNED
|
|
#define SHIFT 16
|
|
#define ENDIAN_CONVERSION natural
|
|
#define ENDIAN_CONVERT(v) (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#define ENDIAN_CONVERSION swap
|
|
#define ENDIAN_CONVERT(v) bswap16 (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#undef SIGNED
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
/* Unsigned 16 bit */
|
|
#define BSIZE 16
|
|
#define ITYPE uint
|
|
#define IN_MIN 0
|
|
#define IN_MAX USHRT_MAX
|
|
#define SHIFT 16
|
|
#define ENDIAN_CONVERSION natural
|
|
#define ENDIAN_CONVERT(v) (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#define ENDIAN_CONVERSION swap
|
|
#define ENDIAN_CONVERT(v) bswap16 (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
/* Signed 32 bit */
|
|
#define BSIZE 32
|
|
#define ITYPE int
|
|
#define IN_MIN INT32_MIN
|
|
#define IN_MAX INT32_MAX
|
|
#define SIGNED
|
|
#define SHIFT 32
|
|
#define ENDIAN_CONVERSION natural
|
|
#define ENDIAN_CONVERT(v) (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#define ENDIAN_CONVERSION swap
|
|
#define ENDIAN_CONVERT(v) bswap32 (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#undef SIGNED
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
/* Unsigned 32 bit */
|
|
#define BSIZE 32
|
|
#define ITYPE uint
|
|
#define IN_MIN 0
|
|
#define IN_MAX UINT32_MAX
|
|
#define SHIFT 32
|
|
#define ENDIAN_CONVERSION natural
|
|
#define ENDIAN_CONVERT(v) (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#define ENDIAN_CONVERSION swap
|
|
#define ENDIAN_CONVERT(v) bswap32 (v)
|
|
#include "mixeng_template.h"
|
|
#undef ENDIAN_CONVERT
|
|
#undef ENDIAN_CONVERSION
|
|
#undef IN_MAX
|
|
#undef IN_MIN
|
|
#undef BSIZE
|
|
#undef ITYPE
|
|
#undef SHIFT
|
|
|
|
t_sample *mixeng_conv[2][2][2][3] = {
|
|
{
|
|
{
|
|
{
|
|
conv_natural_uint8_t_to_mono,
|
|
conv_natural_uint16_t_to_mono,
|
|
conv_natural_uint32_t_to_mono
|
|
},
|
|
{
|
|
conv_natural_uint8_t_to_mono,
|
|
conv_swap_uint16_t_to_mono,
|
|
conv_swap_uint32_t_to_mono,
|
|
}
|
|
},
|
|
{
|
|
{
|
|
conv_natural_int8_t_to_mono,
|
|
conv_natural_int16_t_to_mono,
|
|
conv_natural_int32_t_to_mono
|
|
},
|
|
{
|
|
conv_natural_int8_t_to_mono,
|
|
conv_swap_int16_t_to_mono,
|
|
conv_swap_int32_t_to_mono
|
|
}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{
|
|
conv_natural_uint8_t_to_stereo,
|
|
conv_natural_uint16_t_to_stereo,
|
|
conv_natural_uint32_t_to_stereo
|
|
},
|
|
{
|
|
conv_natural_uint8_t_to_stereo,
|
|
conv_swap_uint16_t_to_stereo,
|
|
conv_swap_uint32_t_to_stereo
|
|
}
|
|
},
|
|
{
|
|
{
|
|
conv_natural_int8_t_to_stereo,
|
|
conv_natural_int16_t_to_stereo,
|
|
conv_natural_int32_t_to_stereo
|
|
},
|
|
{
|
|
conv_natural_int8_t_to_stereo,
|
|
conv_swap_int16_t_to_stereo,
|
|
conv_swap_int32_t_to_stereo,
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
f_sample *mixeng_clip[2][2][2][3] = {
|
|
{
|
|
{
|
|
{
|
|
clip_natural_uint8_t_from_mono,
|
|
clip_natural_uint16_t_from_mono,
|
|
clip_natural_uint32_t_from_mono
|
|
},
|
|
{
|
|
clip_natural_uint8_t_from_mono,
|
|
clip_swap_uint16_t_from_mono,
|
|
clip_swap_uint32_t_from_mono
|
|
}
|
|
},
|
|
{
|
|
{
|
|
clip_natural_int8_t_from_mono,
|
|
clip_natural_int16_t_from_mono,
|
|
clip_natural_int32_t_from_mono
|
|
},
|
|
{
|
|
clip_natural_int8_t_from_mono,
|
|
clip_swap_int16_t_from_mono,
|
|
clip_swap_int32_t_from_mono
|
|
}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{
|
|
clip_natural_uint8_t_from_stereo,
|
|
clip_natural_uint16_t_from_stereo,
|
|
clip_natural_uint32_t_from_stereo
|
|
},
|
|
{
|
|
clip_natural_uint8_t_from_stereo,
|
|
clip_swap_uint16_t_from_stereo,
|
|
clip_swap_uint32_t_from_stereo
|
|
}
|
|
},
|
|
{
|
|
{
|
|
clip_natural_int8_t_from_stereo,
|
|
clip_natural_int16_t_from_stereo,
|
|
clip_natural_int32_t_from_stereo
|
|
},
|
|
{
|
|
clip_natural_int8_t_from_stereo,
|
|
clip_swap_int16_t_from_stereo,
|
|
clip_swap_int32_t_from_stereo
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
#ifdef FLOAT_MIXENG
|
|
#define CONV_NATURAL_FLOAT(x) (x)
|
|
#define CLIP_NATURAL_FLOAT(x) (x)
|
|
#else
|
|
/* macros to map [-1.f, 1.f] <-> [INT32_MIN, INT32_MAX + 1] */
|
|
static const float float_scale = (int64_t)INT32_MAX + 1;
|
|
#define CONV_NATURAL_FLOAT(x) ((x) * float_scale)
|
|
|
|
#ifdef RECIPROCAL
|
|
static const float float_scale_reciprocal = 1.f / ((int64_t)INT32_MAX + 1);
|
|
#define CLIP_NATURAL_FLOAT(x) ((x) * float_scale_reciprocal)
|
|
#else
|
|
#define CLIP_NATURAL_FLOAT(x) ((x) / float_scale)
|
|
#endif
|
|
#endif
|
|
|
|
static void conv_natural_float_to_mono(struct st_sample *dst, const void *src,
|
|
int samples)
|
|
{
|
|
float *in = (float *)src;
|
|
|
|
while (samples--) {
|
|
dst->r = dst->l = CONV_NATURAL_FLOAT(*in++);
|
|
dst++;
|
|
}
|
|
}
|
|
|
|
static void conv_natural_float_to_stereo(struct st_sample *dst, const void *src,
|
|
int samples)
|
|
{
|
|
float *in = (float *)src;
|
|
|
|
while (samples--) {
|
|
dst->l = CONV_NATURAL_FLOAT(*in++);
|
|
dst->r = CONV_NATURAL_FLOAT(*in++);
|
|
dst++;
|
|
}
|
|
}
|
|
|
|
t_sample *mixeng_conv_float[2] = {
|
|
conv_natural_float_to_mono,
|
|
conv_natural_float_to_stereo,
|
|
};
|
|
|
|
static void clip_natural_float_from_mono(void *dst, const struct st_sample *src,
|
|
int samples)
|
|
{
|
|
float *out = (float *)dst;
|
|
|
|
while (samples--) {
|
|
*out++ = CLIP_NATURAL_FLOAT(src->l + src->r);
|
|
src++;
|
|
}
|
|
}
|
|
|
|
static void clip_natural_float_from_stereo(
|
|
void *dst, const struct st_sample *src, int samples)
|
|
{
|
|
float *out = (float *)dst;
|
|
|
|
while (samples--) {
|
|
*out++ = CLIP_NATURAL_FLOAT(src->l);
|
|
*out++ = CLIP_NATURAL_FLOAT(src->r);
|
|
src++;
|
|
}
|
|
}
|
|
|
|
f_sample *mixeng_clip_float[2] = {
|
|
clip_natural_float_from_mono,
|
|
clip_natural_float_from_stereo,
|
|
};
|
|
|
|
void audio_sample_to_uint64(const void *samples, int pos,
|
|
uint64_t *left, uint64_t *right)
|
|
{
|
|
#ifdef FLOAT_MIXENG
|
|
error_report(
|
|
"Coreaudio and floating point samples are not supported by replay yet");
|
|
abort();
|
|
#else
|
|
const struct st_sample *sample = samples;
|
|
sample += pos;
|
|
*left = sample->l;
|
|
*right = sample->r;
|
|
#endif
|
|
}
|
|
|
|
void audio_sample_from_uint64(void *samples, int pos,
|
|
uint64_t left, uint64_t right)
|
|
{
|
|
#ifdef FLOAT_MIXENG
|
|
error_report(
|
|
"Coreaudio and floating point samples are not supported by replay yet");
|
|
abort();
|
|
#else
|
|
struct st_sample *sample = samples;
|
|
sample += pos;
|
|
sample->l = left;
|
|
sample->r = right;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* August 21, 1998
|
|
* Copyright 1998 Fabrice Bellard.
|
|
*
|
|
* [Rewrote completely the code of Lance Norskog And Sundry
|
|
* Contributors with a more efficient algorithm.]
|
|
*
|
|
* This source code is freely redistributable and may be used for
|
|
* any purpose. This copyright notice must be maintained.
|
|
* Lance Norskog And Sundry Contributors are not responsible for
|
|
* the consequences of using this software.
|
|
*/
|
|
|
|
/*
|
|
* Sound Tools rate change effect file.
|
|
*/
|
|
/*
|
|
* Linear Interpolation.
|
|
*
|
|
* The use of fractional increment allows us to use no buffer. It
|
|
* avoid the problems at the end of the buffer we had with the old
|
|
* method which stored a possibly big buffer of size
|
|
* lcm(in_rate,out_rate).
|
|
*
|
|
* Limited to 16 bit samples and sampling frequency <= 65535 Hz. If
|
|
* the input & output frequencies are equal, a delay of one sample is
|
|
* introduced. Limited to processing 32-bit count worth of samples.
|
|
*
|
|
* 1 << FRAC_BITS evaluating to zero in several places. Changed with
|
|
* an (unsigned long) cast to make it safe. MarkMLl 2/1/99
|
|
*/
|
|
|
|
/* Private data */
|
|
struct rate {
|
|
uint64_t opos;
|
|
uint64_t opos_inc;
|
|
uint32_t ipos; /* position in the input stream (integer) */
|
|
struct st_sample ilast; /* last sample in the input stream */
|
|
};
|
|
|
|
/*
|
|
* Prepare processing.
|
|
*/
|
|
void *st_rate_start (int inrate, int outrate)
|
|
{
|
|
struct rate *rate = g_new0(struct rate, 1);
|
|
|
|
rate->opos = 0;
|
|
|
|
/* increment */
|
|
rate->opos_inc = ((uint64_t) inrate << 32) / outrate;
|
|
|
|
rate->ipos = 0;
|
|
rate->ilast.l = 0;
|
|
rate->ilast.r = 0;
|
|
return rate;
|
|
}
|
|
|
|
#define NAME st_rate_flow_mix
|
|
#define OP(a, b) a += b
|
|
#include "rate_template.h"
|
|
|
|
#define NAME st_rate_flow
|
|
#define OP(a, b) a = b
|
|
#include "rate_template.h"
|
|
|
|
void st_rate_stop (void *opaque)
|
|
{
|
|
g_free (opaque);
|
|
}
|
|
|
|
/**
|
|
* st_rate_frames_out() - returns the number of frames the resampling code
|
|
* generates from frames_in frames
|
|
*
|
|
* @opaque: pointer to struct rate
|
|
* @frames_in: number of frames
|
|
*
|
|
* When upsampling, there may be more than one correct result. In this case,
|
|
* the function returns the maximum number of output frames the resampling
|
|
* code can generate.
|
|
*/
|
|
uint32_t st_rate_frames_out(void *opaque, uint32_t frames_in)
|
|
{
|
|
struct rate *rate = opaque;
|
|
uint64_t opos_end, opos_delta;
|
|
uint32_t ipos_end;
|
|
uint32_t frames_out;
|
|
|
|
if (rate->opos_inc == 1ULL << 32) {
|
|
return frames_in;
|
|
}
|
|
|
|
/* no output frame without at least one input frame */
|
|
if (!frames_in) {
|
|
return 0;
|
|
}
|
|
|
|
/* last frame read was at rate->ipos - 1 */
|
|
ipos_end = rate->ipos - 1 + frames_in;
|
|
opos_end = (uint64_t)ipos_end << 32;
|
|
|
|
/* last frame written was at rate->opos - rate->opos_inc */
|
|
if (opos_end + rate->opos_inc <= rate->opos) {
|
|
return 0;
|
|
}
|
|
opos_delta = opos_end - rate->opos + rate->opos_inc;
|
|
frames_out = opos_delta / rate->opos_inc;
|
|
|
|
return opos_delta % rate->opos_inc ? frames_out : frames_out - 1;
|
|
}
|
|
|
|
/**
|
|
* st_rate_frames_in() - returns the number of frames needed to
|
|
* get frames_out frames after resampling
|
|
*
|
|
* @opaque: pointer to struct rate
|
|
* @frames_out: number of frames
|
|
*
|
|
* When downsampling, there may be more than one correct result. In this
|
|
* case, the function returns the maximum number of input frames needed.
|
|
*/
|
|
uint32_t st_rate_frames_in(void *opaque, uint32_t frames_out)
|
|
{
|
|
struct rate *rate = opaque;
|
|
uint64_t opos_start, opos_end;
|
|
uint32_t ipos_start, ipos_end;
|
|
|
|
if (rate->opos_inc == 1ULL << 32) {
|
|
return frames_out;
|
|
}
|
|
|
|
if (frames_out) {
|
|
opos_start = rate->opos;
|
|
ipos_start = rate->ipos;
|
|
} else {
|
|
uint64_t offset;
|
|
|
|
/* add offset = ceil(opos_inc) to opos and ipos to avoid an underflow */
|
|
offset = (rate->opos_inc + (1ULL << 32) - 1) & ~((1ULL << 32) - 1);
|
|
opos_start = rate->opos + offset;
|
|
ipos_start = rate->ipos + (offset >> 32);
|
|
}
|
|
/* last frame written was at opos_start - rate->opos_inc */
|
|
opos_end = opos_start - rate->opos_inc + rate->opos_inc * frames_out;
|
|
ipos_end = (opos_end >> 32) + 1;
|
|
|
|
/* last frame read was at ipos_start - 1 */
|
|
return ipos_end + 1 > ipos_start ? ipos_end + 1 - ipos_start : 0;
|
|
}
|
|
|
|
void mixeng_clear (struct st_sample *buf, int len)
|
|
{
|
|
memset (buf, 0, len * sizeof (struct st_sample));
|
|
}
|
|
|
|
void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol)
|
|
{
|
|
if (vol->mute) {
|
|
mixeng_clear (buf, len);
|
|
return;
|
|
}
|
|
|
|
while (len--) {
|
|
#ifdef FLOAT_MIXENG
|
|
buf->l = buf->l * vol->l;
|
|
buf->r = buf->r * vol->r;
|
|
#else
|
|
buf->l = (buf->l * vol->l) >> 32;
|
|
buf->r = (buf->r * vol->r) >> 32;
|
|
#endif
|
|
buf += 1;
|
|
}
|
|
}
|