e14743d1 |
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 | |