|
1 /* |
|
2 SDL - Simple DirectMedia Layer |
|
3 Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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 #include "CDPlayer.h" |
|
25 #include "AudioFilePlayer.h" |
|
26 #include "SDLOSXCAGuard.h" |
|
27 |
|
28 /* we're exporting these functions into C land for SDL_syscdrom.c */ |
|
29 /*extern "C" {*/ |
|
30 |
|
31 /*/////////////////////////////////////////////////////////////////////////// |
|
32 Constants |
|
33 //////////////////////////////////////////////////////////////////////////*/ |
|
34 |
|
35 #define kAudioCDFilesystemID (UInt16)(('J' << 8) | 'H') /* 'JH'; this avoids compiler warning */ |
|
36 |
|
37 /* XML PList keys */ |
|
38 #define kRawTOCDataString "Format 0x02 TOC Data" |
|
39 #define kSessionsString "Sessions" |
|
40 #define kSessionTypeString "Session Type" |
|
41 #define kTrackArrayString "Track Array" |
|
42 #define kFirstTrackInSessionString "First Track" |
|
43 #define kLastTrackInSessionString "Last Track" |
|
44 #define kLeadoutBlockString "Leadout Block" |
|
45 #define kDataKeyString "Data" |
|
46 #define kPointKeyString "Point" |
|
47 #define kSessionNumberKeyString "Session Number" |
|
48 #define kStartBlockKeyString "Start Block" |
|
49 |
|
50 /*/////////////////////////////////////////////////////////////////////////// |
|
51 Globals |
|
52 //////////////////////////////////////////////////////////////////////////*/ |
|
53 |
|
54 #pragma mark -- Globals -- |
|
55 |
|
56 static int playBackWasInit = 0; |
|
57 static AudioUnit theUnit; |
|
58 static AudioFilePlayer* thePlayer = NULL; |
|
59 static CDPlayerCompletionProc completionProc = NULL; |
|
60 static SDL_mutex *apiMutex = NULL; |
|
61 static SDL_sem *callbackSem; |
|
62 static SDL_CD* theCDROM; |
|
63 |
|
64 /*/////////////////////////////////////////////////////////////////////////// |
|
65 Prototypes |
|
66 //////////////////////////////////////////////////////////////////////////*/ |
|
67 |
|
68 #pragma mark -- Prototypes -- |
|
69 |
|
70 static OSStatus CheckInit (); |
|
71 |
|
72 static void FilePlayNotificationHandler (void* inRefCon, OSStatus inStatus); |
|
73 |
|
74 static int RunCallBackThread (void* inRefCon); |
|
75 |
|
76 |
|
77 #pragma mark -- Public Functions -- |
|
78 |
|
79 void Lock () |
|
80 { |
|
81 if (!apiMutex) { |
|
82 apiMutex = SDL_CreateMutex(); |
|
83 } |
|
84 SDL_mutexP(apiMutex); |
|
85 } |
|
86 |
|
87 void Unlock () |
|
88 { |
|
89 SDL_mutexV(apiMutex); |
|
90 } |
|
91 |
|
92 int DetectAudioCDVolumes(FSVolumeRefNum *volumes, int numVolumes) |
|
93 { |
|
94 int volumeIndex; |
|
95 int cdVolumeCount = 0; |
|
96 OSStatus result = noErr; |
|
97 |
|
98 for (volumeIndex = 1; result == noErr || result != nsvErr; volumeIndex++) |
|
99 { |
|
100 FSVolumeRefNum actualVolume; |
|
101 FSVolumeInfo volumeInfo; |
|
102 |
|
103 memset (&volumeInfo, 0, sizeof(volumeInfo)); |
|
104 |
|
105 result = FSGetVolumeInfo (kFSInvalidVolumeRefNum, |
|
106 volumeIndex, |
|
107 &actualVolume, |
|
108 kFSVolInfoFSInfo, |
|
109 &volumeInfo, |
|
110 NULL, |
|
111 NULL); |
|
112 |
|
113 if (result == noErr) |
|
114 { |
|
115 if (volumeInfo.filesystemID == kAudioCDFilesystemID) /* It's an audio CD */ |
|
116 { |
|
117 if (volumes != NULL && cdVolumeCount < numVolumes) |
|
118 volumes[cdVolumeCount] = actualVolume; |
|
119 |
|
120 cdVolumeCount++; |
|
121 } |
|
122 } |
|
123 else |
|
124 { |
|
125 /* I'm commenting this out because it seems to be harmless */ |
|
126 /*SDL_SetError ("DetectAudioCDVolumes: FSGetVolumeInfo returned %d", result);*/ |
|
127 } |
|
128 } |
|
129 |
|
130 return cdVolumeCount; |
|
131 } |
|
132 |
|
133 int ReadTOCData (FSVolumeRefNum theVolume, SDL_CD *theCD) |
|
134 { |
|
135 HFSUniStr255 dataForkName; |
|
136 OSStatus theErr; |
|
137 SInt16 forkRefNum; |
|
138 SInt64 forkSize; |
|
139 Ptr forkData = 0; |
|
140 ByteCount actualRead; |
|
141 CFDataRef dataRef = 0; |
|
142 CFPropertyListRef propertyListRef = 0; |
|
143 |
|
144 FSRefParam fsRefPB; |
|
145 FSRef tocPlistFSRef; |
|
146 |
|
147 const char* error = "Unspecified Error"; |
|
148 |
|
149 /* get stuff from .TOC.plist */ |
|
150 fsRefPB.ioCompletion = NULL; |
|
151 fsRefPB.ioNamePtr = "\p.TOC.plist"; |
|
152 fsRefPB.ioVRefNum = theVolume; |
|
153 fsRefPB.ioDirID = 0; |
|
154 fsRefPB.newRef = &tocPlistFSRef; |
|
155 |
|
156 theErr = PBMakeFSRefSync (&fsRefPB); |
|
157 if(theErr != noErr) { |
|
158 error = "PBMakeFSRefSync"; |
|
159 goto bail; |
|
160 } |
|
161 |
|
162 /* Load and parse the TOC XML data */ |
|
163 |
|
164 theErr = FSGetDataForkName (&dataForkName); |
|
165 if (theErr != noErr) { |
|
166 error = "FSGetDataForkName"; |
|
167 goto bail; |
|
168 } |
|
169 |
|
170 theErr = FSOpenFork (&tocPlistFSRef, dataForkName.length, dataForkName.unicode, fsRdPerm, &forkRefNum); |
|
171 if (theErr != noErr) { |
|
172 error = "FSOpenFork"; |
|
173 goto bail; |
|
174 } |
|
175 |
|
176 theErr = FSGetForkSize (forkRefNum, &forkSize); |
|
177 if (theErr != noErr) { |
|
178 error = "FSGetForkSize"; |
|
179 goto bail; |
|
180 } |
|
181 |
|
182 /* Allocate some memory for the XML data */ |
|
183 forkData = NewPtr (forkSize); |
|
184 if(forkData == NULL) { |
|
185 error = "NewPtr"; |
|
186 goto bail; |
|
187 } |
|
188 |
|
189 theErr = FSReadFork (forkRefNum, fsFromStart, 0 /* offset location */, forkSize, forkData, &actualRead); |
|
190 if(theErr != noErr) { |
|
191 error = "FSReadFork"; |
|
192 goto bail; |
|
193 } |
|
194 |
|
195 dataRef = CFDataCreate (kCFAllocatorDefault, (UInt8 *)forkData, forkSize); |
|
196 if(dataRef == 0) { |
|
197 error = "CFDataCreate"; |
|
198 goto bail; |
|
199 } |
|
200 |
|
201 propertyListRef = CFPropertyListCreateFromXMLData (kCFAllocatorDefault, |
|
202 dataRef, |
|
203 kCFPropertyListImmutable, |
|
204 NULL); |
|
205 if (propertyListRef == NULL) { |
|
206 error = "CFPropertyListCreateFromXMLData"; |
|
207 goto bail; |
|
208 } |
|
209 |
|
210 /* Now we got the Property List in memory. Parse it. */ |
|
211 |
|
212 /* First, make sure the root item is a CFDictionary. If not, release and bail. */ |
|
213 if(CFGetTypeID(propertyListRef)== CFDictionaryGetTypeID()) |
|
214 { |
|
215 CFDictionaryRef dictRef = (CFDictionaryRef)propertyListRef; |
|
216 |
|
217 CFDataRef theRawTOCDataRef; |
|
218 CFArrayRef theSessionArrayRef; |
|
219 CFIndex numSessions; |
|
220 CFIndex index; |
|
221 |
|
222 /* This is how we get the Raw TOC Data */ |
|
223 theRawTOCDataRef = (CFDataRef)CFDictionaryGetValue (dictRef, CFSTR(kRawTOCDataString)); |
|
224 |
|
225 /* Get the session array info. */ |
|
226 theSessionArrayRef = (CFArrayRef)CFDictionaryGetValue (dictRef, CFSTR(kSessionsString)); |
|
227 |
|
228 /* Find out how many sessions there are. */ |
|
229 numSessions = CFArrayGetCount (theSessionArrayRef); |
|
230 |
|
231 /* Initialize the total number of tracks to 0 */ |
|
232 theCD->numtracks = 0; |
|
233 |
|
234 /* Iterate over all sessions, collecting the track data */ |
|
235 for(index = 0; index < numSessions; index++) |
|
236 { |
|
237 CFDictionaryRef theSessionDict; |
|
238 CFNumberRef leadoutBlock; |
|
239 CFArrayRef trackArray; |
|
240 CFIndex numTracks; |
|
241 CFIndex trackIndex; |
|
242 UInt32 value = 0; |
|
243 |
|
244 theSessionDict = (CFDictionaryRef) CFArrayGetValueAtIndex (theSessionArrayRef, index); |
|
245 leadoutBlock = (CFNumberRef) CFDictionaryGetValue (theSessionDict, CFSTR(kLeadoutBlockString)); |
|
246 |
|
247 trackArray = (CFArrayRef)CFDictionaryGetValue (theSessionDict, CFSTR(kTrackArrayString)); |
|
248 |
|
249 numTracks = CFArrayGetCount (trackArray); |
|
250 |
|
251 for(trackIndex = 0; trackIndex < numTracks; trackIndex++) { |
|
252 |
|
253 CFDictionaryRef theTrackDict; |
|
254 CFNumberRef trackNumber; |
|
255 CFNumberRef sessionNumber; |
|
256 CFNumberRef startBlock; |
|
257 CFBooleanRef isDataTrack; |
|
258 UInt32 value; |
|
259 |
|
260 theTrackDict = (CFDictionaryRef) CFArrayGetValueAtIndex (trackArray, trackIndex); |
|
261 |
|
262 trackNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kPointKeyString)); |
|
263 sessionNumber = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kSessionNumberKeyString)); |
|
264 startBlock = (CFNumberRef) CFDictionaryGetValue (theTrackDict, CFSTR(kStartBlockKeyString)); |
|
265 isDataTrack = (CFBooleanRef) CFDictionaryGetValue (theTrackDict, CFSTR(kDataKeyString)); |
|
266 |
|
267 /* Fill in the SDL_CD struct */ |
|
268 int idx = theCD->numtracks++; |
|
269 |
|
270 CFNumberGetValue (trackNumber, kCFNumberSInt32Type, &value); |
|
271 theCD->track[idx].id = value; |
|
272 |
|
273 CFNumberGetValue (startBlock, kCFNumberSInt32Type, &value); |
|
274 theCD->track[idx].offset = value; |
|
275 |
|
276 theCD->track[idx].type = (isDataTrack == kCFBooleanTrue) ? SDL_DATA_TRACK : SDL_AUDIO_TRACK; |
|
277 |
|
278 /* Since the track lengths are not stored in .TOC.plist we compute them. */ |
|
279 if (trackIndex > 0) { |
|
280 theCD->track[idx-1].length = theCD->track[idx].offset - theCD->track[idx-1].offset; |
|
281 } |
|
282 } |
|
283 |
|
284 /* Compute the length of the last track */ |
|
285 CFNumberGetValue (leadoutBlock, kCFNumberSInt32Type, &value); |
|
286 |
|
287 theCD->track[theCD->numtracks-1].length = |
|
288 value - theCD->track[theCD->numtracks-1].offset; |
|
289 |
|
290 /* Set offset to leadout track */ |
|
291 theCD->track[theCD->numtracks].offset = value; |
|
292 } |
|
293 |
|
294 } |
|
295 |
|
296 theErr = 0; |
|
297 goto cleanup; |
|
298 bail: |
|
299 SDL_SetError ("ReadTOCData: %s returned %d", error, theErr); |
|
300 theErr = -1; |
|
301 cleanup: |
|
302 |
|
303 if (propertyListRef != NULL) |
|
304 CFRelease(propertyListRef); |
|
305 if (dataRef != NULL) |
|
306 CFRelease(dataRef); |
|
307 if (forkData != NULL) |
|
308 DisposePtr(forkData); |
|
309 |
|
310 FSCloseFork (forkRefNum); |
|
311 |
|
312 return theErr; |
|
313 } |
|
314 |
|
315 int ListTrackFiles (FSVolumeRefNum theVolume, FSRef *trackFiles, int numTracks) |
|
316 { |
|
317 OSStatus result = -1; |
|
318 FSIterator iterator; |
|
319 ItemCount actualObjects; |
|
320 FSRef rootDirectory; |
|
321 FSRef ref; |
|
322 HFSUniStr255 nameStr; |
|
323 |
|
324 result = FSGetVolumeInfo (theVolume, |
|
325 0, |
|
326 NULL, |
|
327 kFSVolInfoFSInfo, |
|
328 NULL, |
|
329 NULL, |
|
330 &rootDirectory); |
|
331 |
|
332 if (result != noErr) { |
|
333 SDL_SetError ("ListTrackFiles: FSGetVolumeInfo returned %d", result); |
|
334 return result; |
|
335 } |
|
336 |
|
337 result = FSOpenIterator (&rootDirectory, kFSIterateFlat, &iterator); |
|
338 if (result == noErr) { |
|
339 do |
|
340 { |
|
341 result = FSGetCatalogInfoBulk (iterator, 1, &actualObjects, |
|
342 NULL, kFSCatInfoNone, NULL, &ref, NULL, &nameStr); |
|
343 if (result == noErr) { |
|
344 |
|
345 CFStringRef name; |
|
346 name = CFStringCreateWithCharacters (NULL, nameStr.unicode, nameStr.length); |
|
347 |
|
348 /* Look for .aiff extension */ |
|
349 if (CFStringHasSuffix (name, CFSTR(".aiff")) || |
|
350 CFStringHasSuffix (name, CFSTR(".cdda"))) { |
|
351 |
|
352 /* Extract the track id from the filename */ |
|
353 int trackID = 0, i = 0; |
|
354 while (i < nameStr.length && !isdigit(nameStr.unicode[i])) { |
|
355 ++i; |
|
356 } |
|
357 while (i < nameStr.length && isdigit(nameStr.unicode[i])) { |
|
358 trackID = 10 * trackID +(nameStr.unicode[i] - '0'); |
|
359 ++i; |
|
360 } |
|
361 |
|
362 #if DEBUG_CDROM |
|
363 printf("Found AIFF for track %d: '%s'\n", trackID, |
|
364 CFStringGetCStringPtr (name, CFStringGetSystemEncoding())); |
|
365 #endif |
|
366 |
|
367 /* Track ID's start at 1, but we want to start at 0 */ |
|
368 trackID--; |
|
369 |
|
370 assert(0 <= trackID && trackID <= SDL_MAX_TRACKS); |
|
371 |
|
372 if (trackID < numTracks) |
|
373 memcpy (&trackFiles[trackID], &ref, sizeof(FSRef)); |
|
374 } |
|
375 CFRelease (name); |
|
376 } |
|
377 } while(noErr == result); |
|
378 FSCloseIterator (iterator); |
|
379 } |
|
380 |
|
381 return 0; |
|
382 } |
|
383 |
|
384 int LoadFile (const FSRef *ref, int startFrame, int stopFrame) |
|
385 { |
|
386 int error = -1; |
|
387 |
|
388 if (CheckInit () < 0) |
|
389 goto bail; |
|
390 |
|
391 /* release any currently playing file */ |
|
392 if (ReleaseFile () < 0) |
|
393 goto bail; |
|
394 |
|
395 #if DEBUG_CDROM |
|
396 printf ("LoadFile: %d %d\n", startFrame, stopFrame); |
|
397 #endif |
|
398 |
|
399 /*try {*/ |
|
400 |
|
401 /* create a new player, and attach to the audio unit */ |
|
402 |
|
403 thePlayer = new_AudioFilePlayer(ref); |
|
404 if (thePlayer == NULL) { |
|
405 SDL_SetError ("LoadFile: Could not create player"); |
|
406 return -3; /*throw (-3);*/ |
|
407 } |
|
408 |
|
409 if (!thePlayer->SetDestination(thePlayer, &theUnit)) |
|
410 goto bail; |
|
411 |
|
412 if (startFrame >= 0) |
|
413 thePlayer->SetStartFrame (thePlayer, startFrame); |
|
414 |
|
415 if (stopFrame >= 0 && stopFrame > startFrame) |
|
416 thePlayer->SetStopFrame (thePlayer, stopFrame); |
|
417 |
|
418 /* we set the notifier later */ |
|
419 /*thePlayer->SetNotifier(thePlayer, FilePlayNotificationHandler, NULL);*/ |
|
420 |
|
421 if (!thePlayer->Connect(thePlayer)) |
|
422 goto bail; |
|
423 |
|
424 #if DEBUG_CDROM |
|
425 thePlayer->Print(thePlayer); |
|
426 fflush (stdout); |
|
427 #endif |
|
428 /*} |
|
429 catch (...) |
|
430 { |
|
431 goto bail; |
|
432 }*/ |
|
433 |
|
434 error = 0; |
|
435 |
|
436 bail: |
|
437 return error; |
|
438 } |
|
439 |
|
440 int ReleaseFile () |
|
441 { |
|
442 int error = -1; |
|
443 |
|
444 /* (Don't see any way that the original C++ code could throw here.) --ryan. */ |
|
445 /*try {*/ |
|
446 if (thePlayer != NULL) { |
|
447 |
|
448 thePlayer->Disconnect(thePlayer); |
|
449 |
|
450 delete_AudioFilePlayer(thePlayer); |
|
451 |
|
452 thePlayer = NULL; |
|
453 } |
|
454 /*} |
|
455 catch (...) |
|
456 { |
|
457 goto bail; |
|
458 }*/ |
|
459 |
|
460 error = 0; |
|
461 |
|
462 /* bail: */ |
|
463 return error; |
|
464 } |
|
465 |
|
466 int PlayFile () |
|
467 { |
|
468 OSStatus result = -1; |
|
469 |
|
470 if (CheckInit () < 0) |
|
471 goto bail; |
|
472 |
|
473 /*try {*/ |
|
474 |
|
475 // start processing of the audio unit |
|
476 result = AudioOutputUnitStart (theUnit); |
|
477 if (result) goto bail; //THROW_RESULT("PlayFile: AudioOutputUnitStart") |
|
478 |
|
479 /*} |
|
480 catch (...) |
|
481 { |
|
482 goto bail; |
|
483 }*/ |
|
484 |
|
485 result = 0; |
|
486 |
|
487 bail: |
|
488 return result; |
|
489 } |
|
490 |
|
491 int PauseFile () |
|
492 { |
|
493 OSStatus result = -1; |
|
494 |
|
495 if (CheckInit () < 0) |
|
496 goto bail; |
|
497 |
|
498 /*try {*/ |
|
499 |
|
500 /* stop processing the audio unit */ |
|
501 result = AudioOutputUnitStop (theUnit); |
|
502 if (result) goto bail; /*THROW_RESULT("PauseFile: AudioOutputUnitStop")*/ |
|
503 /*} |
|
504 catch (...) |
|
505 { |
|
506 goto bail; |
|
507 }*/ |
|
508 |
|
509 result = 0; |
|
510 bail: |
|
511 return result; |
|
512 } |
|
513 |
|
514 void SetCompletionProc (CDPlayerCompletionProc proc, SDL_CD *cdrom) |
|
515 { |
|
516 assert(thePlayer != NULL); |
|
517 |
|
518 theCDROM = cdrom; |
|
519 completionProc = proc; |
|
520 thePlayer->SetNotifier (thePlayer, FilePlayNotificationHandler, cdrom); |
|
521 } |
|
522 |
|
523 int GetCurrentFrame () |
|
524 { |
|
525 int frame; |
|
526 |
|
527 if (thePlayer == NULL) |
|
528 frame = 0; |
|
529 else |
|
530 frame = thePlayer->GetCurrentFrame (thePlayer); |
|
531 |
|
532 return frame; |
|
533 } |
|
534 |
|
535 |
|
536 #pragma mark -- Private Functions -- |
|
537 |
|
538 static OSStatus CheckInit () |
|
539 { |
|
540 if (playBackWasInit) |
|
541 return 0; |
|
542 |
|
543 OSStatus result = noErr; |
|
544 |
|
545 /* Create the callback semaphore */ |
|
546 callbackSem = SDL_CreateSemaphore(0); |
|
547 |
|
548 /* Start callback thread */ |
|
549 SDL_CreateThread(RunCallBackThread, NULL); |
|
550 |
|
551 { /*try {*/ |
|
552 ComponentDescription desc; |
|
553 |
|
554 desc.componentType = kAudioUnitComponentType; |
|
555 desc.componentSubType = kAudioUnitSubType_Output; |
|
556 desc.componentManufacturer = kAudioUnitID_DefaultOutput; |
|
557 desc.componentFlags = 0; |
|
558 desc.componentFlagsMask = 0; |
|
559 |
|
560 Component comp = FindNextComponent (NULL, &desc); |
|
561 if (comp == NULL) { |
|
562 SDL_SetError ("CheckInit: FindNextComponent returned NULL"); |
|
563 if (result) return -1; //throw(internalComponentErr); |
|
564 } |
|
565 |
|
566 result = OpenAComponent (comp, &theUnit); |
|
567 if (result) return -1; //THROW_RESULT("CheckInit: OpenAComponent") |
|
568 |
|
569 // you need to initialize the output unit before you set it as a destination |
|
570 result = AudioUnitInitialize (theUnit); |
|
571 if (result) return -1; //THROW_RESULT("CheckInit: AudioUnitInitialize") |
|
572 |
|
573 |
|
574 playBackWasInit = true; |
|
575 } |
|
576 /*catch (...) |
|
577 { |
|
578 return -1; |
|
579 }*/ |
|
580 |
|
581 return 0; |
|
582 } |
|
583 |
|
584 static void FilePlayNotificationHandler(void * inRefCon, OSStatus inStatus) |
|
585 { |
|
586 if (inStatus == kAudioFilePlay_FileIsFinished) { |
|
587 |
|
588 /* notify non-CA thread to perform the callback */ |
|
589 SDL_SemPost(callbackSem); |
|
590 |
|
591 } else if (inStatus == kAudioFilePlayErr_FilePlayUnderrun) { |
|
592 |
|
593 SDL_SetError ("CDPlayer Notification: buffer underrun"); |
|
594 } else if (inStatus == kAudioFilePlay_PlayerIsUninitialized) { |
|
595 |
|
596 SDL_SetError ("CDPlayer Notification: player is uninitialized"); |
|
597 } else { |
|
598 |
|
599 SDL_SetError ("CDPlayer Notification: unknown error %ld", inStatus); |
|
600 } |
|
601 } |
|
602 |
|
603 static int RunCallBackThread (void *param) |
|
604 { |
|
605 for (;;) { |
|
606 |
|
607 SDL_SemWait(callbackSem); |
|
608 |
|
609 if (completionProc && theCDROM) { |
|
610 #if DEBUG_CDROM |
|
611 printf ("callback!\n"); |
|
612 #endif |
|
613 (*completionProc)(theCDROM); |
|
614 } else { |
|
615 #if DEBUG_CDROM |
|
616 printf ("callback?\n"); |
|
617 #endif |
|
618 } |
|
619 } |
|
620 |
|
621 #if DEBUG_CDROM |
|
622 printf ("thread dying now...\n"); |
|
623 #endif |
|
624 |
|
625 return 0; |
|
626 } |
|
627 |
|
628 /*}; // extern "C" */ |