blob: b9eed129ea5709064a9e46ad081eede8f88d5ab6 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include "log.h"
#include "io_misc.h"
#include "pcm.h"
#define PCM_OSS_DEVICE_PATH "/dev/dsp"
#ifndef SNDCTL_DSP_SPEED
#define SNDCTL_DSP_SPEED SOUND_PCM_WRITE_RATE
#endif /* SNDCTL_DSP_SPEED */
#ifndef SNDCTL_DSP_CHANNELS
#define SNDCTL_DSP_CHANNELS SOUND_PCM_WRITE_CHANNELS
#endif /* SNDCTL_DSP_CHANNELS */
struct PcmDeviceStruct {
int fileDescriptor;
int driverVersion;
int sampleRate;
int channelCount;
};
PcmDevice *
openPcmDevice (int errorLevel, const char *device) {
PcmDevice *pcm;
if ((pcm = malloc(sizeof(*pcm)))) {
if (!*device) device = PCM_OSS_DEVICE_PATH;
if ((pcm->fileDescriptor = open(device, O_WRONLY|O_NONBLOCK)) != -1) {
/* Nonblocking if snd_seq_oss is loaded with nonblock_open=1.
* There appears to be a bug in this case as write() always
* returns the full count even though large chunks of sound are
* missing. For now, therefore, force blocking output.
*/
setBlockingIo(pcm->fileDescriptor, 1);
pcm->driverVersion = 0X030000;
#ifdef OSS_GETVERSION
if (ioctl(pcm->fileDescriptor, OSS_GETVERSION, &pcm->driverVersion) == -1)
logMessage(errorLevel, "cannot get OSS driver version");
#endif /* OSS_GETVERSION */
logMessage(LOG_DEBUG, "OPSS driver version: %06X", pcm->driverVersion);
setPcmSampleRate(pcm, 8000);
setPcmChannelCount(pcm, 1);
return pcm;
} else {
logMessage(errorLevel, "cannot open PCM device: %s: %s", device, strerror(errno));
}
free(pcm);
} else {
logSystemError("PCM device allocation");
}
return NULL;
}
void
closePcmDevice (PcmDevice *pcm) {
close(pcm->fileDescriptor);
free(pcm);
}
int
writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
return writeFile(pcm->fileDescriptor, buffer, count) != -1;
}
int
getPcmBlockSize (PcmDevice *pcm) {
int fragmentCount = (1 << 0X10) - 1;
int fragmentShift = 7;
int fragmentSize = 1 << fragmentShift;
int fragmentSetting = (fragmentCount << 0X10) | fragmentShift;
ioctl(pcm->fileDescriptor, SNDCTL_DSP_SETFRAGMENT, &fragmentSetting);
{
int blockSize;
if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_GETBLKSIZE, &blockSize) != -1) return blockSize;
}
return fragmentSize;
}
int
getPcmSampleRate (PcmDevice *pcm) {
return pcm->sampleRate;
}
int
setPcmSampleRate (PcmDevice *pcm, int rate) {
if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_SPEED, &rate) != -1) pcm->sampleRate = rate;
return getPcmSampleRate(pcm);
}
int
getPcmChannelCount (PcmDevice *pcm) {
return pcm->channelCount;
}
int
setPcmChannelCount (PcmDevice *pcm, int channels) {
if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_CHANNELS, &channels) != -1) pcm->channelCount = channels;
return getPcmChannelCount(pcm);
}
typedef struct {
PcmAmplitudeFormat internal;
int external;
} AmplitudeFormatEntry;
static const AmplitudeFormatEntry amplitudeFormatTable[] = {
{PCM_FMT_U8 , AFMT_U8 },
{PCM_FMT_S8 , AFMT_S8 },
{PCM_FMT_U16B , AFMT_U16_BE},
{PCM_FMT_S16B , AFMT_S16_BE},
{PCM_FMT_U16L , AFMT_U16_LE},
{PCM_FMT_S16L , AFMT_S16_LE},
{PCM_FMT_ULAW , AFMT_MU_LAW},
{PCM_FMT_ALAW , AFMT_A_LAW},
{PCM_FMT_UNKNOWN, AFMT_QUERY }
};
static PcmAmplitudeFormat
doPcmAmplitudeFormat (PcmDevice *pcm, int format) {
if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_SETFMT, &format) != -1) {
const AmplitudeFormatEntry *entry = amplitudeFormatTable;
while (entry->internal != PCM_FMT_UNKNOWN) {
if (entry->external == format) return entry->internal;
++entry;
}
}
return PCM_FMT_UNKNOWN;
}
PcmAmplitudeFormat
getPcmAmplitudeFormat (PcmDevice *pcm) {
return doPcmAmplitudeFormat(pcm, AFMT_QUERY);
}
PcmAmplitudeFormat
setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
const AmplitudeFormatEntry *entry = amplitudeFormatTable;
while (entry->internal != PCM_FMT_UNKNOWN) {
if (entry->internal == format) break;
++entry;
}
return doPcmAmplitudeFormat(pcm, entry->external);
}
void
pushPcmOutput (PcmDevice *pcm) {
ioctl(pcm->fileDescriptor, SNDCTL_DSP_POST, 0);
}
void
awaitPcmOutput (PcmDevice *pcm) {
ioctl(pcm->fileDescriptor, SNDCTL_DSP_SYNC, 0);
}
void
cancelPcmOutput (PcmDevice *pcm) {
ioctl(pcm->fileDescriptor, SNDCTL_DSP_RESET, 0);
}