| 1 | /* |
| 2 | SDL - Simple DirectMedia Layer |
| 3 | Copyright (C) 1997-2009 Sam Lantinga |
| 4 | |
| 5 | This library is free software; you can redistribute it and/or |
| 6 | modify it under the terms of the GNU Library General Public |
| 7 | License as published by the Free Software Foundation; either |
| 8 | version 2 of the License, or (at your option) any later version. |
| 9 | |
| 10 | This library is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | Library General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU Library General Public |
| 16 | License along with this library; if not, write to the Free |
| 17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 18 | |
| 19 | Sam Lantinga |
| 20 | slouken@libsdl.org |
| 21 | */ |
| 22 | #include "SDL_config.h" |
| 23 | |
| 24 | /* This is the Mac OS X / CoreAudio specific header for the SDL CD-ROM API |
| 25 | Contributed by Darrell Walisser and Max Horn |
| 26 | */ |
| 27 | |
| 28 | /*********************************************************************************** |
| 29 | Implementation Notes |
| 30 | ********************* |
| 31 | |
| 32 | This code has several limitations currently (all of which are proabaly fixable): |
| 33 | |
| 34 | 1. A CD-ROM device is inferred from a mounted cdfs volume, so device 0 is |
| 35 | not necessarily the first CD-ROM device on the system. (Somewhat easy to fix |
| 36 | by useing the device name from the volume id's to reorder the volumes) |
| 37 | |
| 38 | 2. You can only open and control 1 CD-ROM device at a time. (Challenging to fix, |
| 39 | due to extensive code restructuring) |
| 40 | |
| 41 | 3. The status reported by SDL_CDStatus only changes to from CD_PLAYING to CD_STOPPED in |
| 42 | 1-second intervals (because the audio is buffered in 1-second chunks) If |
| 43 | the audio data is less than 1 second, the remainder is filled with silence. |
| 44 | |
| 45 | If you need to play sequences back-to-back that are less that 1 second long, |
| 46 | use the frame position to determine when to play the next sequence, instead |
| 47 | of SDL_CDStatus. |
| 48 | |
| 49 | This may be possible to fix with a clever usage of the AudioUnit API. |
| 50 | |
| 51 | 4. When new volumes are inserted, our volume information is not updated. The only way |
| 52 | to refresh this information is to reinit the CD-ROM subsystem of SDL. To fix this, |
| 53 | one would probably have to fix point 1 above first, then figure out how to register |
| 54 | for a notification when new media is mounted in order to perform an automatic |
| 55 | rescan for cdfs volumes. |
| 56 | |
| 57 | |
| 58 | |
| 59 | So, here comes a description of how this all works. |
| 60 | |
| 61 | < Initializing > |
| 62 | |
| 63 | To get things rolling, we have to locate mounted volumes that contain |
| 64 | audio (since nearly all Macs don't have analog audio-in on the sound card). |
| 65 | That's easy, since these volumes have a flag that indicates this special |
| 66 | filesystem. See DetectAudioCDVolumes() in CDPlayer.cpp for this code. |
| 67 | |
| 68 | Next, we parse the invisible .TOC.plist in the root of the volume, which gets us |
| 69 | the track information (number, offset, length, leadout, etc). See ReadTOCData() in |
| 70 | CDPlayer.cpp for the skinny on this. |
| 71 | |
| 72 | |
| 73 | < The Playback Loop > |
| 74 | |
| 75 | Now come the tricky parts. Let's start with basic audio playback. When a frame |
| 76 | range to play is requested, we must first find the .aiff files on the volume, |
| 77 | hopefully in the right order. Since these files all begin with a number "1 Audio Track", |
| 78 | etc, this is used to determine the correct track order. |
| 79 | |
| 80 | Once all files are determined, we have to find what file corresponds to the start |
| 81 | and length parameter to SDL_SYS_CDPlay(). Again, this is quite simple by walking the |
| 82 | cdrom's track list. At this point, we also save the offset to the next track and frames |
| 83 | remaining, if we're going to have to play another file after the first one. See |
| 84 | GetFileForOffset() for this code. |
| 85 | |
| 86 | At this point we have all info needed to start playback, so we hand off to the LoadFile() |
| 87 | function, which proceeds to do its magic and plays back the file. |
| 88 | |
| 89 | When the file is finished playing, CompletionProc() is invoked, at which time we can |
| 90 | play the next file if the previously saved next track and frames remaining |
| 91 | indicates that we should. |
| 92 | |
| 93 | |
| 94 | < Magic > |
| 95 | |
| 96 | OK, so it's not really magic, but since I don't fully understand all the hidden details it |
| 97 | seems like it to me ;-) The API's involved are the AudioUnit and AudioFile API's. These |
| 98 | appear to be an extension of CoreAudio for creating modular playback and f/x entities. |
| 99 | The important thing is that CPU usage is very low and reliability is very high. You'd |
| 100 | be hard-pressed to find a way to stutter the playback with other CPU-intensive tasks. |
| 101 | |
| 102 | One part of this magic is that it uses multiple threads, which carries the usual potential |
| 103 | for disaster if not handled carefully. Playback currently requires 4 additional threads: |
| 104 | 1. The coreaudio runloop thread |
| 105 | 2. The coreaudio device i/o thread |
| 106 | 3. The file streaming thread |
| 107 | 4. The notification/callback thread |
| 108 | |
| 109 | The first 2 threads are necessary evil - CoreAudio creates this no matter what the situation |
| 110 | is (even the SDL sound implementation creates theses suckers). The last two are are created |
| 111 | by us. |
| 112 | |
| 113 | The file is streamed from disk using a threaded double-buffer approach. |
| 114 | This way, the high latency operation of reading from disk can be performed without interrupting |
| 115 | the real-time device thread (which amounts to avoiding dropouts). The device thread grabs the |
| 116 | buffer that isn't being read and sends it to the CoreAudio mixer where it eventually gets |
| 117 | to the sound card. |
| 118 | |
| 119 | The device thread posts a notification when the file streaming thread is out of data. This |
| 120 | notification must be handled in a separate thread to avoid potential deadlock in the |
| 121 | device thread. That's where the notification thread comes in. This thread is signaled |
| 122 | whenever a notification needs to be processed, so another file can be played back if need be. |
| 123 | |
| 124 | The API in CDPlayer.cpp contains synchronization because otherwise both the notification thread |
| 125 | and main thread (or another other thread using the SDL CD api) can potentially call it at the same time. |
| 126 | |
| 127 | ************************************************************************************/ |
| 128 | |
| 129 | |
| 130 | #include "SDL_cdrom.h" |
| 131 | #include "../SDL_syscdrom.h" |
| 132 | |
| 133 | #include "CDPlayer.h" |
| 134 | |
| 135 | #define kErrorFakeDevice "Error: Cannot proceed since we're faking a CD-ROM device. Reinit the CD-ROM subsystem to scan for new volumes." |
| 136 | |