ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

win32-audiocd.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  * Original license header:
00022  *
00023  * Cabal - Legacy Game Implementations
00024  *
00025  * Cabal is the legal property of its developers, whose names
00026  * are too numerous to list here. Please refer to the COPYRIGHT
00027  * file distributed with this source distribution.
00028  *
00029  * This program is free software; you can redistribute it and/or
00030  * modify it under the terms of the GNU General Public License
00031  * as published by the Free Software Foundation; either version 2
00032  * of the License, or (at your option) any later version.
00033  *
00034  * This program is distributed in the hope that it will be useful,
00035  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00036  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00037  * GNU General Public License for more details.
00038  *
00039  * You should have received a copy of the GNU General Public License
00040  * along with this program; if not, write to the Free Software
00041  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00042  *
00043  */
00044 
00045 #ifdef WIN32
00046 
00047 #define WIN32_LEAN_AND_MEAN
00048 #include <windows.h>
00049 
00050 #include "backends/audiocd/win32/win32-audiocd.h"
00051 
00052 #include "audio/audiostream.h"
00053 #include "backends/audiocd/audiocd-stream.h"
00054 #include "backends/audiocd/default/default-audiocd.h"
00055 #include "common/array.h"
00056 #include "common/config-manager.h"
00057 #include "common/debug.h"
00058 #include "common/mutex.h"
00059 #include "common/queue.h"
00060 #include "common/str.h"
00061 #include "common/timer.h"
00062 
00063 #include <winioctl.h>
00064 #if _MSC_VER < 1900
00065 // WORKAROUND: Older versions of MSVC might not supply DDK headers by default.
00066 // Visual Studio 2015 contains the required headers. We use a compatability
00067 // header from MinGW's w32api for all older versions.
00068 // TODO: Limit this to the Visual Studio versions which actually require this.
00069 #include "msvc/ntddcdrm.h"
00070 #elif defined(__MINGW32__) && !defined(__MINGW64__)
00071 // Classic MinGW uses non standard paths for DDK headers.
00072 #include <ddk/ntddcdrm.h>
00073 #else
00074 #include <ntddcdrm.h>
00075 #endif
00076 
00077 class Win32AudioCDStream : public AudioCDStream {
00078 public:
00079     Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry);
00080     ~Win32AudioCDStream();
00081 
00082 protected:
00083     uint getStartFrame() const;
00084     uint getEndFrame() const;
00085     bool readFrame(int frame, int16 *buffer);
00086 
00087 private:
00088     HANDLE _driveHandle;
00089     const TRACK_DATA &_startEntry, &_endEntry;
00090 
00091     enum {
00092         // The CD-ROM pre-gap is 2s
00093         kPreGapFrames = kFramesPerSecond * 2
00094     };
00095 
00096     static int getFrameCount(const TRACK_DATA &data) {
00097         int time = data.Address[1];
00098         time *= kSecondsPerMinute;
00099         time += data.Address[2];
00100         time *= kFramesPerSecond;
00101         time += data.Address[3];
00102         return time;
00103     }
00104 };
00105 
00106 Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry) :
00107     _driveHandle(handle), _startEntry(startEntry), _endEntry(endEntry) {
00108     // We fill the buffer here already to prevent any out of sync issues due
00109     // to the CD not yet having spun up.
00110     startTimer(true);
00111 }
00112 
00113 Win32AudioCDStream::~Win32AudioCDStream() {
00114     stopTimer();
00115 }
00116 
00117 uint Win32AudioCDStream::getStartFrame() const {
00118     return getFrameCount(_startEntry);
00119 }
00120 
00121 uint Win32AudioCDStream::getEndFrame() const {
00122     return getFrameCount(_endEntry);
00123 }
00124 
00125 bool Win32AudioCDStream::readFrame(int frame, int16 *buffer) {
00126     // Request to read that frame
00127     RAW_READ_INFO readAudio;
00128     memset(&readAudio, 0, sizeof(readAudio));
00129     readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048;
00130     readAudio.SectorCount = 1;
00131     readAudio.TrackMode = CDDA;
00132 
00133     DWORD bytesReturned;
00134     return DeviceIoControl(
00135                _driveHandle,
00136                IOCTL_CDROM_RAW_READ,
00137                &readAudio,
00138                sizeof(readAudio),
00139                buffer,
00140                kBytesPerFrame,
00141                &bytesReturned,
00142                NULL);
00143 }
00144 
00145 
00146 class Win32AudioCDManager : public DefaultAudioCDManager {
00147 public:
00148     Win32AudioCDManager();
00149     ~Win32AudioCDManager();
00150 
00151     bool open() override;
00152     void close() override;
00153     bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
00154             Audio::Mixer::SoundType soundType) override;
00155 
00156 protected:
00157     bool openCD(int drive) override;
00158     bool openCD(const Common::String &drive) override;
00159 
00160 private:
00161     bool loadTOC();
00162 
00163     typedef Common::Array<char> DriveList;
00164     DriveList detectDrives();
00165     bool tryAddDrive(char drive, DriveList &drives);
00166 
00167     HANDLE _driveHandle;
00168     int _firstTrack, _lastTrack;
00169     Common::Array<TRACK_DATA> _tocEntries;
00170 };
00171 
00172 Win32AudioCDManager::Win32AudioCDManager() {
00173     _driveHandle = INVALID_HANDLE_VALUE;
00174     _firstTrack = _lastTrack = 0;
00175 }
00176 
00177 Win32AudioCDManager::~Win32AudioCDManager() {
00178     close();
00179 }
00180 
00181 bool Win32AudioCDManager::open() {
00182     close();
00183 
00184     if (openRealCD())
00185         return true;
00186 
00187     return DefaultAudioCDManager::open();
00188 }
00189 
00190 bool Win32AudioCDManager::openCD(int drive) {
00191     // Fetch the drive list
00192     DriveList drives = detectDrives();
00193     if (drive >= (int)drives.size())
00194         return false;
00195 
00196     debug(1, "Opening CD drive %c:\\", drives[drive]);
00197 
00198     // Construct the drive path and try to open it
00199     Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[drive]);
00200     _driveHandle = CreateFileA(drivePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
00201     if (_driveHandle == INVALID_HANDLE_VALUE) {
00202         warning("Failed to open drive %c:\\, error %d", drives[drive], (int)GetLastError());
00203         return false;
00204     }
00205 
00206     if (!loadTOC()) {
00207         close();
00208         return false;
00209     }
00210 
00211     return true;
00212 }
00213 
00214 bool Win32AudioCDManager::openCD(const Common::String &drive) {
00215     // Just some bounds checking
00216     if (drive.empty() || drive.size() > 3)
00217         return false;
00218 
00219     if (!Common::isAlpha(drive[0]) || drive[1] != ':')
00220         return false;
00221 
00222     if (drive[2] != 0 && drive[2] != '\\')
00223         return false;
00224 
00225     DriveList drives;
00226     if (!tryAddDrive(toupper(drive[0]), drives))
00227         return false;
00228 
00229     // Construct the drive path and try to open it
00230     Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[0]);
00231     _driveHandle = CreateFileA(drivePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
00232     if (_driveHandle == INVALID_HANDLE_VALUE) {
00233         warning("Failed to open drive %c:\\, error %d", drives[0], (int)GetLastError());
00234         return false;
00235     }
00236 
00237     if (!loadTOC()) {
00238         close();
00239         return false;
00240     }
00241 
00242     return true;
00243 }
00244 
00245 void Win32AudioCDManager::close() {
00246     DefaultAudioCDManager::close();
00247 
00248     if (_driveHandle != INVALID_HANDLE_VALUE) {
00249         CloseHandle(_driveHandle);
00250         _driveHandle = INVALID_HANDLE_VALUE;
00251     }
00252 
00253     _firstTrack = _lastTrack = 0;
00254     _tocEntries.clear();
00255 }
00256 
00257 bool Win32AudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
00258         Audio::Mixer::SoundType soundType) {
00259     // Prefer emulation
00260     if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
00261         return true;
00262 
00263     // If we're set to only emulate, or have no CD drive, return here
00264     if (onlyEmulate || _driveHandle == INVALID_HANDLE_VALUE)
00265         return false;
00266 
00267     // HACK: For now, just assume that track number is right
00268     // That only works because ScummVM uses the wrong track number anyway
00269 
00270     if (track >= (int)_tocEntries.size() - 1) {
00271         warning("No such track %d", track);
00272         return false;
00273     }
00274 
00275     // Bail if the track isn't an audio track
00276     if ((_tocEntries[track].Control & 0x04) != 0) {
00277         warning("Track %d is not audio", track);
00278         return false;
00279     }
00280 
00281     // Create the AudioStream and play it
00282     debug(1, "Playing CD track %d", track);
00283 
00284     Audio::SeekableAudioStream *audioStream = new Win32AudioCDStream(_driveHandle, _tocEntries[track], _tocEntries[track + 1]);
00285 
00286     Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
00287     Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75);
00288 
00289     // Fake emulation since we're really playing an AudioStream
00290     _emulating = true;
00291 
00292     _mixer->playStream(
00293         soundType,
00294         &_handle,
00295         Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops),
00296         -1,
00297         _cd.volume,
00298         _cd.balance,
00299         DisposeAfterUse::YES,
00300         true);
00301     return true;
00302 }
00303 
00304 bool Win32AudioCDManager::loadTOC() {
00305     CDROM_READ_TOC_EX tocRequest;
00306     memset(&tocRequest, 0, sizeof(tocRequest));
00307     tocRequest.Format = CDROM_READ_TOC_EX_FORMAT_TOC;
00308     tocRequest.Msf = 1;
00309     tocRequest.SessionTrack = 0;
00310 
00311     DWORD bytesReturned;
00312     CDROM_TOC tocData;
00313     bool result = DeviceIoControl(
00314                       _driveHandle,
00315                       IOCTL_CDROM_READ_TOC_EX,
00316                       &tocRequest,
00317                       sizeof(tocRequest),
00318                       &tocData,
00319                       sizeof(tocData),
00320                       &bytesReturned,
00321                       NULL);
00322     if (!result) {
00323         debug("Failed to query the CD TOC: %d", (int)GetLastError());
00324         return false;
00325     }
00326 
00327     _firstTrack = tocData.FirstTrack;
00328     _lastTrack = tocData.LastTrack;
00329 #if 0
00330     debug("First Track: %d", tocData.FirstTrack);
00331     debug("Last Track: %d", tocData.LastTrack);
00332 #endif
00333 
00334     for (uint32 i = 0; i < (bytesReturned - 4) / sizeof(TRACK_DATA); i++)
00335         _tocEntries.push_back(tocData.TrackData[i]);
00336 
00337 #if 0
00338     for (uint32 i = 0; i < _tocEntries.size(); i++) {
00339         const TRACK_DATA &entry = _tocEntries[i];
00340         debug("Entry:");
00341         debug("\tTrack: %d", entry.TrackNumber);
00342         debug("\tAdr: %d", entry.Adr);
00343         debug("\tCtrl: %d", entry.Control);
00344         debug("\tMSF: %d:%d:%d\n", entry.Address[1], entry.Address[2], entry.Address[3]);
00345     }
00346 #endif
00347 
00348     return true;
00349 }
00350 
00351 Win32AudioCDManager::DriveList Win32AudioCDManager::detectDrives() {
00352     DriveList drives;
00353 
00354     // Try to get the game path's drive
00355     char gameDrive = 0;
00356     if (ConfMan.hasKey("path")) {
00357         Common::String gamePath = ConfMan.get("path");
00358         char fullPath[MAX_PATH];
00359         DWORD result = GetFullPathNameA(gamePath.c_str(), sizeof(fullPath), fullPath, 0);
00360 
00361         if (result > 0 && result < sizeof(fullPath) && Common::isAlpha(fullPath[0]) && fullPath[1] == ':' && tryAddDrive(toupper(fullPath[0]), drives))
00362             gameDrive = drives[0];
00363     }
00364 
00365     // Try adding the rest of the drives
00366     for (char drive = 'A'; drive <= 'Z'; drive++)
00367         if (drive != gameDrive)
00368             tryAddDrive(drive, drives);
00369 
00370     return drives;
00371 }
00372 
00373 bool Win32AudioCDManager::tryAddDrive(char drive, DriveList &drives) {
00374     Common::String drivePath = Common::String::format("%c:\\", drive);
00375 
00376     // Ensure it's an actual CD drive
00377     if (GetDriveTypeA(drivePath.c_str()) != DRIVE_CDROM)
00378         return false;
00379 
00380     debug(2, "Detected drive %c:\\ as a CD drive", drive);
00381     drives.push_back(drive);
00382     return true;
00383 }
00384 
00385 AudioCDManager *createWin32AudioCDManager() {
00386     return new Win32AudioCDManager();
00387 }
00388 
00389 #endif // WIN32


Generated on Sat May 18 2019 05:01:29 for ResidualVM by doxygen 1.7.1
curved edge   curved edge