rdesktop/rdpsnd_alsa.c
Pierre Ossman 25b8412333 Avoid poking around in STREAM internals
It's easy to make mistakes this way, and bypassed the normal bounds
checking. So make sure we always use macros or functions.
2019-05-06 14:33:38 +02:00

539 lines
12 KiB
C

/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Sound Channel Process Functions - alsa-driver
Copyright (C) Matthew Chapman <matthewc.unsw.edu.au> 2003-2008
Copyright (C) GuoJunBo <guojunbo@ict.ac.cn> 2003
Copyright (C) Michael Gernoth <mike@zerfleddert.de> 2006-2008
Copyright 2006-2008 Pierre Ossman <ossman@cendio.se> for Cendio AB
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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "rdesktop.h"
#include "rdpsnd.h"
#include "rdpsnd_dsp.h"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <alsa/asoundlib.h>
#include <sys/time.h>
#define DEFAULTDEVICE "default"
#define MAX_FRAMES 32
static struct pollfd pfds_out[32];
static size_t num_fds_out;
static struct pollfd pfds_in[32];
static size_t num_fds_in;
static snd_pcm_t *out_handle = NULL;
static snd_pcm_t *in_handle = NULL;
static RD_BOOL reopened;
static short samplewidth_out;
static int audiochannels_out;
static unsigned int rate_out;
static short samplewidth_in;
static int audiochannels_in;
static unsigned int rate_in;
static char *pcm_name;
void alsa_play(void);
void alsa_record(void);
void
alsa_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv)
{
UNUSED(tv);
int err;
struct pollfd *f;
if (out_handle && !rdpsnd_queue_empty())
{
num_fds_out = snd_pcm_poll_descriptors_count(out_handle);
if (num_fds_out > sizeof(pfds_out) / sizeof(*pfds_out))
return;
err = snd_pcm_poll_descriptors(out_handle, pfds_out, num_fds_out);
if (err < 0)
return;
for (f = pfds_out; f < &pfds_out[num_fds_out]; f++)
{
if (f->events & POLLIN)
FD_SET(f->fd, rfds);
if (f->events & POLLOUT)
FD_SET(f->fd, wfds);
if (f->fd > *n && (f->events & (POLLIN | POLLOUT)))
*n = f->fd;
}
}
if (in_handle)
{
num_fds_in = snd_pcm_poll_descriptors_count(in_handle);
if (num_fds_in > sizeof(pfds_in) / sizeof(*pfds_in))
return;
err = snd_pcm_poll_descriptors(in_handle, pfds_in, num_fds_in);
if (err < 0)
return;
for (f = pfds_in; f < &pfds_in[num_fds_in]; f++)
{
if (f->events & POLLIN)
FD_SET(f->fd, rfds);
if (f->events & POLLOUT)
FD_SET(f->fd, wfds);
if (f->fd > *n && (f->events & (POLLIN | POLLOUT)))
*n = f->fd;
}
}
}
void
alsa_check_fds(fd_set * rfds, fd_set * wfds)
{
struct pollfd *f;
int err;
unsigned short revents;
if (out_handle && !rdpsnd_queue_empty())
{
for (f = pfds_out; f < &pfds_out[num_fds_out]; f++)
{
f->revents = 0;
if (f->fd != -1)
{
/* Fixme: This doesn't properly deal with things like POLLHUP */
if (FD_ISSET(f->fd, rfds))
f->revents |= POLLIN;
if (FD_ISSET(f->fd, wfds))
f->revents |= POLLOUT;
}
}
err = snd_pcm_poll_descriptors_revents(out_handle, pfds_out, num_fds_out, &revents);
if (err < 0)
return;
if (revents & POLLOUT)
alsa_play();
}
if (in_handle)
{
for (f = pfds_in; f < &pfds_in[num_fds_in]; f++)
{
f->revents = 0;
if (f->fd != -1)
{
/* Fixme: This doesn't properly deal with things like POLLHUP */
if (FD_ISSET(f->fd, rfds))
f->revents |= POLLIN;
if (FD_ISSET(f->fd, wfds))
f->revents |= POLLOUT;
}
}
err = snd_pcm_poll_descriptors_revents(in_handle, pfds_in, num_fds_in, &revents);
if (err < 0)
return;
if (revents & POLLIN)
alsa_record();
}
}
static RD_BOOL
alsa_set_format(snd_pcm_t * pcm, RD_WAVEFORMATEX * pwfx)
{
snd_pcm_hw_params_t *hwparams = NULL;
int err;
unsigned int buffertime;
unsigned int rate;
if ((err = snd_pcm_hw_params_malloc(&hwparams)) < 0)
{
logger(Sound, Error, "alsa_set_format(), snd_pcm_hw_params_malloc() failed: %s",
snd_strerror(err));
return False;
}
if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
{
logger(Sound, Error, "alsa_set_format(), snd_pcm_hw_params_any() failed: %s",
snd_strerror(err));
return False;
}
if ((err = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
logger(Sound, Error, "alsa_set_format(), snd_pcm_hw_params_set_access() failed: %s",
snd_strerror(err));
return False;
}
if (pwfx->wBitsPerSample == 16)
{
if ((err = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_format() failed: %s",
snd_strerror(err));
return False;
}
}
else
{
if ((err = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S8)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_format() failed: %s",
snd_strerror(err));
return False;
}
}
#if 0
if ((err = snd_pcm_hw_params_set_rate_resample(pcm, hwparams, 1)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_rate_resample() failed: %s",
snd_strerror(err));
return False;
}
#endif
rate = pwfx->nSamplesPerSec;
if ((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_rate_near() failed: %s",
snd_strerror(err));
return False;
}
if ((err = snd_pcm_hw_params_set_channels(pcm, hwparams, pwfx->nChannels)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_channels() failed: %s",
snd_strerror(err));
return False;
}
buffertime = 500000; /* microseconds */
if ((err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams, &buffertime, 0)) < 0)
{
logger(Sound, Error,
"alsa_set_format(), snd_pcm_hw_params_set_buffer_time_near() failed: %s",
snd_strerror(err));
return False;
}
if ((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
{
logger(Sound, Error, "alsa_set_format(), snd_pcm_hw_params(): %s",
snd_strerror(err));
return False;
}
snd_pcm_hw_params_free(hwparams);
if ((err = snd_pcm_prepare(pcm)) < 0)
{
logger(Sound, Error, "alsa_set_format(), snd_pcm_prepare() failed: %s\n",
snd_strerror(err));
return False;
}
reopened = True;
return True;
}
RD_BOOL
alsa_open_out(void)
{
int err;
if ((err = snd_pcm_open(&out_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
{
logger(Sound, Error, "alsa_open_out(), snd_pcm_open() failed: %s",
snd_strerror(err));
return False;
}
reopened = True;
return True;
}
void
alsa_close_out(void)
{
/* Ack all remaining packets */
while (!rdpsnd_queue_empty())
rdpsnd_queue_next(0);
if (out_handle)
{
snd_pcm_close(out_handle);
out_handle = NULL;
}
}
RD_BOOL
alsa_format_supported(RD_WAVEFORMATEX * pwfx)
{
#if 0
int err;
snd_pcm_hw_params_t *hwparams = NULL;
if ((err = snd_pcm_hw_params_malloc(&hwparams)) < 0)
{
logger(Sound, Error,
"alsa_format_supported(), snd_pcm_hw_params_malloc() failed: %s",
snd_strerror(err));
return False;
}
if ((err = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0)
{
logger(Sound, Error,
"alsa_format_supported(), snd_pcm_hw_params_any() failed: %s\n",
snd_strerror(err));
return False;
}
snd_pcm_hw_params_free(hwparams);
#endif
if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
return False;
if ((pwfx->nChannels != 1) && (pwfx->nChannels != 2))
return False;
if ((pwfx->wBitsPerSample != 8) && (pwfx->wBitsPerSample != 16))
return False;
if ((pwfx->nSamplesPerSec != 44100) && (pwfx->nSamplesPerSec != 22050))
return False;
return True;
}
RD_BOOL
alsa_set_format_out(RD_WAVEFORMATEX * pwfx)
{
if (!alsa_set_format(out_handle, pwfx))
return False;
samplewidth_out = pwfx->wBitsPerSample / 8;
audiochannels_out = pwfx->nChannels;
rate_out = pwfx->nSamplesPerSec;
return True;
}
void
alsa_play(void)
{
struct audio_packet *packet;
STREAM out;
int len;
const unsigned char *data;
size_t before;
static long prev_s, prev_us;
int duration;
struct timeval tv;
int next_tick;
if (reopened)
{
reopened = False;
gettimeofday(&tv, NULL);
prev_s = tv.tv_sec;
prev_us = tv.tv_usec;
}
/* We shouldn't be called if the queue is empty, but still */
if (rdpsnd_queue_empty())
return;
packet = rdpsnd_queue_current_packet();
out = packet->s;
next_tick = rdpsnd_queue_next_tick();
before = s_tell(out);
len = s_remaining(out) / (samplewidth_out * audiochannels_out);
len = MIN(len, MAX_FRAMES);
in_uint8p(out, data, len);
len = snd_pcm_writei(out_handle, data, len);
if (len < 0)
{
snd_pcm_prepare(out_handle);
len = 0;
}
/* We might not have written everything */
s_seek(out, before + len * samplewidth_out * audiochannels_out);
gettimeofday(&tv, NULL);
duration = ((tv.tv_sec - prev_s) * 1000000 + (tv.tv_usec - prev_us)) / 1000;
if (packet->tick > next_tick)
next_tick += 65536;
if (s_check_end(out) || duration > next_tick - packet->tick + 500)
{
snd_pcm_sframes_t delay_frames;
unsigned long delay_us;
prev_s = tv.tv_sec;
prev_us = tv.tv_usec;
if (abs((next_tick - packet->tick) - duration) > 20)
{
logger(Sound, Debug,
"alsa_play(), duration=%d, calc=%d, last=%d, is=%d, should=%d",
duration, next_tick - packet->tick, packet->tick,
(packet->tick + duration) % 65536, next_tick % 65536);
}
if (snd_pcm_delay(out_handle, &delay_frames) < 0)
delay_frames = out->size / (samplewidth_out * audiochannels_out);
if (delay_frames < 0)
delay_frames = 0;
delay_us = delay_frames * (1000000 / rate_out);
rdpsnd_queue_next(delay_us);
}
}
RD_BOOL
alsa_open_in(void)
{
int err;
if ((err =
snd_pcm_open(&in_handle, pcm_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0)
{
logger(Sound, Error, "alsa_open_in(), snd_pcm_open() failed: %s",
snd_strerror(err));
return False;
}
return True;
}
void
alsa_close_in(void)
{
if (in_handle)
{
snd_pcm_close(in_handle);
in_handle = NULL;
}
}
RD_BOOL
alsa_set_format_in(RD_WAVEFORMATEX * pwfx)
{
int err;
if (!alsa_set_format(in_handle, pwfx))
return False;
if ((err = snd_pcm_start(in_handle)) < 0)
{
logger(Sound, Error, "alsa_open_in(), snd_pcm_start() failed: %s",
snd_strerror(err));
return False;
}
samplewidth_in = pwfx->wBitsPerSample / 8;
audiochannels_in = pwfx->nChannels;
rate_in = pwfx->nSamplesPerSec;
return True;
}
void
alsa_record(void)
{
int len;
char buffer[32768];
len = snd_pcm_readi(in_handle, buffer,
sizeof(buffer) / (samplewidth_in * audiochannels_in));
if (len < 0)
{
snd_pcm_prepare(in_handle);
len = 0;
}
rdpsnd_record(buffer, len * samplewidth_in * audiochannels_in);
}
struct audio_driver *
alsa_register(char *options)
{
static struct audio_driver alsa_driver;
memset(&alsa_driver, 0, sizeof(alsa_driver));
alsa_driver.name = "alsa";
alsa_driver.description = "ALSA output driver, default device: " DEFAULTDEVICE;
alsa_driver.add_fds = alsa_add_fds;
alsa_driver.check_fds = alsa_check_fds;
alsa_driver.wave_out_open = alsa_open_out;
alsa_driver.wave_out_close = alsa_close_out;
alsa_driver.wave_out_format_supported = alsa_format_supported;
alsa_driver.wave_out_set_format = alsa_set_format_out;
alsa_driver.wave_out_volume = rdpsnd_dsp_softvol_set;
alsa_driver.wave_in_open = alsa_open_in;
alsa_driver.wave_in_close = alsa_close_in;
alsa_driver.wave_in_format_supported = alsa_format_supported;
alsa_driver.wave_in_set_format = alsa_set_format_in;
alsa_driver.wave_in_volume = NULL; /* FIXME */
alsa_driver.need_byteswap_on_be = 0;
alsa_driver.need_resampling = 0;
if (options)
{
pcm_name = xstrdup(options);
}
else
{
pcm_name = DEFAULTDEVICE;
}
return &alsa_driver;
}