| /* |
| * 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 "log.h" |
| #include "parse.h" |
| #include "pcm.h" |
| |
| struct PcmDeviceStruct { |
| HWAVEOUT handle; |
| UINT deviceID; |
| WAVEFORMATEX format; |
| HANDLE done; |
| WAVEHDR waveHdr; |
| size_t bufSize; |
| }; |
| |
| static WAVEFORMATEX defaultFormat = { WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8, 0 }; |
| |
| static void |
| recomputeWaveOutFormat(WAVEFORMATEX *format) { |
| format->nBlockAlign = format->nChannels * ((format->wBitsPerSample + 7) / 8); |
| format->nAvgBytesPerSec = format->nBlockAlign * format->nSamplesPerSec; |
| } |
| |
| static WAVEHDR initWaveHdr = { NULL, 0, 0, 0, 0, 1, NULL, 0 }; |
| |
| static void |
| LogWaveOutError(MMRESULT error, int errorLevel, const char *action) { |
| char msg[MAXERRORLENGTH]; |
| waveOutGetErrorText(error, msg, sizeof(msg)); |
| logMessage(errorLevel, "%s error %d: %s.", action, error, msg); |
| } |
| |
| PcmDevice * |
| openPcmDevice (int errorLevel, const char *device) { |
| PcmDevice *pcm; |
| MMRESULT mmres; |
| WAVEOUTCAPS caps; |
| int id = 0; |
| |
| if (*device) { |
| if (!isInteger(&id, device) || (id < 0) || (id >= waveOutGetNumDevs())) { |
| logMessage(errorLevel, "invalid PCM device number: %s", device); |
| return NULL; |
| } |
| } |
| |
| if (!(pcm = malloc(sizeof(*pcm)))) { |
| logSystemError("PCM device allocation"); |
| return NULL; |
| } |
| pcm->deviceID = id; |
| |
| if ((waveOutGetDevCaps(pcm->deviceID, &caps, sizeof(caps))) != MMSYSERR_NOERROR) |
| pcm->format = defaultFormat; |
| else { |
| logMessage(errorLevel, "PCM device %d is %s", pcm->deviceID, caps.szPname); |
| pcm->format.wFormatTag = WAVE_FORMAT_PCM; |
| if (caps.dwFormats & |
| (WAVE_FORMAT_1S08 |
| |WAVE_FORMAT_1S16 |
| |WAVE_FORMAT_2S08 |
| |WAVE_FORMAT_2S16 |
| |WAVE_FORMAT_4S08 |
| |WAVE_FORMAT_4S16)) |
| pcm->format.nChannels = 2; |
| else |
| pcm->format.nChannels = 1; |
| if (caps.dwFormats & |
| (WAVE_FORMAT_4M08 |
| |WAVE_FORMAT_4M16 |
| |WAVE_FORMAT_4S08 |
| |WAVE_FORMAT_4S16)) |
| pcm->format.nSamplesPerSec = 44100; |
| else if (caps.dwFormats & |
| (WAVE_FORMAT_2M08 |
| |WAVE_FORMAT_2M16 |
| |WAVE_FORMAT_2S08 |
| |WAVE_FORMAT_2S16)) |
| pcm->format.nSamplesPerSec = 22050; |
| else if (caps.dwFormats & |
| (WAVE_FORMAT_1M08 |
| |WAVE_FORMAT_1M16 |
| |WAVE_FORMAT_1S08 |
| |WAVE_FORMAT_1S16)) |
| pcm->format.nSamplesPerSec = 11025; |
| else { |
| logMessage(errorLevel, "unknown PCM capability %#lx", caps.dwFormats); |
| goto out; |
| } |
| if (caps.dwFormats & |
| (WAVE_FORMAT_1M16 |
| |WAVE_FORMAT_1S16 |
| |WAVE_FORMAT_2M16 |
| |WAVE_FORMAT_2S16 |
| |WAVE_FORMAT_4M16 |
| |WAVE_FORMAT_4S16)) |
| pcm->format.wBitsPerSample = 16; |
| else if (caps.dwFormats & |
| (WAVE_FORMAT_1M08 |
| |WAVE_FORMAT_1S08 |
| |WAVE_FORMAT_2M08 |
| |WAVE_FORMAT_2S08 |
| |WAVE_FORMAT_4M08 |
| |WAVE_FORMAT_4S08)) |
| pcm->format.wBitsPerSample = 8; |
| else { |
| logMessage(LOG_ERR, "unknown PCM capability %#lx", caps.dwFormats); |
| goto out; |
| } |
| recomputeWaveOutFormat(&pcm->format); |
| pcm->format.cbSize = 0; |
| } |
| |
| if (!(pcm->done = CreateEvent(NULL, FALSE, TRUE, NULL))) { |
| logWindowsSystemError("creating PCM completion event"); |
| goto out; |
| } |
| |
| pcm->waveHdr = initWaveHdr; |
| pcm->bufSize = 0; |
| |
| if ((mmres = waveOutOpen(&pcm->handle, pcm->deviceID, |
| &pcm->format, (DWORD) pcm->done, 0, CALLBACK_EVENT)) != MMSYSERR_NOERROR) { |
| LogWaveOutError(mmres, errorLevel, "opening PCM device"); |
| goto outEvent; |
| } |
| return pcm; |
| |
| outEvent: |
| CloseHandle(pcm->done); |
| out: |
| free(pcm); |
| return NULL; |
| } |
| |
| static int |
| unprepareHeader(PcmDevice *pcm) { |
| MMRESULT mmres; |
| awaitPcmOutput(pcm); |
| if ((mmres = waveOutUnprepareHeader(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) { |
| LogWaveOutError(mmres, LOG_ERR, "unpreparing PCM data header"); |
| return 0; |
| } |
| return 1; |
| } |
| |
| static int |
| updateWaveOutFormat(PcmDevice *pcm, WAVEFORMATEX *format, const char *errmsg) { |
| MMRESULT mmres; |
| recomputeWaveOutFormat(format); |
| if (!(unprepareHeader(pcm))) return 0; |
| if (waveOutOpen(NULL, pcm->deviceID, format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR) { |
| waveOutClose(pcm->handle); |
| pcm->handle = INVALID_HANDLE_VALUE; |
| if ((mmres = waveOutOpen(&pcm->handle, pcm->deviceID, format, |
| (DWORD) pcm->done, 0, CALLBACK_EVENT)) == MMSYSERR_NOERROR) { |
| pcm->format = *format; |
| return 1; |
| } |
| LogWaveOutError(mmres, LOG_ERR, errmsg); |
| } |
| return 0; |
| } |
| |
| void |
| closePcmDevice (PcmDevice *pcm) { |
| CloseHandle(pcm->done); |
| unprepareHeader(pcm); |
| waveOutClose(pcm->handle); |
| free(pcm->waveHdr.lpData); |
| free(pcm); |
| } |
| |
| int |
| writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) { |
| MMRESULT mmres; |
| void *newBuf; |
| if (!count) return 1; |
| if (count > pcm->bufSize) { |
| if (!(unprepareHeader(pcm))) return 0; |
| if (!(newBuf = realloc(pcm->waveHdr.lpData, 2 * count))) { |
| logSystemError("allocating PCM data buffer"); |
| return 0; |
| } |
| pcm->waveHdr.lpData = newBuf; |
| pcm->waveHdr.dwFlags = 0; |
| pcm->waveHdr.dwBufferLength = pcm->bufSize = 2 * count; |
| } |
| awaitPcmOutput(pcm); |
| if (!(pcm->waveHdr.dwFlags & WHDR_PREPARED)) |
| if ((mmres = waveOutPrepareHeader(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) { |
| LogWaveOutError(mmres, LOG_ERR, "preparing PCM data header"); |
| return 0; |
| } |
| pcm->waveHdr.dwBufferLength = count; |
| memcpy(pcm->waveHdr.lpData, buffer, count); |
| ResetEvent(pcm->done); |
| if ((mmres = waveOutWrite(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) { |
| SetEvent(pcm->done); |
| LogWaveOutError(mmres, LOG_ERR, "writing PCM data"); |
| return 0; |
| } |
| return 1; |
| } |
| |
| int |
| getPcmBlockSize (PcmDevice *pcm) { |
| return 0X10000; |
| } |
| |
| int |
| getPcmSampleRate (PcmDevice *pcm) { |
| return pcm->format.nSamplesPerSec; |
| } |
| |
| int |
| setPcmSampleRate (PcmDevice *pcm, int rate) { |
| WAVEFORMATEX format = pcm->format; |
| format.nSamplesPerSec = rate; |
| if (!updateWaveOutFormat(pcm, &format, "setting PCM sample rate")) |
| return getPcmSampleRate(pcm); |
| else |
| return rate; |
| } |
| |
| int |
| getPcmChannelCount (PcmDevice *pcm) { |
| return pcm->format.nChannels; |
| } |
| |
| int |
| setPcmChannelCount (PcmDevice *pcm, int channels) { |
| WAVEFORMATEX format = pcm->format; |
| format.nChannels = channels; |
| if (!updateWaveOutFormat(pcm, &format, "setting PCM channel count")) |
| return getPcmChannelCount(pcm); |
| else |
| return channels; |
| } |
| |
| PcmAmplitudeFormat |
| getPcmAmplitudeFormat (PcmDevice *pcm) { |
| if (pcm->format.wBitsPerSample == 8) return PCM_FMT_U8; |
| if (pcm->format.wBitsPerSample == 16) return PCM_FMT_S16L; |
| return PCM_FMT_UNKNOWN; |
| } |
| |
| PcmAmplitudeFormat |
| setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) { |
| WAVEFORMATEX newFormat = pcm->format; |
| if (format == PCM_FMT_U8) newFormat.wBitsPerSample = 8; |
| else if (format == PCM_FMT_S16L) newFormat.wBitsPerSample = 16; |
| else return getPcmAmplitudeFormat(pcm); |
| if (!updateWaveOutFormat(pcm, &newFormat, "setting PCM amplitude format")) |
| return getPcmAmplitudeFormat(pcm); |
| else |
| return format; |
| } |
| |
| void |
| pushPcmOutput (PcmDevice *pcm) { |
| } |
| |
| void |
| awaitPcmOutput (PcmDevice *pcm) { |
| while ((pcm->waveHdr.dwFlags & WHDR_PREPARED) |
| && !(pcm->waveHdr.dwFlags & WHDR_DONE)) |
| WaitForSingleObject(pcm->done, INFINITE); |
| SetEvent(pcm->done); |
| } |
| |
| void |
| cancelPcmOutput (PcmDevice *pcm) { |
| waveOutReset(pcm->handle); |
| } |