From 41a1dcb0bb8bd7aaa20d03ea92aa49210b66fa92 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 16 Oct 2015 08:36:16 -0400 Subject: BACKENDS: Switch to a common base class for threaded audio CD streams --- backends/audiocd/audiocd-stream.cpp | 174 ++++++++++++++++++++++++++ backends/audiocd/audiocd-stream.h | 86 +++++++++++++ backends/audiocd/linux/linux-audiocd.cpp | 185 +++++----------------------- backends/audiocd/win32/win32-audiocd.cpp | 202 +++++-------------------------- backends/module.mk | 1 + 5 files changed, 316 insertions(+), 332 deletions(-) create mode 100644 backends/audiocd/audiocd-stream.cpp create mode 100644 backends/audiocd/audiocd-stream.h diff --git a/backends/audiocd/audiocd-stream.cpp b/backends/audiocd/audiocd-stream.cpp new file mode 100644 index 0000000000..da55654a1d --- /dev/null +++ b/backends/audiocd/audiocd-stream.cpp @@ -0,0 +1,174 @@ +/* Cabal - Legacy Game Implementations + * + * Cabal is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "backends/audiocd/audiocd-stream.h" +#include "common/textconsole.h" + +AudioCDStream::AudioCDStream() : _buffer(), _frame(0), _bufferPos(0), _bufferFrame(0), _forceStop(false) { +} + +AudioCDStream::~AudioCDStream() { + // Stop the timer; the subclass needs to do this, + // so this is just a last resort. + stopTimer(); + + // Clear any buffered frames + emptyQueue(); +} + +int AudioCDStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + // See if any data is left first + while (_bufferPos < kSamplesPerFrame && samples < numSamples) + buffer[samples++] = _buffer[_bufferPos++]; + + // Bail out if done + if (endOfData()) + return samples; + + while (samples < numSamples && !endOfData()) { + if (!readNextFrame()) + return samples; + + // Copy the samples over + for (_bufferPos = 0; _bufferPos < kSamplesPerFrame && samples < numSamples; ) + buffer[samples++] = _buffer[_bufferPos++]; + } + + return samples; +} + +bool AudioCDStream::readNextFrame() { + // Fetch a frame from the queue + int16 *buffer; + + { + Common::StackLock lock(_mutex); + + // Nothing we can do if it's empty + if (_bufferQueue.empty()) + return false; + + buffer = _bufferQueue.pop(); + } + + memcpy(_buffer, buffer, kSamplesPerFrame * 2); + delete[] buffer; + _frame++; + return true; +} + +bool AudioCDStream::endOfData() const { + return !shouldForceStop() && getStartFrame() + _frame >= getEndFrame() && _bufferPos == kSamplesPerFrame; +} + +bool AudioCDStream::seek(const Audio::Timestamp &where) { + // Stop the timer + stopTimer(); + + // Clear anything out of the queue + emptyQueue(); + + // Convert to the frame number + // Really not much else needed + _bufferPos = kSamplesPerFrame; + _frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames(); + _bufferFrame = _frame; + + // Start the timer again + startTimer(); + return true; +} + +Audio::Timestamp AudioCDStream::getLength() const { + return Audio::Timestamp(0, getEndFrame() - getStartFrame(), kFramesPerSecond); +} + +void AudioCDStream::timerProc(void *refCon) { + static_cast(refCon)->onTimer(); +} + +void AudioCDStream::onTimer() { + // The goal here is to do as much work in this timer instead + // of doing it in the readBuffer() call, which is the mixer. + + // If we're done, bail. + if (shouldForceStop() || getStartFrame() + _bufferFrame >= getEndFrame()) + return; + + // Get a quick count of the number of items in the queue + // We don't care that much; we only need a quick estimate + _mutex.lock(); + uint32 queueCount = _bufferQueue.size(); + _mutex.unlock(); + + // If we have enough audio buffered, bail out + if (queueCount >= kBufferThreshold) + return; + + while (!shouldForceStop() && queueCount < kBufferThreshold && getStartFrame() + _bufferFrame < getEndFrame()) { + int16 *buffer = new int16[kSamplesPerFrame]; + + // Figure out the MSF of the frame we're looking for + int frame = _bufferFrame + getStartFrame(); + + // Request to read that frame + if (!readFrame(frame, buffer)) { + warning("Failed to read CD audio"); + forceStop(); + return; + } + + _bufferFrame++; + + // Now push the buffer onto the queue + Common::StackLock lock(_mutex); + _bufferQueue.push(buffer); + queueCount = _bufferQueue.size(); + } +} + +void AudioCDStream::startTimer() { + _forceStop = false; + g_system->getTimerManager()->installTimerProc(timerProc, 10 * 1000, this, "AudioCDStream"); +} + +void AudioCDStream::stopTimer() { + forceStop(); + g_system->getTimerManager()->removeTimerProc(timerProc); +} + +void AudioCDStream::emptyQueue() { + while (!_bufferQueue.empty()) + delete[] _bufferQueue.pop(); +} + +bool AudioCDStream::shouldForceStop() const { + Common::StackLock lock(_forceStopMutex); + return _forceStop; +} + +void AudioCDStream::forceStop() { + Common::StackLock lock(_forceStopMutex); + _forceStop = true; +} diff --git a/backends/audiocd/audiocd-stream.h b/backends/audiocd/audiocd-stream.h new file mode 100644 index 0000000000..a8938025b6 --- /dev/null +++ b/backends/audiocd/audiocd-stream.h @@ -0,0 +1,86 @@ +/* Cabal - Legacy Game Implementations + * + * Cabal is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BACKENDS_AUDIOCD_AUDIOCD_STREAM_H +#define BACKENDS_AUDIOCD_AUDIOCD_STREAM_H + +#include "audio/audiostream.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "common/timer.h" + +class AudioCDStream : public Audio::SeekableAudioStream { +public: + AudioCDStream(); + ~AudioCDStream(); + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return true; } + int getRate() const { return 44100; } + bool endOfData() const; + bool seek(const Audio::Timestamp &where); + Audio::Timestamp getLength() const; + +protected: + virtual uint getStartFrame() const = 0; + virtual uint getEndFrame() const = 0; + virtual bool readFrame(int frame, int16 *buffer) = 0; + + void startTimer(); + void stopTimer(); + + enum { + kBytesPerFrame = 2352, + kSamplesPerFrame = kBytesPerFrame / 2 + }; + + enum { + kSecondsPerMinute = 60, + kFramesPerSecond = 75 + }; + + enum { + // Keep about a second's worth of audio in the buffer + kBufferThreshold = kFramesPerSecond + }; + +private: + int16 _buffer[kSamplesPerFrame]; + int _frame; + uint _bufferPos; + + Common::Queue _bufferQueue; + int _bufferFrame; + Common::Mutex _mutex; + + bool _forceStop; + bool shouldForceStop() const; + void forceStop(); + Common::Mutex _forceStopMutex; + + bool readNextFrame(); + static void timerProc(void *refCon); + void onTimer(); + void emptyQueue(); +}; + +#endif diff --git a/backends/audiocd/linux/linux-audiocd.cpp b/backends/audiocd/linux/linux-audiocd.cpp index a835ade218..ea5eb1f9be 100644 --- a/backends/audiocd/linux/linux-audiocd.cpp +++ b/backends/audiocd/linux/linux-audiocd.cpp @@ -29,15 +29,12 @@ #include "backends/audiocd/linux/linux-audiocd.h" -#include "audio/audiostream.h" +#include "backends/audiocd/audiocd-stream.h" #include "backends/audiocd/default/default-audiocd.h" #include "common/array.h" #include "common/config-manager.h" -#include "common/mutex.h" -#include "common/queue.h" #include "common/str.h" #include "common/debug.h" -#include "common/timer.h" enum { kLeadoutTrack = 0xAA @@ -67,185 +64,59 @@ static int getFrameCount(const cdrom_msf0 &msf) { return time; } -class LinuxAudioCDStream : public Audio::SeekableAudioStream { +class LinuxAudioCDStream : public AudioCDStream { public: LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry); ~LinuxAudioCDStream(); - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return true; } - int getRate() const { return 44100; } - bool endOfData() const; - bool seek(const Audio::Timestamp &where); - Audio::Timestamp getLength() const; +protected: + uint getStartFrame() const; + uint getEndFrame() const; + bool readFrame(int frame, int16 *buffer); private: int _fd; const cdrom_tocentry &_startEntry, &_endEntry; - int16 _buffer[kSamplesPerFrame]; - int _frame; - uint _bufferPos; - - Common::Queue _bufferQueue; - int _bufferFrame; - Common::Mutex _mutex; - - bool readNextFrame(); - static void timerProc(void *refCon); - void onTimer(); - void emptyQueue(); - void startTimer(); - void stopTimer(); }; LinuxAudioCDStream::LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry) : - _fd(fd), _startEntry(startEntry), _endEntry(endEntry), _buffer(), _frame(0), _bufferPos(kSamplesPerFrame), _bufferFrame(0) { + _fd(fd), _startEntry(startEntry), _endEntry(endEntry) { startTimer(); } LinuxAudioCDStream::~LinuxAudioCDStream() { stopTimer(); - emptyQueue(); } -int LinuxAudioCDStream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - - // See if any data is left first - while (_bufferPos < kSamplesPerFrame && samples < numSamples) - buffer[samples++] = _buffer[_bufferPos++]; - - // Bail out if done - if (endOfData()) - return samples; - - while (samples < numSamples && !endOfData()) { - if (!readNextFrame()) - return samples; - - // Copy the samples over - for (_bufferPos = 0; _bufferPos < kSamplesPerFrame && samples < numSamples; ) - buffer[samples++] = _buffer[_bufferPos++]; - } - - return samples; -} - -bool LinuxAudioCDStream::readNextFrame() { - // Fetch a frame from the queue - int16 *buffer; - - { - Common::StackLock lock(_mutex); - - // Nothing we can do if it's empty - if (_bufferQueue.empty()) - return false; - - buffer = _bufferQueue.pop(); - } - - memcpy(_buffer, buffer, kSamplesPerFrame * 2); - delete[] buffer; - _frame++; - return true; -} - -bool LinuxAudioCDStream::endOfData() const { - return getFrameCount(_startEntry.cdte_addr.msf) + _frame >= getFrameCount(_endEntry.cdte_addr.msf) && _bufferPos == kSamplesPerFrame; -} - -bool LinuxAudioCDStream::seek(const Audio::Timestamp &where) { - // Stop the timer - stopTimer(); - - // Clear anything out of the queue - emptyQueue(); - - // Convert to the frame number - // Really not much else needed - _bufferPos = kSamplesPerFrame; - _frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames(); - _bufferFrame = _frame; +bool LinuxAudioCDStream::readFrame(int frame, int16 *buffer) { + int seconds = frame / kFramesPerSecond; + frame %= kFramesPerSecond; + int minutes = seconds / kSecondsPerMinute; + seconds %= kSecondsPerMinute; + + // Request to read that frame + cdrom_read_audio readAudio; + readAudio.addr.msf.minute = minutes; + readAudio.addr.msf.second = seconds; + readAudio.addr.msf.frame = frame; + readAudio.addr_format = CDROM_MSF; + readAudio.nframes = 1; + readAudio.buf = reinterpret_cast<__u8*>(buffer); + + if (ioctl(_fd, CDROMREADAUDIO, &readAudio) < 0) + return false; - // Start the timer again - startTimer(); return true; } -Audio::Timestamp LinuxAudioCDStream::getLength() const { - return Audio::Timestamp(0, getFrameCount(_endEntry.cdte_addr.msf) - getFrameCount(_startEntry.cdte_addr.msf), 75); +uint LinuxAudioCDStream::getStartFrame() const { + return getFrameCount(_startEntry.cdte_addr.msf); } -void LinuxAudioCDStream::timerProc(void *refCon) { - static_cast(refCon)->onTimer(); +uint LinuxAudioCDStream::getEndFrame() const { + return getFrameCount(_endEntry.cdte_addr.msf); } -void LinuxAudioCDStream::onTimer() { - // The goal here is to do as much work in this timer instead - // of doing it in the readBuffer() call, which is the mixer. - - // If we're done, bail. - if (getFrameCount(_startEntry.cdte_addr.msf) + _bufferFrame >= getFrameCount(_endEntry.cdte_addr.msf)) - return; - - // Get a quick count of the number of items in the queue - // We don't care that much; we only need a quick estimate - _mutex.lock(); - uint32 queueCount = _bufferQueue.size(); - _mutex.unlock(); - - // If we have enough audio buffered, bail out - if (queueCount >= kBufferThreshold) - return; - - while (queueCount < kBufferThreshold && getFrameCount(_startEntry.cdte_addr.msf) + _bufferFrame < getFrameCount(_endEntry.cdte_addr.msf)) { - int16 *buffer = new int16[kSamplesPerFrame]; - - // Figure out the MSF of the frame we're looking for - int frame = _bufferFrame + getFrameCount(_startEntry.cdte_addr.msf); - - int seconds = frame / kFramesPerSecond; - frame %= kFramesPerSecond; - int minutes = seconds / kSecondsPerMinute; - seconds %= kSecondsPerMinute; - - // Request to read that frame - cdrom_read_audio readAudio; - readAudio.addr.msf.minute = minutes; - readAudio.addr.msf.second = seconds; - readAudio.addr.msf.frame = frame; - readAudio.addr_format = CDROM_MSF; - readAudio.nframes = 1; - readAudio.buf = reinterpret_cast<__u8*>(buffer); - - if (ioctl(_fd, CDROMREADAUDIO, &readAudio) < 0) { - warning("Failed to read audio"); - _bufferFrame = getFrameCount(_endEntry.cdte_addr.msf); - return; - } - - _bufferFrame++; - - // Now push the buffer onto the queue - Common::StackLock lock(_mutex); - _bufferQueue.push(buffer); - queueCount = _bufferQueue.size(); - } -} - -void LinuxAudioCDStream::startTimer() { - g_system->getTimerManager()->installTimerProc(timerProc, 10 * 1000, this, "LinuxAudioCDStream"); -} - -void LinuxAudioCDStream::stopTimer() { - g_system->getTimerManager()->removeTimerProc(timerProc); -} - -void LinuxAudioCDStream::emptyQueue() { - while (!_bufferQueue.empty()) - delete[] _bufferQueue.pop(); -} class LinuxAudioCDManager : public DefaultAudioCDManager { public: diff --git a/backends/audiocd/win32/win32-audiocd.cpp b/backends/audiocd/win32/win32-audiocd.cpp index e3fdcf6477..0b04a7ca5d 100644 --- a/backends/audiocd/win32/win32-audiocd.cpp +++ b/backends/audiocd/win32/win32-audiocd.cpp @@ -23,6 +23,7 @@ #include "backends/audiocd/win32/win32-audiocd.h" #include "audio/audiostream.h" +#include "backends/audiocd/audiocd-stream.h" #include "backends/audiocd/default/default-audiocd.h" #include "common/array.h" #include "common/config-manager.h" @@ -42,30 +43,11 @@ #include #endif -enum { - kLeadoutTrack = 0xAA -}; - -enum { - kBytesPerFrame = 2352, - kSamplesPerFrame = kBytesPerFrame / 2 -}; - -enum { - kSecondsPerMinute = 60, - kFramesPerSecond = 75 -}; - enum { // The CD-ROM pre-gap is 2s kPreGapFrames = kFramesPerSecond * 2 }; -enum { - // Keep about a second's worth of audio in the buffer - kBufferThreshold = kFramesPerSecond -}; - static int getFrameCount(const TRACK_DATA &data) { int time = data.Address[1]; time *= kSecondsPerMinute; @@ -75,35 +57,19 @@ static int getFrameCount(const TRACK_DATA &data) { return time; } -class Win32AudioCDStream : public Audio::SeekableAudioStream { +class Win32AudioCDStream : public AudioCDStream { public: Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry); ~Win32AudioCDStream(); - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return true; } - int getRate() const { return 44100; } - bool endOfData() const; - bool seek(const Audio::Timestamp &where); - Audio::Timestamp getLength() const; +protected: + uint getStartFrame() const; + uint getEndFrame() const; + bool readFrame(int frame, int16 *buffer); private: HANDLE _driveHandle; const TRACK_DATA &_startEntry, &_endEntry; - int16 _buffer[kSamplesPerFrame]; - int _frame; - uint _bufferPos; - - Common::Queue _bufferQueue; - int _bufferFrame; - Common::Mutex _mutex; - - bool readNextFrame(); - static void timerProc(void *refCon); - void onTimer(); - void emptyQueue(); - void startTimer(); - void stopTimer(); }; Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry) : @@ -113,150 +79,36 @@ Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEnt Win32AudioCDStream::~Win32AudioCDStream() { stopTimer(); - emptyQueue(); -} - -int Win32AudioCDStream::readBuffer(int16 *buffer, const int numSamples) { - int samples = 0; - - // See if any data is left first - while (_bufferPos < kSamplesPerFrame && samples < numSamples) - buffer[samples++] = _buffer[_bufferPos++]; - - // Bail out if done - if (endOfData()) - return samples; - - while (samples < numSamples && !endOfData()) { - if (!readNextFrame()) - return samples; - - // Copy the samples over - for (_bufferPos = 0; _bufferPos < kSamplesPerFrame && samples < numSamples; ) - buffer[samples++] = _buffer[_bufferPos++]; - } - - return samples; -} - -bool Win32AudioCDStream::readNextFrame() { - // Fetch a frame from the queue - int16 *buffer; - - { - Common::StackLock lock(_mutex); - - // Nothing we can do if it's empty - if (_bufferQueue.empty()) - return false; - - buffer = _bufferQueue.pop(); - } - - memcpy(_buffer, buffer, kSamplesPerFrame * 2); - delete[] buffer; - _frame++; - return true; -} - -bool Win32AudioCDStream::endOfData() const { - return getFrameCount(_startEntry) + _frame >= getFrameCount(_endEntry) && _bufferPos == kSamplesPerFrame; -} - -bool Win32AudioCDStream::seek(const Audio::Timestamp &where) { - // Stop the timer - stopTimer(); - - // Clear anything out of the queue - emptyQueue(); - - // Convert to the frame number - // Really not much else needed - _bufferPos = kSamplesPerFrame; - _frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames(); - _bufferFrame = _frame; - - // Start the timer again - startTimer(); - return true; } -Audio::Timestamp Win32AudioCDStream::getLength() const { - return Audio::Timestamp(0, getFrameCount(_endEntry) - getFrameCount(_startEntry), 75); +uint Win32AudioCDStream::getStartFrame() const { + return getFrameCount(_startEntry); } -void Win32AudioCDStream::timerProc(void *refCon) { - static_cast(refCon)->onTimer(); -} - -void Win32AudioCDStream::onTimer() { - // The goal here is to do as much work in this timer instead - // of doing it in the readBuffer() call, which is the mixer. - - // If we're done, bail. - if (getFrameCount(_startEntry) + _bufferFrame >= getFrameCount(_endEntry)) - return; - - // Get a quick count of the number of items in the queue - // We don't care that much; we only need a quick estimate - _mutex.lock(); - uint32 queueCount = _bufferQueue.size(); - _mutex.unlock(); - - // If we have enough audio buffered, bail out - if (queueCount >= kBufferThreshold) - return; - - while (queueCount < kBufferThreshold && getFrameCount(_startEntry) + _bufferFrame < getFrameCount(_endEntry)) { - int16 *buffer = new int16[kSamplesPerFrame]; - - // Figure out the MSF of the frame we're looking for - int frame = _bufferFrame + getFrameCount(_startEntry); - - // Request to read that frame - RAW_READ_INFO readAudio; - memset(&readAudio, 0, sizeof(readAudio)); - readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048; - readAudio.SectorCount = 1; - readAudio.TrackMode = CDDA; - - DWORD bytesReturned; - bool result = DeviceIoControl( - _driveHandle, - IOCTL_CDROM_RAW_READ, - &readAudio, - sizeof(readAudio), - buffer, - kBytesPerFrame, - &bytesReturned, - NULL); - if (!result) { - warning("Failed to retrieve CD sector %d: %d", frame, (int)GetLastError()); - _bufferFrame = getFrameCount(_endEntry) - getFrameCount(_startEntry); - return; - } - - _bufferFrame++; - - // Now push the buffer onto the queue - Common::StackLock lock(_mutex); - _bufferQueue.push(buffer); - queueCount = _bufferQueue.size(); - } +uint Win32AudioCDStream::getEndFrame() const { + return getFrameCount(_endFrame); } -void Win32AudioCDStream::startTimer() { - g_system->getTimerManager()->installTimerProc(timerProc, 10 * 1000, this, "Win32AudioCDStream"); -} +bool Win32AudioCDStream::readFrame(int frame, int16 *buffer) { + // Request to read that frame + RAW_READ_INFO readAudio; + memset(&readAudio, 0, sizeof(readAudio)); + readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048; + readAudio.SectorCount = 1; + readAudio.TrackMode = CDDA; -void Win32AudioCDStream::stopTimer() { - g_system->getTimerManager()->removeTimerProc(timerProc); + DWORD bytesReturned; + return DeviceIoControl( + _driveHandle, + IOCTL_CDROM_RAW_READ, + &readAudio, + sizeof(readAudio), + buffer, + kBytesPerFrame, + &bytesReturned, + NULL); } -void Win32AudioCDStream::emptyQueue() { - while (!_bufferQueue.empty()) - delete[] _bufferQueue.pop(); -} class Win32AudioCDManager : public DefaultAudioCDManager { public: diff --git a/backends/module.mk b/backends/module.mk index 8abab65f14..3f7620e2a8 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -3,6 +3,7 @@ MODULE := backends MODULE_OBJS := \ base-backend.o \ modular-backend.o \ + audiocd/audiocd-stream.o \ audiocd/default/default-audiocd.o \ events/default/default-events.o \ fs/abstract-fs.o \ -- cgit v1.2.3