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

linux-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 // Enable all forbidden symbols to allow us to include and use necessary APIs.
00046 #define FORBIDDEN_SYMBOL_ALLOW_ALL
00047 
00048 #include "backends/audiocd/linux/linux-audiocd.h"
00049 
00050 #ifdef USE_LINUXCD
00051 
00052 #include "backends/audiocd/audiocd-stream.h"
00053 #include "backends/audiocd/default/default-audiocd.h"
00054 #include "common/array.h"
00055 #include "common/config-manager.h"
00056 #include "common/str.h"
00057 #include "common/debug.h"
00058 
00059 #include <errno.h>
00060 #include <fcntl.h>
00061 #include <unistd.h>
00062 #include <linux/cdrom.h>
00063 #include <sys/ioctl.h>
00064 #include <sys/stat.h>
00065 #include <sys/sysmacros.h>
00066 #include <sys/types.h>
00067 
00068 enum {
00069     kLeadoutTrack = 0xAA
00070 };
00071 
00072 enum {
00073     kBytesPerFrame = 2352,
00074     kSamplesPerFrame = kBytesPerFrame / 2
00075 };
00076 
00077 enum {
00078     kSecondsPerMinute = 60,
00079     kFramesPerSecond = 75
00080 };
00081 
00082 enum {
00083     // Keep about a second's worth of audio in the buffer
00084     kBufferThreshold = kFramesPerSecond
00085 };
00086 
00087 static int getFrameCount(const cdrom_msf0 &msf) {
00088     int time = msf.minute;
00089     time *= kSecondsPerMinute;
00090     time += msf.second;
00091     time *= kFramesPerSecond;
00092     time += msf.frame;
00093     return time;
00094 }
00095 
00096 // Helper function to convert an error code into a human-readable message
00097 static Common::String getErrorMessage(int errorCode) {
00098     char buf[256];
00099     buf[0] = 0;
00100 
00101 #ifdef _GNU_SOURCE
00102     // glibc sucks
00103     return Common::String(strerror_r(errorCode, buf, sizeof(buf)));
00104 #else
00105     strerror_r(errorCode, buf, sizeof(buf));
00106     return Common::String(buf);
00107 #endif
00108 }
00109 
00110 class LinuxAudioCDStream : public AudioCDStream {
00111 public:
00112     LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry);
00113     ~LinuxAudioCDStream();
00114 
00115 protected:
00116     uint getStartFrame() const;
00117     uint getEndFrame() const;
00118     bool readFrame(int frame, int16 *buffer);
00119 
00120 private:
00121     int _fd;
00122     const cdrom_tocentry &_startEntry, &_endEntry;
00123 };
00124 
00125 LinuxAudioCDStream::LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry) :
00126     _fd(fd), _startEntry(startEntry), _endEntry(endEntry) {
00127     // We fill the buffer here already to prevent any out of sync issues due
00128     // to the CD not yet having spun up.
00129     startTimer(true);
00130 }
00131 
00132 LinuxAudioCDStream::~LinuxAudioCDStream() {
00133     stopTimer();
00134 }
00135 
00136 bool LinuxAudioCDStream::readFrame(int frame, int16 *buffer) {
00137     // Create the argument
00138     union {
00139         cdrom_msf msf;
00140         char buffer[kBytesPerFrame];
00141     } arg;
00142 
00143     int seconds = frame / kFramesPerSecond;
00144     frame %= kFramesPerSecond;
00145     int minutes = seconds / kSecondsPerMinute;
00146     seconds %= kSecondsPerMinute;
00147 
00148     // Request to read that frame
00149     // We don't use CDROMREADAUDIO, as it seems to cause kernel
00150     // panics on ejecting discs. Probably bad to eject the disc
00151     // while playing, but at least let's try to prevent that case.
00152     arg.msf.cdmsf_min0 = minutes;
00153     arg.msf.cdmsf_sec0 = seconds;
00154     arg.msf.cdmsf_frame0 = frame;
00155     // The "end" part is irrelevant (why isn't cdrom_msf0 the type
00156     // instead?)
00157 
00158     if (ioctl(_fd, CDROMREADRAW, &arg) < 0) {
00159         warning("Failed to CD read audio: %s", getErrorMessage(errno).c_str());
00160         return false;
00161     }
00162 
00163     memcpy(buffer, arg.buffer, kBytesPerFrame);
00164     return true;
00165 }
00166 
00167 uint LinuxAudioCDStream::getStartFrame() const {
00168     return getFrameCount(_startEntry.cdte_addr.msf);
00169 }
00170 
00171 uint LinuxAudioCDStream::getEndFrame() const {
00172     return getFrameCount(_endEntry.cdte_addr.msf);
00173 }
00174 
00175 
00176 class LinuxAudioCDManager : public DefaultAudioCDManager {
00177 public:
00178     LinuxAudioCDManager();
00179     ~LinuxAudioCDManager();
00180 
00181     bool open() override;
00182     void close() override;
00183     bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
00184             Audio::Mixer::SoundType soundType) override;
00185 
00186 protected:
00187     bool openCD(int drive) override;
00188     bool openCD(const Common::String &drive) override;
00189 
00190 private:
00191     struct Device {
00192         Device(const Common::String &n, dev_t d) : name(n), device(d) {}
00193         Common::String name;
00194         dev_t device;
00195     };
00196 
00197     typedef Common::Array<Device> DeviceList;
00198     DeviceList scanDevices();
00199     bool tryAddDrive(DeviceList &devices, const Common::String &drive);
00200     bool tryAddDrive(DeviceList &devices, const Common::String &drive, dev_t device);
00201     bool tryAddDrive(DeviceList &devices, dev_t device);
00202     bool tryAddPath(DeviceList &devices, const Common::String &path);
00203     bool tryAddGamePath(DeviceList &devices);
00204     bool loadTOC();
00205     static bool hasDevice(const DeviceList &devices, dev_t device);
00206 
00207     int _fd;
00208     cdrom_tochdr _tocHeader;
00209     Common::Array<cdrom_tocentry> _tocEntries;
00210 };
00211 
00212 static bool isTrayEmpty(int errorNumber) {
00213     switch (errorNumber) {
00214     case EIO:
00215     case ENOENT:
00216     case EINVAL:
00217 #ifdef ENOMEDIUM
00218     case ENOMEDIUM:
00219 #endif
00220         return true;
00221     }
00222 
00223     return false;
00224 }
00225 
00226 LinuxAudioCDManager::LinuxAudioCDManager() {
00227     _fd = -1;
00228     memset(&_tocHeader, 0, sizeof(_tocHeader));
00229 }
00230 
00231 LinuxAudioCDManager::~LinuxAudioCDManager() {
00232     close();
00233 }
00234 
00235 bool LinuxAudioCDManager::open() {
00236     close();
00237 
00238     if (openRealCD())
00239         return true;
00240 
00241     return DefaultAudioCDManager::open();
00242 }
00243 
00244 void LinuxAudioCDManager::close() {
00245     DefaultAudioCDManager::close();
00246 
00247     if (_fd < 0)
00248         return;
00249 
00250     ::close(_fd);
00251     memset(&_tocHeader, 0, sizeof(_tocHeader));
00252     _tocEntries.clear();
00253 }
00254 
00255 bool LinuxAudioCDManager::openCD(int drive) {
00256     DeviceList devices = scanDevices();
00257     if (drive >= (int)devices.size())
00258         return false;
00259 
00260     _fd = ::open(devices[drive].name.c_str(), O_RDONLY | O_NONBLOCK, 0);
00261     if (_fd < 0)
00262         return false;
00263 
00264     if (!loadTOC()) {
00265         close();
00266         return false;
00267     }
00268 
00269     return true;
00270 }
00271 
00272 bool LinuxAudioCDManager::openCD(const Common::String &drive) {
00273     DeviceList devices;
00274     if (!tryAddDrive(devices, drive) && !tryAddPath(devices, drive))
00275         return false;
00276 
00277     _fd = ::open(devices[0].name.c_str(), O_RDONLY | O_NONBLOCK, 0);
00278     if (_fd < 0)
00279         return false;
00280 
00281     if (!loadTOC()) {
00282         close();
00283         return false;
00284     }
00285 
00286     return true;
00287 }
00288 
00289 bool LinuxAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
00290         Audio::Mixer::SoundType soundType) {
00291     // Prefer emulation
00292     if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
00293         return true;
00294 
00295     // If we're set to only emulate, or have no CD drive, return here
00296     if (onlyEmulate || _fd < 0)
00297         return false;
00298 
00299     // HACK: For now, just assume that track number is right
00300     // That only works because ScummVM uses the wrong track number anyway
00301 
00302     if (track >= (int)_tocEntries.size() - 1) {
00303         warning("No such track %d", track);
00304         return false;
00305     }
00306 
00307     // Bail if the track isn't an audio track
00308     if ((_tocEntries[track].cdte_ctrl & 0x04) != 0) {
00309         warning("Track %d is not audio", track);
00310         return false;
00311     }
00312 
00313     // Create the AudioStream and play it
00314     debug(1, "Playing CD track %d", track);
00315 
00316     Audio::SeekableAudioStream *audioStream = new LinuxAudioCDStream(_fd, _tocEntries[track], _tocEntries[track + 1]);
00317 
00318     Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
00319     Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75);
00320 
00321     // Fake emulation since we're really playing an AudioStream
00322     _emulating = true;
00323 
00324     _mixer->playStream(
00325         soundType,
00326         &_handle,
00327         Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops),
00328         -1,
00329         _cd.volume,
00330         _cd.balance,
00331         DisposeAfterUse::YES,
00332         true);
00333 
00334     return true;
00335 }
00336 
00337 LinuxAudioCDManager::DeviceList LinuxAudioCDManager::scanDevices() {
00338     DeviceList devices;
00339 
00340     // Try to use the game's path first as the device
00341     tryAddGamePath(devices);
00342 
00343     // Try adding the default CD-ROM
00344     tryAddDrive(devices, "/dev/cdrom");
00345 
00346     // TODO: Try others?
00347 
00348     return devices;
00349 }
00350 
00351 bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::String &drive) {
00352     struct stat stbuf;
00353     if (stat(drive.c_str(), &stbuf) < 0)
00354         return false;
00355 
00356     // Must be a character or block device
00357     if (!S_ISCHR(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode))
00358         return false;
00359 
00360     return tryAddDrive(devices, drive, stbuf.st_rdev);
00361 }
00362 
00363 bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::String &drive, dev_t device) {
00364     if (hasDevice(devices, device))
00365         return true;
00366 
00367     // Try opening the device and seeing if it is a CD-ROM drve
00368     int fd = ::open(drive.c_str(), O_RDONLY | O_NONBLOCK, 0);
00369     if (fd >= 0) {
00370         cdrom_subchnl info;
00371         info.cdsc_format = CDROM_MSF;
00372 
00373         bool isCD = ioctl(fd, CDROMSUBCHNL, &info) == 0 || isTrayEmpty(errno);
00374         ::close(fd);
00375         if (isCD) {
00376             devices.push_back(Device(drive, device));
00377             return true;
00378         }
00379     }
00380 
00381     return false;
00382 }
00383 
00384 bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, dev_t device) {
00385     // Construct the block name
00386     // TODO: libblkid's blkid_devno_to_devname is exactly what we look for.
00387     // This requires an external dependency though.
00388     Common::String name = Common::String::format("/dev/block/%d:%d", major(device), minor(device));
00389 
00390     return tryAddDrive(devices, name, device);
00391 }
00392 
00393 bool LinuxAudioCDManager::tryAddPath(DeviceList &devices, const Common::String &path) {
00394     struct stat stbuf;
00395     if (stat(path.c_str(), &stbuf) < 0)
00396         return false;
00397 
00398     return tryAddDrive(devices, stbuf.st_dev);
00399 }
00400 
00401 bool LinuxAudioCDManager::tryAddGamePath(DeviceList &devices) {
00402     if (!ConfMan.hasKey("path"))
00403         return false;
00404 
00405     return tryAddPath(devices, ConfMan.get("path"));
00406 }
00407 
00408 bool LinuxAudioCDManager::loadTOC() {
00409     if (_fd < 0)
00410         return false;
00411 
00412     if (ioctl(_fd, CDROMREADTOCHDR, &_tocHeader) < 0)
00413         return false;
00414 
00415     debug(4, "CD: Start Track: %d, End Track %d", _tocHeader.cdth_trk0, _tocHeader.cdth_trk1);
00416 
00417     for (int i = _tocHeader.cdth_trk0; i <= _tocHeader.cdth_trk1; i++) {
00418         cdrom_tocentry entry;
00419         memset(&entry, 0, sizeof(entry));
00420         entry.cdte_track = i;
00421         entry.cdte_format = CDROM_MSF;
00422 
00423         if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0)
00424             return false;
00425 
00426 #if 0
00427         debug("Entry:");
00428         debug("\tTrack: %d", entry.cdte_track);
00429         debug("\tAdr: %d", entry.cdte_adr);
00430         debug("\tCtrl: %d", entry.cdte_ctrl);
00431         debug("\tFormat: %d", entry.cdte_format);
00432         debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame);
00433         debug("\tMode: %d\n", entry.cdte_datamode);
00434 #endif
00435 
00436         _tocEntries.push_back(entry);
00437     }
00438 
00439     // Fetch the leadout so we can get the length of the last frame
00440     cdrom_tocentry entry;
00441     memset(&entry, 0, sizeof(entry));
00442     entry.cdte_track = kLeadoutTrack;
00443     entry.cdte_format = CDROM_MSF;
00444 
00445     if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0)
00446         return false;
00447 
00448 #if 0
00449     debug("Lead out:");
00450     debug("\tTrack: %d", entry.cdte_track);
00451     debug("\tAdr: %d", entry.cdte_adr);
00452     debug("\tCtrl: %d", entry.cdte_ctrl);
00453     debug("\tFormat: %d", entry.cdte_format);
00454     debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame);
00455     debug("\tMode: %d\n", entry.cdte_datamode);
00456 #endif
00457 
00458     _tocEntries.push_back(entry);
00459     return true;
00460 }
00461 
00462 bool LinuxAudioCDManager::hasDevice(const DeviceList &devices, dev_t device) {
00463     for (DeviceList::const_iterator it = devices.begin(); it != devices.end(); it++)
00464         if (it->device == device)
00465             return true;
00466 
00467     return false;
00468 }
00469 
00470 AudioCDManager *createLinuxAudioCDManager() {
00471     return new LinuxAudioCDManager();
00472 }
00473 
00474 #endif // USE_LINUXCD


Generated on Sat Jun 15 2019 05:01:02 for ResidualVM by doxygen 1.7.1
curved edge   curved edge