Mercurial > SDL_sound_CoreAudio
comparison decoders/quicktime.c @ 318:df024df56996
Initial add.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Mon, 29 Apr 2002 06:01:52 +0000 |
parents | |
children | a7c1dbcf5e00 |
comparison
equal
deleted
inserted
replaced
317:132d303b3ef0 | 318:df024df56996 |
---|---|
1 /* | |
2 * SDL_sound -- An abstract sound format decoding API. | |
3 * Copyright (C) 2001 Ryan C. Gordon. | |
4 * | |
5 * This library is free software; you can redistribute it and/or | |
6 * modify it under the terms of the GNU Lesser General Public | |
7 * License as published by the Free Software Foundation; either | |
8 * version 2.1 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 * Lesser General Public License for more details. | |
14 * | |
15 * You should have received a copy of the GNU Lesser General Public | |
16 * License along with this library; if not, write to the Free Software | |
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 */ | |
19 | |
20 /* | |
21 * QuickTime decoder for sound formats that QuickTime supports. | |
22 * April 28, 2002 | |
23 * | |
24 * This driver handles .mov files with a sound track. In | |
25 * theory, it could handle any format that QuickTime supports. | |
26 * In practice, it may only handle a select few of these formats. | |
27 * | |
28 * It seems able to play back AIFF and other standard Mac formats. | |
29 * Rewinding is not supported yet. | |
30 * | |
31 * The routine QT_create_data_ref() needs to be | |
32 * tweaked to support different media types. | |
33 * This code was originally written to get MP3 support, | |
34 * as it turns out, this isn't possible using this method. | |
35 * | |
36 * The only way to get streaming MP3 support through QuickTime, | |
37 * and hence support for SDL_RWops, is to write | |
38 * a DataHandler component, which suddenly gets much more difficult :-( | |
39 * | |
40 * This file was written by Darrell Walisser (walisser@mac.com) | |
41 * Portions have been borrowed from the "MP3Player" sample code, | |
42 * courtesy of Apple. | |
43 */ | |
44 | |
45 #if HAVE_CONFIG_H | |
46 # include <config.h> | |
47 #endif | |
48 | |
49 #ifdef SOUND_SUPPORTS_QUICKTIME | |
50 #ifdef macintosh | |
51 typedef long int32_t; | |
52 # define OPAQUE_UPP_TYPES 0 | |
53 # include <QuickTime.h> | |
54 #else | |
55 # include <QuickTime/QuickTime.h> | |
56 # include <Carbon/Carbon.h> | |
57 #endif | |
58 | |
59 #include <stdio.h> | |
60 #include <stdlib.h> | |
61 #include <stdint.h> | |
62 #include <string.h> | |
63 #include <assert.h> | |
64 | |
65 #include "SDL_sound.h" | |
66 | |
67 #define __SDL_SOUND_INTERNAL__ | |
68 #include "SDL_sound_internal.h" | |
69 | |
70 static int QT_init(void); | |
71 static void QT_quit(void); | |
72 static int QT_open(Sound_Sample *sample, const char *ext); | |
73 static void QT_close(Sound_Sample *sample); | |
74 static Uint32 QT_read(Sound_Sample *sample); | |
75 static int QT_rewind(Sound_Sample *sample); | |
76 | |
77 #define QT_MAX_INPUT_BUFFER (32*1024) /* Maximum size of internal buffer (internal->buffer_size) */ | |
78 | |
79 static const char *extensions_quicktime[] = { "mov", NULL }; | |
80 const Sound_DecoderFunctions __Sound_DecoderFunctions_QuickTime = | |
81 { | |
82 { | |
83 extensions_quicktime, | |
84 "QuickTime format", | |
85 "Darrell Walisser <dwaliss1@purdue.edu>", | |
86 "http://www.icculus.org/SDL_sound/" | |
87 }, | |
88 | |
89 QT_init, /* init() method */ | |
90 QT_quit, /* quit() method */ | |
91 QT_open, /* open() method */ | |
92 QT_close, /* close() method */ | |
93 QT_read, /* read() method */ | |
94 QT_rewind /* rewind() method */ | |
95 }; | |
96 | |
97 typedef struct { | |
98 | |
99 ExtendedSoundComponentData compData; | |
100 Handle hSource; /* source media buffer */ | |
101 Media sourceMedia; /* sound media identifier */ | |
102 TimeValue getMediaAtThisTime; | |
103 TimeValue sourceDuration; | |
104 Boolean isThereMoreSource; | |
105 UInt32 maxBufferSize; | |
106 | |
107 } SCFillBufferData, *SCFillBufferDataPtr; | |
108 | |
109 typedef struct { | |
110 | |
111 Movie movie; | |
112 Track track; | |
113 Media media; | |
114 AudioFormatAtomPtr atom; | |
115 SoundComponentData source_format; | |
116 SoundComponentData dest_format; | |
117 SoundConverter converter; | |
118 SCFillBufferData buffer_data; | |
119 SoundConverterFillBufferDataUPP fill_buffer_proc; | |
120 | |
121 } qt_t; | |
122 | |
123 | |
124 | |
125 | |
126 /* | |
127 * This procedure creates a description of the raw data | |
128 * read from SDL_RWops so that QuickTime can identify | |
129 * the codec it needs to use to decompress it. | |
130 */ | |
131 static Handle QT_create_data_ref (const char *file_extension) { | |
132 | |
133 Handle tmp_handle, data_ref; | |
134 StringPtr file_name = "\p"; /* empty since we don't know the file name! */ | |
135 OSType file_type; | |
136 StringPtr mime_type; | |
137 long atoms[3]; | |
138 | |
139 /* | |
140 if (__Sound_strcasecmp (file_extension, "mp3")==0) { | |
141 file_type = 'MPEG'; | |
142 mime_type = "\pvideo/mpeg"; | |
143 } | |
144 else { | |
145 | |
146 return NULL; | |
147 } | |
148 */ | |
149 | |
150 if (__Sound_strcasecmp (file_extension, "mov") == 0) { | |
151 | |
152 file_type = 'MooV'; | |
153 mime_type = "\pvideo/quicktime"; | |
154 } | |
155 else { | |
156 | |
157 return NULL; | |
158 } | |
159 | |
160 tmp_handle = NewHandle(0); | |
161 assert (tmp_handle != NULL); | |
162 assert (noErr == PtrToHand (&tmp_handle, &data_ref, sizeof(Handle))); | |
163 assert (noErr == PtrAndHand (file_name, data_ref, file_name[0]+1)); | |
164 | |
165 atoms[0] = EndianU32_NtoB (sizeof(long) * 3); | |
166 atoms[1] = EndianU32_NtoB (kDataRefExtensionMacOSFileType); | |
167 atoms[2] = EndianU32_NtoB (file_type); | |
168 | |
169 assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*3)); | |
170 | |
171 atoms[0] = EndianU32_NtoB (sizeof(long)*2 + mime_type[0]+1); | |
172 atoms[1] = EndianU32_NtoB (kDataRefExtensionMIMEType); | |
173 | |
174 assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*2)); | |
175 assert (noErr == PtrAndHand (mime_type, data_ref, mime_type[0]+1)); | |
176 | |
177 return data_ref; | |
178 } | |
179 | |
180 /* | |
181 * This procedure is a hook for QuickTime to grab data from the | |
182 * SDL_RWOps data structure when it needs it | |
183 */ | |
184 static pascal OSErr QT_get_movie_data_proc (long offset, long size, | |
185 void *data, void *user_data) | |
186 { | |
187 SDL_RWops* rw = (SDL_RWops*)user_data; | |
188 OSErr error; | |
189 | |
190 if (offset == SDL_RWseek (rw, offset, SEEK_SET)) { | |
191 | |
192 if (size == SDL_RWread (rw, data, 1, size)) { | |
193 error = noErr; | |
194 } | |
195 else { | |
196 error = notEnoughDataErr; | |
197 } | |
198 } | |
199 else { | |
200 error = fileOffsetTooBigErr; | |
201 } | |
202 | |
203 return (error); | |
204 } | |
205 | |
206 /* * ---------------------------- | |
207 * SoundConverterFillBufferDataProc | |
208 * | |
209 * the callback routine that provides the source data for conversion - | |
210 * it provides data by setting outData to a pointer to a properly | |
211 * filled out ExtendedSoundComponentData structure | |
212 */ | |
213 static pascal Boolean QT_sound_converter_fill_buffer_data_proc (SoundComponentDataPtr *outData, void *inRefCon) | |
214 { | |
215 SCFillBufferDataPtr pFillData = (SCFillBufferDataPtr)inRefCon; | |
216 | |
217 OSErr err = noErr; | |
218 | |
219 /* if after getting the last chunk of data the total time is over | |
220 * the duration, we're done | |
221 */ | |
222 if (pFillData->getMediaAtThisTime >= pFillData->sourceDuration) { | |
223 pFillData->isThereMoreSource = false; | |
224 pFillData->compData.desc.buffer = NULL; | |
225 pFillData->compData.desc.sampleCount = 0; | |
226 pFillData->compData.bufferSize = 0; | |
227 } | |
228 | |
229 if (pFillData->isThereMoreSource) { | |
230 | |
231 long sourceBytesReturned; | |
232 long numberOfSamples; | |
233 TimeValue sourceReturnedTime, durationPerSample; | |
234 | |
235 HUnlock(pFillData->hSource); | |
236 | |
237 err = GetMediaSample | |
238 (pFillData->sourceMedia,/* specifies the media for this operation */ | |
239 pFillData->hSource, /* function returns the sample data into this handle */ | |
240 pFillData->maxBufferSize, /* maximum number of bytes of sample data to be returned */ | |
241 &sourceBytesReturned, /* the number of bytes of sample data returned */ | |
242 pFillData->getMediaAtThisTime,/* starting time of the sample to | |
243 be retrieved (must be in | |
244 Media's TimeScale) */ | |
245 &sourceReturnedTime,/* indicates the actual time of the returned sample data */ | |
246 &durationPerSample, /* duration of each sample in the media */ | |
247 NULL, /* sample description corresponding to the returned sample data (NULL to ignore) */ | |
248 NULL, /* index value to the sample description that corresponds | |
249 to the returned sample data (NULL to ignore) */ | |
250 0, /* maximum number of samples to be returned (0 to use a | |
251 value that is appropriate for the media) */ | |
252 &numberOfSamples, /* number of samples it actually returned */ | |
253 NULL); /* flags that describe the sample (NULL to ignore) */ | |
254 | |
255 HLock(pFillData->hSource); | |
256 | |
257 if ((noErr != err) || (sourceBytesReturned == 0)) { | |
258 pFillData->isThereMoreSource = false; | |
259 pFillData->compData.desc.buffer = NULL; | |
260 pFillData->compData.desc.sampleCount = 0; | |
261 | |
262 if ((err != noErr) && (sourceBytesReturned > 0)) | |
263 DebugStr("\pGetMediaSample - Failed in FillBufferDataProc"); | |
264 } | |
265 | |
266 pFillData->getMediaAtThisTime = sourceReturnedTime + (durationPerSample * numberOfSamples); | |
267 pFillData->compData.bufferSize = sourceBytesReturned; | |
268 } | |
269 | |
270 /* set outData to a properly filled out ExtendedSoundComponentData struct */ | |
271 *outData = (SoundComponentDataPtr)&pFillData->compData; | |
272 | |
273 return (pFillData->isThereMoreSource); | |
274 } | |
275 | |
276 | |
277 static int QT_init_internal () { | |
278 | |
279 OSErr error; | |
280 | |
281 error = EnterMovies(); /* initialize the movie toolbox */ | |
282 | |
283 return (error == noErr); | |
284 } | |
285 | |
286 static void QT_quit_internal () { | |
287 | |
288 ExitMovies(); | |
289 } | |
290 | |
291 static qt_t* QT_open_internal (Sound_Sample *sample, const char *extension) | |
292 { | |
293 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
294 | |
295 qt_t *instance; | |
296 OSErr error; | |
297 Movie movie; | |
298 Track sound_track; | |
299 Media sound_track_media; | |
300 AudioFormatAtomPtr source_sound_decomp_atom; | |
301 | |
302 SoundDescriptionV1Handle source_sound_description; | |
303 Handle source_sound_description_extension; | |
304 Size source_sound_description_extension_size; | |
305 Handle data_ref; | |
306 | |
307 data_ref = QT_create_data_ref (extension); | |
308 | |
309 /* create a movie that will read data using SDL_RWops */ | |
310 error = NewMovieFromUserProc | |
311 (&movie, | |
312 0, | |
313 NULL, | |
314 NewGetMovieUPP(QT_get_movie_data_proc), | |
315 (void*) internal->rw, | |
316 data_ref, | |
317 'hndl'); | |
318 | |
319 if (error != noErr) { | |
320 | |
321 return NULL; | |
322 } | |
323 | |
324 /* get the first sound track of the movie; other tracks will be ignored */ | |
325 sound_track = GetMovieIndTrackType (movie, 1, SoundMediaType, movieTrackMediaType); | |
326 if (sound_track == NULL) { | |
327 | |
328 /* movie needs a sound track! */ | |
329 | |
330 return NULL; | |
331 } | |
332 | |
333 /* get and return the sound track media */ | |
334 sound_track_media = GetTrackMedia (sound_track); | |
335 if (sound_track_media == NULL) { | |
336 | |
337 return NULL; | |
338 } | |
339 | |
340 /* create a description of the source sound so we can convert it later */ | |
341 source_sound_description = (SoundDescriptionV1Handle)NewHandle(0); | |
342 assert (source_sound_description != NULL); /* out of memory */ | |
343 | |
344 GetMediaSampleDescription (sound_track_media, 1, | |
345 (SampleDescriptionHandle)source_sound_description); | |
346 error = GetMoviesError(); | |
347 if (error != noErr) { | |
348 | |
349 return NULL; | |
350 } | |
351 | |
352 source_sound_description_extension = NewHandle(0); | |
353 assert (source_sound_description_extension != NULL); /* out of memory */ | |
354 | |
355 error = GetSoundDescriptionExtension ((SoundDescriptionHandle) source_sound_description, | |
356 &source_sound_description_extension, | |
357 siDecompressionParams); | |
358 | |
359 if (error == noErr) { | |
360 | |
361 /* copy extension to atom format description if we have an extension */ | |
362 | |
363 source_sound_description_extension_size = | |
364 GetHandleSize (source_sound_description_extension); | |
365 HLock (source_sound_description_extension); | |
366 | |
367 source_sound_decomp_atom = (AudioFormatAtom*) | |
368 NewPtr (source_sound_description_extension_size); | |
369 assert (source_sound_decomp_atom != NULL); /* out of memory */ | |
370 | |
371 BlockMoveData (*source_sound_description_extension, | |
372 source_sound_decomp_atom, | |
373 source_sound_description_extension_size); | |
374 | |
375 HUnlock (source_sound_description_extension); | |
376 } | |
377 | |
378 else { | |
379 | |
380 source_sound_decomp_atom = NULL; | |
381 } | |
382 | |
383 instance = (qt_t*) malloc (sizeof(*instance)); | |
384 assert (instance != NULL); /* out of memory */ | |
385 | |
386 instance->movie = movie; | |
387 instance->track = sound_track; | |
388 instance->media = sound_track_media; | |
389 instance->atom = source_sound_decomp_atom; | |
390 | |
391 instance->source_format.flags = 0; | |
392 instance->source_format.format = (*source_sound_description)->desc.dataFormat; | |
393 instance->source_format.numChannels = (*source_sound_description)->desc.numChannels; | |
394 instance->source_format.sampleSize = (*source_sound_description)->desc.sampleSize; | |
395 instance->source_format.sampleRate = (*source_sound_description)->desc.sampleRate; | |
396 instance->source_format.sampleCount = 0; | |
397 instance->source_format.buffer = NULL; | |
398 instance->source_format.reserved = 0; | |
399 | |
400 instance->dest_format.flags = 0; | |
401 instance->dest_format.format = kSoundNotCompressed; | |
402 instance->dest_format.numChannels = (*source_sound_description)->desc.numChannels; | |
403 instance->dest_format.sampleSize = (*source_sound_description)->desc.sampleSize; | |
404 instance->dest_format.sampleRate = (*source_sound_description)->desc.sampleRate; | |
405 instance->dest_format.sampleCount = 0; | |
406 instance->dest_format.buffer = NULL; | |
407 instance->dest_format.reserved = 0; | |
408 | |
409 sample->actual.channels = (*source_sound_description)->desc.numChannels; | |
410 sample->actual.rate = (*source_sound_description)->desc.sampleRate >> 16; | |
411 | |
412 if ((*source_sound_description)->desc.sampleSize == 16) { | |
413 | |
414 sample->actual.format = AUDIO_S16SYS; | |
415 } | |
416 else if ((*source_sound_description)->desc.sampleSize == 8) { | |
417 | |
418 sample->actual.format = AUDIO_U8; | |
419 } | |
420 else { | |
421 | |
422 /* 24-bit or others... (which SDL can't handle) */ | |
423 return NULL; | |
424 } | |
425 | |
426 DisposeHandle (source_sound_description_extension); | |
427 DisposeHandle ((Handle)source_sound_description); | |
428 | |
429 /* This next code sets up the SoundConverter component */ | |
430 error = SoundConverterOpen (&instance->source_format, &instance->dest_format, | |
431 &instance->converter); | |
432 | |
433 if (error != noErr) { | |
434 | |
435 return NULL; | |
436 } | |
437 | |
438 error = SoundConverterSetInfo (instance->converter, siDecompressionParams, | |
439 instance->atom); | |
440 if (error == siUnknownInfoType) { | |
441 | |
442 /* ignore */ | |
443 } | |
444 else if (error != noErr) { | |
445 | |
446 /* reall error */ | |
447 return NULL; | |
448 } | |
449 | |
450 error = SoundConverterBeginConversion (instance->converter); | |
451 if (error != noErr) { | |
452 | |
453 return NULL; | |
454 } | |
455 | |
456 instance->buffer_data.sourceMedia = instance->media; | |
457 instance->buffer_data.getMediaAtThisTime = 0; | |
458 instance->buffer_data.sourceDuration = GetMediaDuration(instance->media); | |
459 instance->buffer_data.isThereMoreSource = true; | |
460 instance->buffer_data.maxBufferSize = QT_MAX_INPUT_BUFFER; | |
461 /* allocate source media buffer */ | |
462 instance->buffer_data.hSource = NewHandle((long)instance->buffer_data.maxBufferSize); | |
463 assert (instance->buffer_data.hSource != NULL); /* out of memory */ | |
464 | |
465 instance->buffer_data.compData.desc = instance->source_format; | |
466 instance->buffer_data.compData.desc.buffer = (Byte *)*instance->buffer_data.hSource; | |
467 instance->buffer_data.compData.desc.flags = kExtendedSoundData; | |
468 instance->buffer_data.compData.recordSize = sizeof(ExtendedSoundComponentData); | |
469 instance->buffer_data.compData.extendedFlags = | |
470 kExtendedSoundSampleCountNotValid | kExtendedSoundBufferSizeValid; | |
471 instance->buffer_data.compData.bufferSize = 0; | |
472 | |
473 instance->fill_buffer_proc = | |
474 NewSoundConverterFillBufferDataUPP (QT_sound_converter_fill_buffer_data_proc); | |
475 | |
476 return (instance); | |
477 | |
478 } /* QT_open_internal */ | |
479 | |
480 static void QT_close_internal (qt_t *instance) | |
481 { | |
482 | |
483 } /* QT_close_internal */ | |
484 | |
485 static Uint32 QT_read_internal(Sound_Sample *sample) | |
486 { | |
487 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
488 qt_t *instance = (qt_t*) internal->decoder_private; | |
489 long output_bytes, output_frames, output_flags; | |
490 OSErr error; | |
491 | |
492 error = SoundConverterFillBuffer | |
493 (instance->converter, /* a sound converter */ | |
494 instance->fill_buffer_proc, /* the callback UPP */ | |
495 &instance->buffer_data, /* refCon passed to FillDataProc */ | |
496 internal->buffer, /* the decompressed data 'play' buffer */ | |
497 internal->buffer_size, /* size of the 'play' buffer */ | |
498 &output_bytes, /* number of output bytes */ | |
499 &output_frames, /* number of output frames */ | |
500 &output_flags); /* fillbuffer retured advisor flags */ | |
501 | |
502 if (output_flags & kSoundConverterHasLeftOverData) { | |
503 | |
504 sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; | |
505 } | |
506 else { | |
507 | |
508 sample->flags |= SOUND_SAMPLEFLAG_EOF; | |
509 } | |
510 | |
511 if (error != noErr) { | |
512 | |
513 sample->flags |= SOUND_SAMPLEFLAG_ERROR; | |
514 } | |
515 | |
516 return (output_bytes); | |
517 | |
518 } /* QT_read_internal */ | |
519 | |
520 static int QT_rewind_internal (Sound_Sample *sample) | |
521 { | |
522 | |
523 return 0; | |
524 | |
525 } /* QT_rewind_internal */ | |
526 | |
527 | |
528 | |
529 static int QT_init(void) | |
530 { | |
531 return (QT_init_internal()); | |
532 | |
533 } /* QT_init */ | |
534 | |
535 static void QT_quit(void) | |
536 { | |
537 QT_quit_internal(); | |
538 | |
539 } /* QT_quit */ | |
540 | |
541 static int QT_open(Sound_Sample *sample, const char *ext) | |
542 { | |
543 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
544 qt_t *instance; | |
545 | |
546 instance = QT_open_internal(sample, ext); | |
547 internal->decoder_private = (void*)instance; | |
548 | |
549 return(instance != NULL); | |
550 | |
551 } /* QT_open */ | |
552 | |
553 | |
554 static void QT_close(Sound_Sample *sample) | |
555 { | |
556 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
557 qt_t *instance = (qt_t *) internal->decoder_private; | |
558 | |
559 QT_close_internal (instance); | |
560 | |
561 free (instance); | |
562 | |
563 } /* QT_close */ | |
564 | |
565 | |
566 static Uint32 QT_read(Sound_Sample *sample) | |
567 { | |
568 return(QT_read_internal(sample)); | |
569 | |
570 } /* QT_read */ | |
571 | |
572 | |
573 static int QT_rewind(Sound_Sample *sample) | |
574 { | |
575 | |
576 return(QT_rewind_internal(sample)); | |
577 | |
578 } /* QT_rewind */ | |
579 | |
580 #endif /* SOUND_SUPPORTS_QUICKTIME */ | |
581 | |
582 /* end of quicktime.c ... */ | |
583 |