linux/sound/core/oss/pcm_plugin.c
Takashi Iwai efb6402c3c ALSA: oss: Fix PCM OSS buffer allocation overflow
We've got syzbot reports hitting INT_MAX overflow at vmalloc()
allocation that is called from snd_pcm_plug_alloc().  Although we
apply the restrictions to input parameters, it's based only on the
hw_params of the underlying PCM device.  Since the PCM OSS layer
allocates a temporary buffer for the data conversion, the size may
become unexpectedly large when more channels or higher rates is given;
in the reported case, it went over INT_MAX, hence it hits WARN_ON().

This patch is an attempt to avoid such an overflow and an allocation
for too large buffers.  First off, it adds the limit of 1MB as the
upper bound for period bytes.  This must be large enough for all use
cases, and we really don't want to handle a larger temporary buffer
than this size.  The size check is performed at two places, where the
original period bytes is calculated and where the plugin buffer size
is calculated.

In addition, the driver uses array_size() and array3_size() for
multiplications to catch overflows for the converted period size and
buffer bytes.

Reported-by: syzbot+72732c532ac1454eeee9@syzkaller.appspotmail.com
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/00000000000085b1b305da5a66f3@google.com
Link: https://lore.kernel.org/r/20220318082036.29699-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2022-03-18 14:01:07 +01:00

782 lines
22 KiB
C

/*
* PCM Plug-In shared (kernel/library) code
* Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* 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 program 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
*
*/
#if 0
#define PLUGIN_DEBUG
#endif
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "pcm_plugin.h"
#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first)
#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last)
/*
* because some cards might have rates "very close", we ignore
* all "resampling" requests within +-5%
*/
static int rate_match(unsigned int src_rate, unsigned int dst_rate)
{
unsigned int low = (src_rate * 95) / 100;
unsigned int high = (src_rate * 105) / 100;
return dst_rate >= low && dst_rate <= high;
}
static int snd_pcm_plugin_alloc(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
{
struct snd_pcm_plugin_format *format;
ssize_t width;
size_t size;
unsigned int channel;
struct snd_pcm_plugin_channel *c;
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) {
format = &plugin->src_format;
} else {
format = &plugin->dst_format;
}
width = snd_pcm_format_physical_width(format->format);
if (width < 0)
return width;
size = array3_size(frames, format->channels, width);
/* check for too large period size once again */
if (size > 1024 * 1024)
return -ENOMEM;
if (snd_BUG_ON(size % 8))
return -ENXIO;
size /= 8;
if (plugin->buf_frames < frames) {
kvfree(plugin->buf);
plugin->buf = kvzalloc(size, GFP_KERNEL);
plugin->buf_frames = frames;
}
if (!plugin->buf) {
plugin->buf_frames = 0;
return -ENOMEM;
}
c = plugin->buf_channels;
if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf;
c->area.first = channel * width;
c->area.step = format->channels * width;
}
} else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
if (snd_BUG_ON(size % format->channels))
return -EINVAL;
size /= format->channels;
for (channel = 0; channel < format->channels; channel++, c++) {
c->frames = frames;
c->enabled = 1;
c->wanted = 0;
c->area.addr = plugin->buf + (channel * size);
c->area.first = 0;
c->area.step = width;
}
} else
return -EINVAL;
return 0;
}
int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames)
{
int err;
if (snd_BUG_ON(!snd_pcm_plug_first(plug)))
return -ENXIO;
if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug);
while (plugin->next) {
if (plugin->dst_frames)
frames = plugin->dst_frames(plugin, frames);
if ((snd_pcm_sframes_t)frames <= 0)
return -ENXIO;
plugin = plugin->next;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
} else {
struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug);
while (plugin->prev) {
if (plugin->src_frames)
frames = plugin->src_frames(plugin, frames);
if ((snd_pcm_sframes_t)frames <= 0)
return -ENXIO;
plugin = plugin->prev;
err = snd_pcm_plugin_alloc(plugin, frames);
if (err < 0)
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
snd_pcm_uframes_t frames,
struct snd_pcm_plugin_channel **channels)
{
*channels = plugin->buf_channels;
return frames;
}
int snd_pcm_plugin_build(struct snd_pcm_substream *plug,
const char *name,
struct snd_pcm_plugin_format *src_format,
struct snd_pcm_plugin_format *dst_format,
size_t extra,
struct snd_pcm_plugin **ret)
{
struct snd_pcm_plugin *plugin;
unsigned int channels;
if (snd_BUG_ON(!plug))
return -ENXIO;
if (snd_BUG_ON(!src_format || !dst_format))
return -ENXIO;
plugin = kzalloc(sizeof(*plugin) + extra, GFP_KERNEL);
if (plugin == NULL)
return -ENOMEM;
plugin->name = name;
plugin->plug = plug;
plugin->stream = snd_pcm_plug_stream(plug);
plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
plugin->src_format = *src_format;
plugin->src_width = snd_pcm_format_physical_width(src_format->format);
snd_BUG_ON(plugin->src_width <= 0);
plugin->dst_format = *dst_format;
plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
snd_BUG_ON(plugin->dst_width <= 0);
if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK)
channels = src_format->channels;
else
channels = dst_format->channels;
plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL);
if (plugin->buf_channels == NULL) {
snd_pcm_plugin_free(plugin);
return -ENOMEM;
}
plugin->client_channels = snd_pcm_plugin_client_channels;
*ret = plugin;
return 0;
}
int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin)
{
if (! plugin)
return 0;
if (plugin->private_free)
plugin->private_free(plugin);
kfree(plugin->buf_channels);
kvfree(plugin->buf);
kfree(plugin);
return 0;
}
static snd_pcm_sframes_t calc_dst_frames(struct snd_pcm_substream *plug,
snd_pcm_sframes_t frames,
bool check_size)
{
struct snd_pcm_plugin *plugin, *plugin_next;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
plugin_next = plugin->next;
if (check_size && plugin->buf_frames &&
frames > plugin->buf_frames)
frames = plugin->buf_frames;
if (plugin->dst_frames) {
frames = plugin->dst_frames(plugin, frames);
if (frames < 0)
return frames;
}
plugin = plugin_next;
}
return frames;
}
static snd_pcm_sframes_t calc_src_frames(struct snd_pcm_substream *plug,
snd_pcm_sframes_t frames,
bool check_size)
{
struct snd_pcm_plugin *plugin, *plugin_prev;
plugin = snd_pcm_plug_last(plug);
while (plugin && frames > 0) {
plugin_prev = plugin->prev;
if (plugin->src_frames) {
frames = plugin->src_frames(plugin, frames);
if (frames < 0)
return frames;
}
if (check_size && plugin->buf_frames &&
frames > plugin->buf_frames)
frames = plugin->buf_frames;
plugin = plugin_prev;
}
return frames;
}
snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t drv_frames)
{
if (snd_BUG_ON(!plug))
return -ENXIO;
switch (snd_pcm_plug_stream(plug)) {
case SNDRV_PCM_STREAM_PLAYBACK:
return calc_src_frames(plug, drv_frames, false);
case SNDRV_PCM_STREAM_CAPTURE:
return calc_dst_frames(plug, drv_frames, false);
default:
snd_BUG();
return -EINVAL;
}
}
snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t clt_frames)
{
if (snd_BUG_ON(!plug))
return -ENXIO;
switch (snd_pcm_plug_stream(plug)) {
case SNDRV_PCM_STREAM_PLAYBACK:
return calc_dst_frames(plug, clt_frames, false);
case SNDRV_PCM_STREAM_CAPTURE:
return calc_src_frames(plug, clt_frames, false);
default:
snd_BUG();
return -EINVAL;
}
}
static int snd_pcm_plug_formats(const struct snd_mask *mask,
snd_pcm_format_t format)
{
struct snd_mask formats = *mask;
u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE);
snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW);
if (formats.bits[0] & lower_32_bits(linfmts))
formats.bits[0] |= lower_32_bits(linfmts);
if (formats.bits[1] & upper_32_bits(linfmts))
formats.bits[1] |= upper_32_bits(linfmts);
return snd_mask_test(&formats, (__force int)format);
}
static const snd_pcm_format_t preferred_formats[] = {
SNDRV_PCM_FORMAT_S16_LE,
SNDRV_PCM_FORMAT_S16_BE,
SNDRV_PCM_FORMAT_U16_LE,
SNDRV_PCM_FORMAT_U16_BE,
SNDRV_PCM_FORMAT_S24_3LE,
SNDRV_PCM_FORMAT_S24_3BE,
SNDRV_PCM_FORMAT_U24_3LE,
SNDRV_PCM_FORMAT_U24_3BE,
SNDRV_PCM_FORMAT_S24_LE,
SNDRV_PCM_FORMAT_S24_BE,
SNDRV_PCM_FORMAT_U24_LE,
SNDRV_PCM_FORMAT_U24_BE,
SNDRV_PCM_FORMAT_S32_LE,
SNDRV_PCM_FORMAT_S32_BE,
SNDRV_PCM_FORMAT_U32_LE,
SNDRV_PCM_FORMAT_U32_BE,
SNDRV_PCM_FORMAT_S8,
SNDRV_PCM_FORMAT_U8
};
snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format,
const struct snd_mask *format_mask)
{
int i;
if (snd_mask_test(format_mask, (__force int)format))
return format;
if (!snd_pcm_plug_formats(format_mask, format))
return (__force snd_pcm_format_t)-EINVAL;
if (snd_pcm_format_linear(format)) {
unsigned int width = snd_pcm_format_width(format);
int unsignd = snd_pcm_format_unsigned(format) > 0;
int big = snd_pcm_format_big_endian(format) > 0;
unsigned int badness, best = -1;
snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1;
for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) {
snd_pcm_format_t f = preferred_formats[i];
unsigned int w;
if (!snd_mask_test(format_mask, (__force int)f))
continue;
w = snd_pcm_format_width(f);
if (w >= width)
badness = w - width;
else
badness = width - w + 32;
badness += snd_pcm_format_unsigned(f) != unsignd;
badness += snd_pcm_format_big_endian(f) != big;
if (badness < best) {
best_format = f;
best = badness;
}
}
if ((__force int)best_format >= 0)
return best_format;
else
return (__force snd_pcm_format_t)-EINVAL;
} else {
switch (format) {
case SNDRV_PCM_FORMAT_MU_LAW:
for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) {
snd_pcm_format_t format1 = preferred_formats[i];
if (snd_mask_test(format_mask, (__force int)format1))
return format1;
}
fallthrough;
default:
return (__force snd_pcm_format_t)-EINVAL;
}
}
}
int snd_pcm_plug_format_plugins(struct snd_pcm_substream *plug,
struct snd_pcm_hw_params *params,
struct snd_pcm_hw_params *slave_params)
{
struct snd_pcm_plugin_format tmpformat;
struct snd_pcm_plugin_format dstformat;
struct snd_pcm_plugin_format srcformat;
snd_pcm_access_t src_access, dst_access;
struct snd_pcm_plugin *plugin = NULL;
int err;
int stream = snd_pcm_plug_stream(plug);
int slave_interleaved = (params_channels(slave_params) == 1 ||
params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED);
switch (stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
dstformat.format = params_format(slave_params);
dstformat.rate = params_rate(slave_params);
dstformat.channels = params_channels(slave_params);
srcformat.format = params_format(params);
srcformat.rate = params_rate(params);
srcformat.channels = params_channels(params);
src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
break;
case SNDRV_PCM_STREAM_CAPTURE:
dstformat.format = params_format(params);
dstformat.rate = params_rate(params);
dstformat.channels = params_channels(params);
srcformat.format = params_format(slave_params);
srcformat.rate = params_rate(slave_params);
srcformat.channels = params_channels(slave_params);
src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED :
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
break;
default:
snd_BUG();
return -EINVAL;
}
tmpformat = srcformat;
pdprintf("srcformat: format=%i, rate=%i, channels=%i\n",
srcformat.format,
srcformat.rate,
srcformat.channels);
pdprintf("dstformat: format=%i, rate=%i, channels=%i\n",
dstformat.format,
dstformat.rate,
dstformat.channels);
/* Format change (linearization) */
if (! rate_match(srcformat.rate, dstformat.rate) &&
! snd_pcm_format_linear(srcformat.format)) {
if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW)
return -EINVAL;
tmpformat.format = SNDRV_PCM_FORMAT_S16;
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels reduction */
if (srcformat.channels > dstformat.channels) {
tmpformat.channels = dstformat.channels;
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* rate resampling */
if (!rate_match(srcformat.rate, dstformat.rate)) {
if (srcformat.format != SNDRV_PCM_FORMAT_S16) {
/* convert to S16 for resampling */
tmpformat.format = SNDRV_PCM_FORMAT_S16;
err = snd_pcm_plugin_build_linear(plug,
&srcformat, &tmpformat,
&plugin);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
tmpformat.rate = dstformat.rate;
err = snd_pcm_plugin_build_rate(plug,
&srcformat, &tmpformat,
&plugin);
pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* format change */
if (srcformat.format != dstformat.format) {
tmpformat.format = dstformat.format;
if (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW ||
tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) {
err = snd_pcm_plugin_build_mulaw(plug,
&srcformat, &tmpformat,
&plugin);
}
else if (snd_pcm_format_linear(srcformat.format) &&
snd_pcm_format_linear(tmpformat.format)) {
err = snd_pcm_plugin_build_linear(plug,
&srcformat, &tmpformat,
&plugin);
}
else
return -EINVAL;
pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* channels extension */
if (srcformat.channels < dstformat.channels) {
tmpformat.channels = dstformat.channels;
err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
srcformat = tmpformat;
src_access = dst_access;
}
/* de-interleave */
if (src_access != dst_access) {
err = snd_pcm_plugin_build_copy(plug,
&srcformat,
&tmpformat,
&plugin);
pdprintf("interleave change (copy: returns %i)\n", err);
if (err < 0)
return err;
err = snd_pcm_plugin_append(plugin);
if (err < 0) {
snd_pcm_plugin_free(plugin);
return err;
}
}
return 0;
}
snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *plug,
char *buf,
snd_pcm_uframes_t count,
struct snd_pcm_plugin_channel **channels)
{
struct snd_pcm_plugin *plugin;
struct snd_pcm_plugin_channel *v;
struct snd_pcm_plugin_format *format;
int width, nchannels, channel;
int stream = snd_pcm_plug_stream(plug);
if (snd_BUG_ON(!buf))
return -ENXIO;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
plugin = snd_pcm_plug_first(plug);
format = &plugin->src_format;
} else {
plugin = snd_pcm_plug_last(plug);
format = &plugin->dst_format;
}
v = plugin->buf_channels;
*channels = v;
width = snd_pcm_format_physical_width(format->format);
if (width < 0)
return width;
nchannels = format->channels;
if (snd_BUG_ON(plugin->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
format->channels > 1))
return -ENXIO;
for (channel = 0; channel < nchannels; channel++, v++) {
v->frames = count;
v->enabled = 1;
v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE);
v->area.addr = buf;
v->area.first = channel * width;
v->area.step = nchannels * width;
}
return count;
}
snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size)
{
struct snd_pcm_plugin *plugin, *next;
struct snd_pcm_plugin_channel *dst_channels;
int err;
snd_pcm_sframes_t frames = size;
plugin = snd_pcm_plug_first(plug);
while (plugin) {
if (frames <= 0)
return frames;
next = plugin->next;
if (next) {
snd_pcm_sframes_t frames1 = frames;
if (plugin->dst_frames) {
frames1 = plugin->dst_frames(plugin, frames);
if (frames1 <= 0)
return frames1;
}
err = next->client_channels(next, frames1, &dst_channels);
if (err < 0)
return err;
if (err != frames1) {
frames = err;
if (plugin->src_frames) {
frames = plugin->src_frames(plugin, frames1);
if (frames <= 0)
return frames;
}
}
} else
dst_channels = NULL;
pdprintf("write plugin: %s, %li\n", plugin->name, frames);
frames = plugin->transfer(plugin, src_channels, dst_channels, frames);
if (frames < 0)
return frames;
src_channels = dst_channels;
plugin = next;
}
return calc_src_frames(plug, frames, true);
}
snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size)
{
struct snd_pcm_plugin *plugin, *next;
struct snd_pcm_plugin_channel *src_channels, *dst_channels;
snd_pcm_sframes_t frames = size;
int err;
frames = calc_src_frames(plug, frames, true);
if (frames < 0)
return frames;
src_channels = NULL;
plugin = snd_pcm_plug_first(plug);
while (plugin && frames > 0) {
next = plugin->next;
if (next) {
err = plugin->client_channels(plugin, frames, &dst_channels);
if (err < 0)
return err;
frames = err;
} else {
dst_channels = dst_channels_final;
}
pdprintf("read plugin: %s, %li\n", plugin->name, frames);
frames = plugin->transfer(plugin, src_channels, dst_channels, frames);
if (frames < 0)
return frames;
plugin = next;
src_channels = dst_channels;
}
return frames;
}
int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
size_t samples, snd_pcm_format_t format)
{
/* FIXME: sub byte resolution and odd dst_offset */
unsigned char *dst;
unsigned int dst_step;
int width;
const unsigned char *silence;
if (!dst_area->addr)
return 0;
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (dst_area->step == (unsigned int) width && width >= 8)
return snd_pcm_format_set_silence(format, dst, samples);
silence = snd_pcm_format_silence_64(format);
if (! silence)
return -EINVAL;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
if (dstbit)
*dst &= 0xf0;
else
*dst &= 0x0f;
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, silence, width);
dst += dst_step;
}
}
return 0;
}
int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_area, size_t src_offset,
const struct snd_pcm_channel_area *dst_area, size_t dst_offset,
size_t samples, snd_pcm_format_t format)
{
/* FIXME: sub byte resolution and odd dst_offset */
char *src, *dst;
int width;
int src_step, dst_step;
src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8;
if (!src_area->addr)
return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8;
if (!dst_area->addr)
return 0;
width = snd_pcm_format_physical_width(format);
if (width <= 0)
return -EINVAL;
if (src_area->step == (unsigned int) width &&
dst_area->step == (unsigned int) width && width >= 8) {
size_t bytes = samples * width / 8;
memcpy(dst, src, bytes);
return 0;
}
src_step = src_area->step / 8;
dst_step = dst_area->step / 8;
if (width == 4) {
/* Ima ADPCM */
int srcbit = src_area->first % 8;
int srcbit_step = src_area->step % 8;
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
unsigned char srcval;
if (srcbit)
srcval = *src & 0x0f;
else
srcval = (*src & 0xf0) >> 4;
if (dstbit)
*dst = (*dst & 0xf0) | srcval;
else
*dst = (*dst & 0x0f) | (srcval << 4);
src += src_step;
srcbit += srcbit_step;
if (srcbit == 8) {
src++;
srcbit = 0;
}
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
} else {
width /= 8;
while (samples-- > 0) {
memcpy(dst, src, width);
src += src_step;
dst += dst_step;
}
}
return 0;
}