/* rdesktop: A Remote Desktop Protocol client. Sound DSP routines Copyright (C) Michael Gernoth 2006-2007 This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * Oracle GPL Disclaimer: For the avoidance of doubt, except that if any license choice * other than GPL or LGPL is available it will apply instead, Oracle elects to use only * the General Public License version 2 (GPLv2) at this time for any software where * a choice of GPL license versions is made available with the language indicating * that GPLv2 or any later version may be used, or where a choice of which version * of the GPL is applied is otherwise unspecified. */ #include #include "rdesktop.h" #include "rdpsnd.h" #include "rdpsnd_dsp.h" #ifdef HAVE_LIBSAMPLERATE #include #define SRC_CONVERTER SRC_SINC_MEDIUM_QUALITY #endif #define MAX_VOLUME 65535 static uint16 softvol_left = MAX_VOLUME; static uint16 softvol_right = MAX_VOLUME; static uint32 resample_to_srate = 44100; static uint16 resample_to_bitspersample = 16; static uint16 resample_to_channels = 2; #ifdef HAVE_LIBSAMPLERATE static SRC_STATE *src_converter = NULL; #endif void rdpsnd_dsp_softvol_set(uint16 left, uint16 right) { softvol_left = left; softvol_right = right; DEBUG(("rdpsnd_dsp_softvol_set: left: %u, right: %u\n", left, right)); } void rdpsnd_dsp_softvol(unsigned char *buffer, unsigned int size, RD_WAVEFORMATEX * format) { unsigned int factor_left, factor_right; unsigned char *posin = buffer; unsigned char *posout = buffer; if ((softvol_left == MAX_VOLUME) && (softvol_right == MAX_VOLUME)) return; factor_left = (softvol_left * 256) / MAX_VOLUME; factor_right = (softvol_right * 256) / MAX_VOLUME; if (format->nChannels == 1) { factor_left = factor_right = (factor_left + factor_right) / 2; } if (format->wBitsPerSample == 8) { sint8 val; while (posout < buffer + size) { /* Left */ val = *posin++; val = (val * factor_left) >> 8; *posout++ = val; /* Right */ val = *posin++; val = (val * factor_right) >> 8; *posout++ = val; } } else { sint16 val; while (posout < buffer + size) { /* Left */ val = *posin++; val |= *posin++ << 8; val = (val * factor_left) >> 8; *posout++ = val & 0xff; *posout++ = val >> 8; /* Right */ val = *posin++; val |= *posin++ << 8; val = (val * factor_right) >> 8; *posout++ = val & 0xff; *posout++ = val >> 8; } } DEBUG(("using softvol with factors left: %d, right: %d (%d/%d)\n", factor_left, factor_right, format->wBitsPerSample, format->nChannels)); } void rdpsnd_dsp_swapbytes(unsigned char *buffer, unsigned int size, RD_WAVEFORMATEX * format) { int i; uint8 swap; if (format->wBitsPerSample == 8) return; if (size & 0x1) warning("badly aligned sound data"); for (i = 0; i < (int) size; i += 2) { swap = *(buffer + i); *(buffer + i) = *(buffer + i + 1); *(buffer + i + 1) = swap; } } RD_BOOL rdpsnd_dsp_resample_set(uint32 device_srate, uint16 device_bitspersample, uint16 device_channels) { #ifdef HAVE_LIBSAMPLERATE int err; #endif if (device_bitspersample != 16 && device_bitspersample != 8) return False; if (device_channels != 1 && device_channels != 2) return False; resample_to_srate = device_srate; resample_to_bitspersample = device_bitspersample; resample_to_channels = device_channels; #ifdef HAVE_LIBSAMPLERATE if (src_converter != NULL) src_converter = src_delete(src_converter); if ((src_converter = src_new(SRC_CONVERTER, device_channels, &err)) == NULL) { warning("src_new failed: %d!\n", err); return False; } #endif return True; } RD_BOOL rdpsnd_dsp_resample_supported(RD_WAVEFORMATEX * format) { if (format->wFormatTag != WAVE_FORMAT_PCM) return False; if ((format->nChannels != 1) && (format->nChannels != 2)) return False; if ((format->wBitsPerSample != 8) && (format->wBitsPerSample != 16)) return False; return True; } uint32 rdpsnd_dsp_resample(unsigned char **out, unsigned char *in, unsigned int size, RD_WAVEFORMATEX * format, RD_BOOL stream_be) { #ifdef HAVE_LIBSAMPLERATE SRC_DATA resample_data; float *infloat, *outfloat; int err; #else int ratio1k = (resample_to_srate * 1000) / format->nSamplesPerSec; #endif int innum, outnum; unsigned char *tmpdata = NULL, *tmp = NULL; int samplewidth = format->wBitsPerSample / 8; int outsize = 0; int i; if ((resample_to_bitspersample == format->wBitsPerSample) && (resample_to_channels == format->nChannels) && (resample_to_srate == format->nSamplesPerSec)) return 0; #ifdef B_ENDIAN if (!stream_be) rdpsnd_dsp_swapbytes(in, size, format); #endif if (resample_to_channels != format->nChannels) { int newsize = (size / format->nChannels) * resample_to_channels; tmpdata = (unsigned char *) xmalloc(newsize); for (i = 0; i < newsize / samplewidth; i++) { if (format->nChannels > resample_to_channels) memcpy(tmpdata + (i * samplewidth), in + (((i * format->nChannels) / resample_to_channels) * samplewidth), samplewidth); else memcpy(tmpdata + (i * samplewidth), in + (((i / resample_to_channels) * format->nChannels + (i % format->nChannels)) * samplewidth), samplewidth); } in = tmpdata; size = newsize; } /* Expand 8bit input-samples to 16bit */ #ifndef HAVE_LIBSAMPLERATE /* libsamplerate needs 16bit samples */ if (format->wBitsPerSample != resample_to_bitspersample) #endif { /* source: 8 bit, dest: 16bit */ if (format->wBitsPerSample == 8) { tmp = tmpdata; tmpdata = (unsigned char *) xmalloc(size * 2); for (i = 0; i < (int) size; i++) { tmpdata[i * 2] = in[i]; tmpdata[(i * 2) + 1] = 0x00; } in = tmpdata; samplewidth = 16 / 2; size *= 2; if (tmp != NULL) xfree(tmp); } } innum = size / samplewidth; /* Do the resampling */ #ifdef HAVE_LIBSAMPLERATE if (src_converter == NULL) { warning("no samplerate converter available!!\n"); return 0; } outnum = ((float) innum * ((float) resample_to_srate / (float) format->nSamplesPerSec)) + 1; infloat = (float *) xmalloc(sizeof(float) * innum); outfloat = (float *) xmalloc(sizeof(float) * outnum); src_short_to_float_array((short *) in, infloat, innum); bzero(&resample_data, sizeof(resample_data)); resample_data.data_in = infloat; resample_data.data_out = outfloat; resample_data.input_frames = innum / resample_to_channels; resample_data.output_frames = outnum / resample_to_channels; resample_data.src_ratio = (double) resample_to_srate / (double) format->nSamplesPerSec; resample_data.end_of_input = 0; if ((err = src_process(src_converter, &resample_data)) != 0) error("src_process: %s", src_strerror(err)); xfree(infloat); outsize = resample_data.output_frames_gen * resample_to_channels * samplewidth; *out = (unsigned char *) xmalloc(outsize); src_float_to_short_array(outfloat, (short *) *out, resample_data.output_frames_gen * resample_to_channels); xfree(outfloat); #else /* Michaels simple linear resampler */ if (resample_to_srate < format->nSamplesPerSec) { warning("downsampling currently not supported!\n"); return 0; } outnum = (innum * ratio1k) / 1000; outsize = outnum * samplewidth; *out = (unsigned char *) xmalloc(outsize); bzero(*out, outsize); for (i = 0; i < outsize / (resample_to_channels * samplewidth); i++) { int source = (i * 1000) / ratio1k; #if 0 /* Partial for linear resampler */ int part = (i * 100000) / ratio1k - source * 100; #endif int j; if (source * resample_to_channels + samplewidth > (int) size) break; #if 0 /* Linear resampling, TODO: soundquality fixes (LP filter) */ if (samplewidth == 1) { sint8 cval1, cval2; for (j = 0; j < resample_to_channels; j++) { memcpy(&cval1, in + (source * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); memcpy(&cval2, in + ((source + 1) * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); cval1 += (sint8) (cval2 * part) / 100; memcpy(*out + (i * resample_to_channels * samplewidth) + (samplewidth * j), &cval1, samplewidth); } } else { sint16 sval1, sval2; for (j = 0; j < resample_to_channels; j++) { memcpy(&sval1, in + (source * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); memcpy(&sval2, in + ((source + 1) * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); sval1 += (sint16) (sval2 * part) / 100; memcpy(*out + (i * resample_to_channels * samplewidth) + (samplewidth * j), &sval1, samplewidth); } } #else /* Nearest neighbor search */ for (j = 0; j < resample_to_channels; j++) { memcpy(*out + (i * resample_to_channels * samplewidth) + (samplewidth * j), in + (source * resample_to_channels * samplewidth) + (samplewidth * j), samplewidth); } #endif } outsize = i * resample_to_channels * samplewidth; #endif if (tmpdata != NULL) xfree(tmpdata); /* Shrink 16bit output-samples to 8bit */ #ifndef HAVE_LIBSAMPLERATE /* libsamplerate produces 16bit samples */ if (format->wBitsPerSample != resample_to_bitspersample) #endif { /* source: 16 bit, dest: 8 bit */ if (resample_to_bitspersample == 8) { for (i = 0; i < outsize; i++) { *out[i] = *out[i * 2]; } outsize /= 2; } } #ifdef B_ENDIAN if (!stream_be) rdpsnd_dsp_swapbytes(*out, outsize, format); #endif return outsize; } STREAM rdpsnd_dsp_process(unsigned char *data, unsigned int size, struct audio_driver * current_driver, RD_WAVEFORMATEX * format) { static struct stream out; RD_BOOL stream_be = False; /* softvol and byteswap do not change the amount of data they return, so they can operate on the input-stream */ if (current_driver->wave_out_volume == rdpsnd_dsp_softvol_set) rdpsnd_dsp_softvol(data, size, format); #ifdef B_ENDIAN if (current_driver->need_byteswap_on_be) { rdpsnd_dsp_swapbytes(data, size, format); stream_be = True; } #endif out.data = NULL; if (current_driver->need_resampling) out.size = rdpsnd_dsp_resample(&out.data, data, size, format, stream_be); if (out.data == NULL) { out.data = (unsigned char *) xmalloc(size); memcpy(out.data, data, size); out.size = size; } out.p = out.data; out.end = out.p + out.size; return &out; }