MuseScore/mscore/alsa.cpp
mirabilos 65cd276439
remove RCS IDs from (almost) all files
they do not make any sense, nor do they get updated, if the
source code is kept in git
2018-11-02 23:53:21 +01:00

783 lines
24 KiB
C++

//=============================================================================
// MuseScore
// Linux Music Score Editor
//
// AlsaDriver based on code from Fons Adriaensen (clalsadr.cc)
// Copyright (C) 2003 Fons Adriaensen
// partly based on original work from Paul Davis
//
// Copyright (C) 2002-2010 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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.
//=============================================================================
#include "config.h"
#ifdef USE_ALSA
#include <sys/time.h>
#include "alsa.h"
#include "libmscore/score.h"
#include "musescore.h"
#include "preferences.h"
#include "seq.h"
#include "alsamidi.h"
#include "libmscore/utils.h"
namespace Ms {
//---------------------------------------------------------
// AlsaDriver
//---------------------------------------------------------
AlsaDriver::AlsaDriver(QString s, unsigned rate,
snd_pcm_uframes_t frsize, unsigned nfrags)
{
_name = s;
_play_handle = 0;
_play_hwpar = 0;
_play_swpar = 0;
_play_npfd = 0;
_rate = rate;
_frsize = frsize;
_nfrags = nfrags;
_stat = -1;
_play_nchan = 2;
}
//---------------------------------------------------------
// init
// return true on success
//---------------------------------------------------------
bool AlsaDriver::init()
{
if (snd_pcm_open(&_play_handle, _name.toLatin1().data(), SND_PCM_STREAM_PLAYBACK, 0) < 0) {
_play_handle = 0;
qDebug ("Alsa_driver: Cannot open PCM device %s for playback.",
_name.toLatin1().data());
return false;
}
// check capabilities here
if (snd_pcm_hw_params_malloc (&_play_hwpar) < 0) {
qDebug ("Alsa_driver: can't allocate playback hw params");
return false;
}
if (snd_pcm_sw_params_malloc (&_play_swpar) < 0) {
qDebug ("Alsa_driver: can't allocate playback sw params");
return false;
}
if (!setHwpar(_play_handle, _play_hwpar))
return false;
if (!setSwpar(_play_handle, _play_swpar))
return false;
int dir;
unsigned rate = _rate;
if (snd_pcm_hw_params_get_rate (_play_hwpar, &_rate, &dir) || (rate != _rate) || dir) {
qDebug ("Alsa_driver: can't get requested sample rate for playback.");
return false;
}
snd_pcm_hw_params_get_format (_play_hwpar, &_play_format);
snd_pcm_hw_params_get_access (_play_hwpar, &_play_access);
switch (_play_format) {
case SND_PCM_FORMAT_S32_LE:
_play_func = play_32le;
_clear_func = clear_32le;
break;
case SND_PCM_FORMAT_S24_3LE:
_play_func = play_24le;
_clear_func = clear_24le;
break;
case SND_PCM_FORMAT_S16_LE:
_play_func = play_16le;
_clear_func = clear_16le;
break;
default:
qDebug ("Alsa_driver: can't handle playback sample format.");
return false;
}
_play_npfd = snd_pcm_poll_descriptors_count (_play_handle);
if (_play_npfd > MAXPFD) {
qDebug ("Alsa_driver: interface requires more than %d pollfd", MAXPFD);
return false;
}
_stat = 0;
return true;
}
//---------------------------------------------------------
// ~AlsaDriver
//---------------------------------------------------------
AlsaDriver::~AlsaDriver()
{
snd_pcm_sw_params_free (_play_swpar);
snd_pcm_hw_params_free (_play_hwpar);
if (_play_handle)
snd_pcm_close(_play_handle);
}
//---------------------------------------------------------
// pcmStart
// return true on success
//---------------------------------------------------------
bool AlsaDriver::pcmStart()
{
snd_pcm_sframes_t n = snd_pcm_avail_update(_play_handle);
if (unsigned(n) != _frsize * _nfrags) {
qDebug("Alsa_driver: %ld != %ld full buffer not available at start.", n, _frsize * _nfrags);
return false;
}
if (mmappedInterface) {
for (unsigned i = 0; i < _nfrags; i++) {
playInit (_frsize);
for (unsigned j = 0; j < _play_nchan; j++)
clearChan(j, _frsize);
snd_pcm_mmap_commit(_play_handle, _play_offs, _frsize);
}
}
int err = snd_pcm_start(_play_handle);
if (err < 0) {
qDebug ("Alsa_driver: pcmStart: pcm_start: %s.", snd_strerror (err));
return false;
}
return true;
}
//---------------------------------------------------------
// pcmStop
//---------------------------------------------------------
int AlsaDriver::pcmStop()
{
int err;
if (_play_handle && ((err = snd_pcm_drop (_play_handle)) < 0)) {
qDebug ("Alsa_driver: pcm_drop(play): %s", snd_strerror (err));
return -1;
}
return 0;
}
//---------------------------------------------------------
// pcmWait
//---------------------------------------------------------
snd_pcm_sframes_t AlsaDriver::pcmWait()
{
_stat = 0;
_xrun = false;
bool need_play = true;
while (need_play) {
if (need_play)
snd_pcm_poll_descriptors(_play_handle, _pfd, _play_npfd);
errno = 0;
// timeout in ms or infinite
if (poll(_pfd, _play_npfd, -1) < 0) {
if (errno == EINTR) {
_stat = 1;
return 0;
}
qDebug ("Alsa_driver: poll(): %s.", strerror (errno));
_stat = 2;
return 0;
}
int play_to = 0;
if (need_play) {
for (int i = 0; i < _play_npfd; i++) {
if (_pfd[i].revents & POLLERR) {
_xrun = true;
_stat |= 4;
}
if (_pfd[i].revents == 0)
play_to++;
}
if (!play_to)
need_play = false;
}
if ((play_to && (play_to == _play_npfd))) {
qDebug ("Alsa_driver: poll timed out.");
_stat |= 16;
return 0;
}
}
snd_pcm_sframes_t play_av = snd_pcm_avail_update(_play_handle);
if (play_av < 0) {
_xrun = true;
_stat |= 64;
}
if (_xrun) {
recover();
return 0;
}
return play_av;
}
//---------------------------------------------------------
// playInit
//---------------------------------------------------------
int AlsaDriver::playInit(snd_pcm_uframes_t len)
{
int err;
const snd_pcm_channel_area_t* a;
if ((err = snd_pcm_mmap_begin (_play_handle, &a, &_play_offs, &len)) < 0) {
qDebug ("Alsa_driver: snd_pcm_mmap_begin(play): %s.", snd_strerror (err));
return -1;
}
_play_step = (a->step) >> 3;
for (unsigned i = 0; i < _play_nchan; i++, a++) {
_play_ptr[i] = (char *)a->addr + ((a->first + a->step * _play_offs) >> 3);
}
return len;
}
//---------------------------------------------------------
// printinfo
//---------------------------------------------------------
void AlsaDriver::printinfo()
{
qDebug("Info:");
qDebug(" nchan : %d", _play_nchan);
qDebug(" rate : %d", _rate);
qDebug(" frsize : %ld", _frsize);
qDebug(" nfrags : %d", _nfrags);
snd_pcm_format_t format;
snd_pcm_hw_params_get_format (_play_hwpar, &format);
qDebug(" format : %s", snd_pcm_format_name (format));
}
//---------------------------------------------------------
// setHwpar
// return true on success
//---------------------------------------------------------
bool AlsaDriver::setHwpar(snd_pcm_t* handle, snd_pcm_hw_params_t* hwpar)
{
int err;
if ((err = snd_pcm_hw_params_any(handle, hwpar)) < 0) {
qDebug("Alsa_driver: no hw configurations available: %s.", snd_strerror (err));
return false;
}
if ((err = snd_pcm_hw_params_set_periods_integer (handle, hwpar)) < 0) {
qDebug("Alsa_driver: can't set period size to integral value.");
return false;
}
mmappedInterface = true;
if (((err = snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0)
&& ((err = snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0)) {
mmappedInterface = false;
if (MScore::debugMode)
qDebug("Alsa_driver: the interface doesn't support mmap-based access.");
if (((err = snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_RW_NONINTERLEAVED)) < 0)
&& ((err = snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)) {
qDebug("Alsa_driver: the interface doesn't support rw-based access.");
return false;
}
}
if (((err = snd_pcm_hw_params_set_format(handle, hwpar, SND_PCM_FORMAT_S16)) < 0)
&& ((err = snd_pcm_hw_params_set_format(handle, hwpar, SND_PCM_FORMAT_S24_3LE)) < 0)
&& ((err = snd_pcm_hw_params_set_format(handle, hwpar, SND_PCM_FORMAT_S32)) < 0)) {
qDebug("Alsa_driver: the interface doesn't support 32, 24 or 16 bit access.");
return false;
}
if ((err = snd_pcm_hw_params_set_rate(handle, hwpar, _rate, 0)) < 0) {
qDebug("Alsa_driver: can't set sample rate to %u.", _rate);
return false;
}
if ((err = snd_pcm_hw_params_set_channels(handle, hwpar, _play_nchan)) < 0) {
qDebug("Alsa_driver: can't set channel count to %u.",
_play_nchan);
return false;
}
int dir = 0;
// if ((err = snd_pcm_hw_params_set_periods_near (handle, hwpar, &_nfrags, &dir)) < 0) {
if ((err = snd_pcm_hw_params_set_periods(handle, hwpar, _nfrags, 0)) < 0) {
qDebug("Alsa_driver: can't set periods to %u.", _nfrags);
return false;
}
dir = 0;
if ((err = snd_pcm_hw_params_set_period_size_near(handle, hwpar, &_frsize, &dir)) < 0) {
qDebug("Alsa_driver: can't set period size to %lu: %s",
_frsize, snd_strerror(err));
return false;
}
snd_pcm_uframes_t n = _frsize * _nfrags;
if ((err = snd_pcm_hw_params_set_buffer_size_near (handle, hwpar, &n)) < 0) {
qDebug("Alsa_driver: can't set buffer length to %lu.", _frsize * _nfrags);
return false;
}
if (n != _frsize * _nfrags) {
qDebug("Alsa_driver: buffer size requested %lu got %lu, _frsize %lu _nfrags %d",
_frsize * _nfrags, n, _frsize, _nfrags);
if (n < _frsize * _nfrags)
return false;
}
if ((err = snd_pcm_hw_params (handle, hwpar)) < 0) {
qDebug("Alsa_driver: can't set hardware parameters: %s", snd_strerror(err));
printinfo();
return false;
}
return true;
}
//---------------------------------------------------------
// setSwpar
// return true on success
//---------------------------------------------------------
bool AlsaDriver::setSwpar(snd_pcm_t *handle, snd_pcm_sw_params_t *swpar)
{
int err;
snd_pcm_sw_params_current(handle, swpar);
if ((err = snd_pcm_sw_params_set_silence_size(handle, swpar, 0)) < 0) {
qDebug("AlsaDriver: can't set timestamp mode to %u.",
SND_PCM_TSTAMP_MMAP);
return false;
}
if ((err = snd_pcm_sw_params_set_avail_min(handle, swpar, _frsize)) < 0) {
qDebug("AlsaDriver: can't set availmin to %lu.", _frsize);
return false;
}
if ((err = snd_pcm_sw_params(handle, swpar)) < 0) {
qDebug ("Alsa_driver: can't set software parameters.");
return false;
}
return true;
}
//---------------------------------------------------------
// recover
// return true on success
//---------------------------------------------------------
bool AlsaDriver::recover()
{
int err;
snd_pcm_status_t* stat;
snd_pcm_status_alloca (&stat);
if ((err = snd_pcm_status (_play_handle, stat)) < 0) {
qDebug("Alsa_driver: recover: pcm_status(): %s", snd_strerror (err));
return false;
}
if (snd_pcm_status_get_state (stat) == SND_PCM_STATE_XRUN) {
struct timeval tnow, trig;
gettimeofday (&tnow, 0);
snd_pcm_status_get_trigger_tstamp (stat, &trig);
qDebug("AlsaDriver: recover: stat = %02x, xrun of at least %8.3lf ms", _stat,
1e3 * tnow.tv_sec - 1e3 * trig.tv_sec + 1e-3 * tnow.tv_usec - 1e-3 * trig.tv_usec);
}
if (pcmStop()) {
qDebug("AlsaDriver: recover: pcmStop failed");
return false;
}
if (_play_handle && ((err = snd_pcm_prepare (_play_handle)) < 0)) {
qDebug("Alsa_driver: recover: pcm_prepare(play): %s", snd_strerror (err));
return false;
}
return pcmStart();
}
//---------------------------------------------------------
// play_16le
//---------------------------------------------------------
char* AlsaDriver::play_16le (const float* src, char* dst, int step, int nfrm)
{
while (nfrm--) {
short d;
float s = *src++;
if (s > 1)
d = 0x7fff;
else if (s < -1)
d = 0x8001;
else
d = (short)(0x7fff * s);
*((short*) dst) = d;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// play_24le
//---------------------------------------------------------
char* AlsaDriver::play_24le(const float* src, char* dst, int step, int nfrm)
{
float s;
int d;
while (nfrm--) {
s = *src++;
if (s > 1)
d = 0x007fffff;
else if (s < -1)
d = 0x00800001;
else
d = (int)(0x007fffff * s);
dst [0] = d;
dst [1] = d >> 8;
dst [2] = d >> 16;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// play_32le
//---------------------------------------------------------
char* AlsaDriver::play_32le(const float* src, char* dst, int step, int nfrm)
{
float s;
int d;
while (nfrm--) {
s = *src++;
if (s > 1)
d = 0x007fffff;
else if (s < -1)
d = 0x00800001;
else
d = (int)(0x007fffff * s);
*((int *) dst) = d << 8;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// clear_16le
//---------------------------------------------------------
char* AlsaDriver::clear_16le (char* dst, int step, int nfrm)
{
while (nfrm--) {
*((short int *) dst) = 0;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// clear_24le
//---------------------------------------------------------
char* AlsaDriver::clear_24le(char* dst, int step, int nfrm)
{
while (nfrm--) {
dst [0] = 0;
dst [1] = 0;
dst [2] = 0;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// clear_32le
//---------------------------------------------------------
char* AlsaDriver::clear_32le(char* dst, int step, int nfrm)
{
while (nfrm--) {
*((int *) dst) = 0;
dst += step;
}
return dst;
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void AlsaDriver::write(int n, float* l, float* r)
{
for (;;) {
int err = snd_pcm_wait(_play_handle, -1);
if (err < 0) {
recover();
continue;
}
int avail = snd_pcm_avail_update(_play_handle);
if (avail < 0) {
qDebug("AlsaDriver::write: snd_pcm_avail_update() (%s)", snd_strerror(avail));
recover();
continue;
}
else if (avail >= n)
break;
}
if (mmappedInterface) {
playInit(n);
_play_ptr[0] = _play_func(l, _play_ptr[0], _play_step, n);
_play_ptr[1] = _play_func(r, _play_ptr[1], _play_step, n);
snd_pcm_mmap_commit(_play_handle, _play_offs, n);
}
else {
//
// tested only INTERLEAVED and S16 format
//
int err;
if (_play_access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
//
// untested
//
short lbuffer[n];
short rbuffer[n];
_play_func(l, (char*)lbuffer, 2, n);
_play_func(r, (char*)rbuffer, 2, n);
void* bp[2];
bp[0] = lbuffer;
bp[1] = rbuffer;
if ((err = snd_pcm_writen(_play_handle, bp, n)) < 0)
qDebug("AlsaDriver::write(): failed (%s)", snd_strerror(err));
}
else if (_play_access == SND_PCM_ACCESS_RW_INTERLEAVED) {
short buffer[n * 2];
_play_func(l, (char*)buffer, 4, n);
_play_func(r, (char*)(buffer + 1), 4, n);
if ((err = snd_pcm_writei(_play_handle, buffer, n)) < 0)
qDebug("AlsaDriver::write(): failed (%s)", snd_strerror(err));
}
else {
qDebug("AlsaDriver::write(): unsupported access type %d", _play_access);
return;
}
}
}
//---------------------------------------------------------
// AlsaAudio
//---------------------------------------------------------
AlsaAudio::AlsaAudio(Seq* s)
: Driver(s)
{
alsa = 0;
state = Transport::STOP;
seekflag = false;
midiDriver = 0;
}
//---------------------------------------------------------
// sampleRate
//---------------------------------------------------------
int AlsaAudio::sampleRate() const
{
if (alsa)
return alsa->sampleRate();
else
return preferences.getInt(PREF_IO_ALSA_SAMPLERATE);
}
//---------------------------------------------------------
// AlsaAudio
//---------------------------------------------------------
AlsaAudio::~AlsaAudio()
{
stop();
delete alsa;
}
//---------------------------------------------------------
// init
// return false on error
//---------------------------------------------------------
bool AlsaAudio::init(bool /*hot*/)
{
alsa = new AlsaDriver(
preferences.getString(PREF_IO_ALSA_DEVICE),
preferences.getInt(PREF_IO_ALSA_SAMPLERATE),
preferences.getInt(PREF_IO_ALSA_PERIODSIZE),
preferences.getInt(PREF_IO_ALSA_FRAGMENTS));
if (!alsa->init()) {
delete alsa;
alsa = 0;
qDebug("init ALSA audio driver failed");
return false;
}
midiDriver = new AlsaMidiDriver(seq);
if (!midiDriver->init()) {
delete midiDriver;
midiDriver = 0;
return false;
}
return true;
}
//---------------------------------------------------------
// alsaLoop
//---------------------------------------------------------
static void* alsaLoop(void* alsa)
{
((AlsaAudio*)alsa)->alsaLoop();
return 0;
}
//---------------------------------------------------------
// alsaLoop
//---------------------------------------------------------
void AlsaAudio::alsaLoop()
{
//
// try to get realtime privileges
//
struct sched_param rt_param;
memset(&rt_param, 0, sizeof(rt_param));
rt_param.sched_priority = 50;
int rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &rt_param);
if (rv == -1)
perror("Set realtime scheduler failed");
if (!alsa->pcmStart()) {
alsa->pcmStop();
runAlsa = 0;
return;
}
int size = alsa->fsize();
float buffer[size * 2];
runAlsa = 2;
while (runAlsa == 2) {
seq->process(size, buffer);
float l[size];
float r[size];
float* lp = l;
float* rp = r;
float* sp = buffer;
for (int i = 0; i < size; ++i) {
*lp++ = *sp++;
*rp++ = *sp++;
}
alsa->write(size, l, r);
}
alsa->pcmStop();
runAlsa = 0;
}
//---------------------------------------------------------
// start
//---------------------------------------------------------
bool AlsaAudio::start(bool)
{
pthread_attr_t* attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t));
pthread_attr_init(attributes);
if (pthread_create(&thread, attributes, Ms::alsaLoop, this))
perror("creating thread failed:");
pthread_attr_destroy(attributes);
return true;
}
//---------------------------------------------------------
// stop
//---------------------------------------------------------
bool AlsaAudio::stop()
{
if (runAlsa == 2) {
runAlsa = 1;
int i = 0;
for (;i < 4; ++i) {
if (runAlsa == 0)
break;
sleep(1);
}
pthread_cancel(thread);
pthread_join(thread, 0);
}
return true;
}
//---------------------------------------------------------
// startTransport
//---------------------------------------------------------
void AlsaAudio::startTransport()
{
state = Transport::PLAY;
}
//---------------------------------------------------------
// stopTransport
//---------------------------------------------------------
void AlsaAudio::stopTransport()
{
state = Transport::STOP;
}
//---------------------------------------------------------
// getState
//---------------------------------------------------------
Transport AlsaAudio::getState()
{
return state;
}
//---------------------------------------------------------
// midiRead
//---------------------------------------------------------
void AlsaAudio::midiRead()
{
midiDriver->read();
}
//---------------------------------------------------------
// updateOutPortCount
// Add/remove ALSA MIDI Out ports
//---------------------------------------------------------
void AlsaAudio::updateOutPortCount(int maxport)
{
static_cast<AlsaMidiDriver*>(midiDriver)->updateInPortCount(maxport);
}
}
#endif