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
64 #include "SDL_sound.h"
65
66 #define __SDL_SOUND_INTERNAL__
67 #include "SDL_sound_internal.h"
68
69 static int QT_init(void);
70 static void QT_quit(void);
71 static int QT_open(Sound_Sample *sample, const char *ext);
72 static void QT_close(Sound_Sample *sample);
73 static Uint32 QT_read(Sound_Sample *sample);
74 static int QT_rewind(Sound_Sample *sample);
75 static int QT_seek(Sound_Sample *sample, Uint32 ms);
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 QT_seek /* seek() method */
96 };
97
98 typedef struct {
99
100 ExtendedSoundComponentData compData;
101 Handle hSource; /* source media buffer */
102 Media sourceMedia; /* sound media identifier */
103 TimeValue getMediaAtThisTime;
104 TimeValue sourceDuration;
105 Boolean isThereMoreSource;
106 UInt32 maxBufferSize;
107
108 } SCFillBufferData, *SCFillBufferDataPtr;
109
110 typedef struct {
111
112 Movie movie;
113 Track track;
114 Media media;
115 AudioFormatAtomPtr atom;
116 SoundComponentData source_format;
117 SoundComponentData dest_format;
118 SoundConverter converter;
119 SCFillBufferData buffer_data;
120 SoundConverterFillBufferDataUPP fill_buffer_proc;
121
122 } qt_t;
123
124
125
126
127 /*
128 * This procedure creates a description of the raw data
129 * read from SDL_RWops so that QuickTime can identify
130 * the codec it needs to use to decompress it.
131 */
QT_create_data_ref(const char * file_extension)132 static Handle QT_create_data_ref (const char *file_extension) {
133
134 Handle tmp_handle, data_ref;
135 StringPtr file_name = "\p"; /* empty since we don't know the file name! */
136 OSType file_type;
137 StringPtr mime_type;
138 long atoms[3];
139
140 /*
141 if (__Sound_strcasecmp (file_extension, "mp3")==0) {
142 file_type = 'MPEG';
143 mime_type = "\pvideo/mpeg";
144 }
145 else {
146
147 return NULL;
148 }
149 */
150
151 if (__Sound_strcasecmp (file_extension, "mov") == 0) {
152
153 file_type = 'MooV';
154 mime_type = "\pvideo/quicktime";
155 }
156 else {
157
158 return NULL;
159 }
160
161 tmp_handle = NewHandle(0);
162 assert (tmp_handle != NULL);
163 assert (noErr == PtrToHand (&tmp_handle, &data_ref, sizeof(Handle)));
164 assert (noErr == PtrAndHand (file_name, data_ref, file_name[0]+1));
165
166 atoms[0] = EndianU32_NtoB (sizeof(long) * 3);
167 atoms[1] = EndianU32_NtoB (kDataRefExtensionMacOSFileType);
168 atoms[2] = EndianU32_NtoB (file_type);
169
170 assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*3));
171
172 atoms[0] = EndianU32_NtoB (sizeof(long)*2 + mime_type[0]+1);
173 atoms[1] = EndianU32_NtoB (kDataRefExtensionMIMEType);
174
175 assert (noErr == PtrAndHand (atoms, data_ref, sizeof(long)*2));
176 assert (noErr == PtrAndHand (mime_type, data_ref, mime_type[0]+1));
177
178 return data_ref;
179 }
180
181 /*
182 * This procedure is a hook for QuickTime to grab data from the
183 * SDL_RWOps data structure when it needs it
184 */
QT_get_movie_data_proc(long offset,long size,void * data,void * user_data)185 static pascal OSErr QT_get_movie_data_proc (long offset, long size,
186 void *data, void *user_data)
187 {
188 SDL_RWops* rw = (SDL_RWops*)user_data;
189 OSErr error;
190
191 if (offset == SDL_RWseek (rw, offset, SEEK_SET)) {
192
193 if (size == SDL_RWread (rw, data, 1, size)) {
194 error = noErr;
195 }
196 else {
197 error = notEnoughDataErr;
198 }
199 }
200 else {
201 error = fileOffsetTooBigErr;
202 }
203
204 return (error);
205 }
206
207 /* * ----------------------------
208 * SoundConverterFillBufferDataProc
209 *
210 * the callback routine that provides the source data for conversion -
211 * it provides data by setting outData to a pointer to a properly
212 * filled out ExtendedSoundComponentData structure
213 */
QT_sound_converter_fill_buffer_data_proc(SoundComponentDataPtr * outData,void * inRefCon)214 static pascal Boolean QT_sound_converter_fill_buffer_data_proc (SoundComponentDataPtr *outData, void *inRefCon)
215 {
216 SCFillBufferDataPtr pFillData = (SCFillBufferDataPtr)inRefCon;
217
218 OSErr err = noErr;
219
220 /* if after getting the last chunk of data the total time is over
221 * the duration, we're done
222 */
223 if (pFillData->getMediaAtThisTime >= pFillData->sourceDuration) {
224 pFillData->isThereMoreSource = false;
225 pFillData->compData.desc.buffer = NULL;
226 pFillData->compData.desc.sampleCount = 0;
227 pFillData->compData.bufferSize = 0;
228 }
229
230 if (pFillData->isThereMoreSource) {
231
232 long sourceBytesReturned;
233 long numberOfSamples;
234 TimeValue sourceReturnedTime, durationPerSample;
235
236 HUnlock(pFillData->hSource);
237
238 err = GetMediaSample
239 (pFillData->sourceMedia,/* specifies the media for this operation */
240 pFillData->hSource, /* function returns the sample data into this handle */
241 pFillData->maxBufferSize, /* maximum number of bytes of sample data to be returned */
242 &sourceBytesReturned, /* the number of bytes of sample data returned */
243 pFillData->getMediaAtThisTime,/* starting time of the sample to
244 be retrieved (must be in
245 Media's TimeScale) */
246 &sourceReturnedTime,/* indicates the actual time of the returned sample data */
247 &durationPerSample, /* duration of each sample in the media */
248 NULL, /* sample description corresponding to the returned sample data (NULL to ignore) */
249 NULL, /* index value to the sample description that corresponds
250 to the returned sample data (NULL to ignore) */
251 0, /* maximum number of samples to be returned (0 to use a
252 value that is appropriate for the media) */
253 &numberOfSamples, /* number of samples it actually returned */
254 NULL); /* flags that describe the sample (NULL to ignore) */
255
256 HLock(pFillData->hSource);
257
258 if ((noErr != err) || (sourceBytesReturned == 0)) {
259 pFillData->isThereMoreSource = false;
260 pFillData->compData.desc.buffer = NULL;
261 pFillData->compData.desc.sampleCount = 0;
262
263 if ((err != noErr) && (sourceBytesReturned > 0))
264 DebugStr("\pGetMediaSample - Failed in FillBufferDataProc");
265 }
266
267 pFillData->getMediaAtThisTime = sourceReturnedTime + (durationPerSample * numberOfSamples);
268 pFillData->compData.bufferSize = sourceBytesReturned;
269 }
270
271 /* set outData to a properly filled out ExtendedSoundComponentData struct */
272 *outData = (SoundComponentDataPtr)&pFillData->compData;
273
274 return (pFillData->isThereMoreSource);
275 }
276
277
QT_init_internal()278 static int QT_init_internal () {
279
280 OSErr error;
281
282 error = EnterMovies(); /* initialize the movie toolbox */
283
284 return (error == noErr);
285 }
286
QT_quit_internal()287 static void QT_quit_internal () {
288
289 ExitMovies();
290 }
291
QT_open_internal(Sound_Sample * sample,const char * extension)292 static qt_t* QT_open_internal (Sound_Sample *sample, const char *extension)
293 {
294 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
295
296 qt_t *instance;
297 OSErr error;
298 Movie movie;
299 Track sound_track;
300 Media sound_track_media;
301 AudioFormatAtomPtr source_sound_decomp_atom;
302
303 SoundDescriptionV1Handle source_sound_description;
304 Handle source_sound_description_extension;
305 Size source_sound_description_extension_size;
306 Handle data_ref;
307
308 data_ref = QT_create_data_ref (extension);
309
310 /* create a movie that will read data using SDL_RWops */
311 error = NewMovieFromUserProc
312 (&movie,
313 0,
314 NULL,
315 NewGetMovieUPP(QT_get_movie_data_proc),
316 (void*) internal->rw,
317 data_ref,
318 'hndl');
319
320 if (error != noErr) {
321
322 return NULL;
323 }
324
325 /* get the first sound track of the movie; other tracks will be ignored */
326 sound_track = GetMovieIndTrackType (movie, 1, SoundMediaType, movieTrackMediaType);
327 if (sound_track == NULL) {
328
329 /* movie needs a sound track! */
330
331 return NULL;
332 }
333
334 /* get and return the sound track media */
335 sound_track_media = GetTrackMedia (sound_track);
336 if (sound_track_media == NULL) {
337
338 return NULL;
339 }
340
341 /* create a description of the source sound so we can convert it later */
342 source_sound_description = (SoundDescriptionV1Handle)NewHandle(0);
343 assert (source_sound_description != NULL); /* out of memory */
344
345 GetMediaSampleDescription (sound_track_media, 1,
346 (SampleDescriptionHandle)source_sound_description);
347 error = GetMoviesError();
348 if (error != noErr) {
349
350 return NULL;
351 }
352
353 source_sound_description_extension = NewHandle(0);
354 assert (source_sound_description_extension != NULL); /* out of memory */
355
356 error = GetSoundDescriptionExtension ((SoundDescriptionHandle) source_sound_description,
357 &source_sound_description_extension,
358 siDecompressionParams);
359
360 if (error == noErr) {
361
362 /* copy extension to atom format description if we have an extension */
363
364 source_sound_description_extension_size =
365 GetHandleSize (source_sound_description_extension);
366 HLock (source_sound_description_extension);
367
368 source_sound_decomp_atom = (AudioFormatAtom*)
369 NewPtr (source_sound_description_extension_size);
370 assert (source_sound_decomp_atom != NULL); /* out of memory */
371
372 BlockMoveData (*source_sound_description_extension,
373 source_sound_decomp_atom,
374 source_sound_description_extension_size);
375
376 HUnlock (source_sound_description_extension);
377 }
378
379 else {
380
381 source_sound_decomp_atom = NULL;
382 }
383
384 instance = (qt_t*) malloc (sizeof(*instance));
385 assert (instance != NULL); /* out of memory */
386
387 instance->movie = movie;
388 instance->track = sound_track;
389 instance->media = sound_track_media;
390 instance->atom = source_sound_decomp_atom;
391
392 instance->source_format.flags = 0;
393 instance->source_format.format = (*source_sound_description)->desc.dataFormat;
394 instance->source_format.numChannels = (*source_sound_description)->desc.numChannels;
395 instance->source_format.sampleSize = (*source_sound_description)->desc.sampleSize;
396 instance->source_format.sampleRate = (*source_sound_description)->desc.sampleRate;
397 instance->source_format.sampleCount = 0;
398 instance->source_format.buffer = NULL;
399 instance->source_format.reserved = 0;
400
401 instance->dest_format.flags = 0;
402 instance->dest_format.format = kSoundNotCompressed;
403 instance->dest_format.numChannels = (*source_sound_description)->desc.numChannels;
404 instance->dest_format.sampleSize = (*source_sound_description)->desc.sampleSize;
405 instance->dest_format.sampleRate = (*source_sound_description)->desc.sampleRate;
406 instance->dest_format.sampleCount = 0;
407 instance->dest_format.buffer = NULL;
408 instance->dest_format.reserved = 0;
409
410 sample->actual.channels = (*source_sound_description)->desc.numChannels;
411 sample->actual.rate = (*source_sound_description)->desc.sampleRate >> 16;
412
413 if ((*source_sound_description)->desc.sampleSize == 16) {
414
415 sample->actual.format = AUDIO_S16SYS;
416 }
417 else if ((*source_sound_description)->desc.sampleSize == 8) {
418
419 sample->actual.format = AUDIO_U8;
420 }
421 else {
422
423 /* 24-bit or others... (which SDL can't handle) */
424 return NULL;
425 }
426
427 DisposeHandle (source_sound_description_extension);
428 DisposeHandle ((Handle)source_sound_description);
429
430 /* This next code sets up the SoundConverter component */
431 error = SoundConverterOpen (&instance->source_format, &instance->dest_format,
432 &instance->converter);
433
434 if (error != noErr) {
435
436 return NULL;
437 }
438
439 error = SoundConverterSetInfo (instance->converter, siDecompressionParams,
440 instance->atom);
441 if (error == siUnknownInfoType) {
442
443 /* ignore */
444 }
445 else if (error != noErr) {
446
447 /* reall error */
448 return NULL;
449 }
450
451 error = SoundConverterBeginConversion (instance->converter);
452 if (error != noErr) {
453
454 return NULL;
455 }
456
457 instance->buffer_data.sourceMedia = instance->media;
458 instance->buffer_data.getMediaAtThisTime = 0;
459 instance->buffer_data.sourceDuration = GetMediaDuration(instance->media);
460 instance->buffer_data.isThereMoreSource = true;
461 instance->buffer_data.maxBufferSize = QT_MAX_INPUT_BUFFER;
462 /* allocate source media buffer */
463 instance->buffer_data.hSource = NewHandle((long)instance->buffer_data.maxBufferSize);
464 assert (instance->buffer_data.hSource != NULL); /* out of memory */
465
466 instance->buffer_data.compData.desc = instance->source_format;
467 instance->buffer_data.compData.desc.buffer = (Byte *)*instance->buffer_data.hSource;
468 instance->buffer_data.compData.desc.flags = kExtendedSoundData;
469 instance->buffer_data.compData.recordSize = sizeof(ExtendedSoundComponentData);
470 instance->buffer_data.compData.extendedFlags =
471 kExtendedSoundSampleCountNotValid | kExtendedSoundBufferSizeValid;
472 instance->buffer_data.compData.bufferSize = 0;
473
474 instance->fill_buffer_proc =
475 NewSoundConverterFillBufferDataUPP (QT_sound_converter_fill_buffer_data_proc);
476
477 return (instance);
478
479 } /* QT_open_internal */
480
QT_close_internal(qt_t * instance)481 static void QT_close_internal (qt_t *instance)
482 {
483
484 } /* QT_close_internal */
485
QT_read_internal(Sound_Sample * sample)486 static Uint32 QT_read_internal(Sound_Sample *sample)
487 {
488 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
489 qt_t *instance = (qt_t*) internal->decoder_private;
490 long output_bytes, output_frames, output_flags;
491 OSErr error;
492
493 error = SoundConverterFillBuffer
494 (instance->converter, /* a sound converter */
495 instance->fill_buffer_proc, /* the callback UPP */
496 &instance->buffer_data, /* refCon passed to FillDataProc */
497 internal->buffer, /* the decompressed data 'play' buffer */
498 internal->buffer_size, /* size of the 'play' buffer */
499 &output_bytes, /* number of output bytes */
500 &output_frames, /* number of output frames */
501 &output_flags); /* fillbuffer retured advisor flags */
502
503 if (output_flags & kSoundConverterHasLeftOverData) {
504
505 sample->flags |= SOUND_SAMPLEFLAG_EAGAIN;
506 }
507 else {
508
509 sample->flags |= SOUND_SAMPLEFLAG_EOF;
510 }
511
512 if (error != noErr) {
513
514 sample->flags |= SOUND_SAMPLEFLAG_ERROR;
515 }
516
517 return (output_bytes);
518
519 } /* QT_read_internal */
520
QT_rewind_internal(Sound_Sample * sample)521 static int QT_rewind_internal (Sound_Sample *sample)
522 {
523
524 return 0;
525
526 } /* QT_rewind_internal */
527
528
529
QT_init(void)530 static int QT_init(void)
531 {
532 return (QT_init_internal());
533
534 } /* QT_init */
535
QT_quit(void)536 static void QT_quit(void)
537 {
538 QT_quit_internal();
539
540 } /* QT_quit */
541
QT_open(Sound_Sample * sample,const char * ext)542 static int QT_open(Sound_Sample *sample, const char *ext)
543 {
544 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
545 qt_t *instance;
546
547 instance = QT_open_internal(sample, ext);
548 internal->decoder_private = (void*)instance;
549
550 return(instance != NULL);
551
552 } /* QT_open */
553
554
QT_close(Sound_Sample * sample)555 static void QT_close(Sound_Sample *sample)
556 {
557 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
558 qt_t *instance = (qt_t *) internal->decoder_private;
559
560 QT_close_internal (instance);
561
562 free (instance);
563
564 } /* QT_close */
565
566
QT_read(Sound_Sample * sample)567 static Uint32 QT_read(Sound_Sample *sample)
568 {
569 return(QT_read_internal(sample));
570
571 } /* QT_read */
572
573
QT_rewind(Sound_Sample * sample)574 static int QT_rewind(Sound_Sample *sample)
575 {
576
577 return(QT_rewind_internal(sample));
578
579 } /* QT_rewind */
580
581
QT_seek(Sound_Sample * sample,Uint32 ms)582 static int QT_seek(Sound_Sample *sample, Uint32 ms)
583 {
584 BAIL_MACRO("QUICKTIME: Seeking not implemented", 0);
585 } /* QT_seek */
586
587
588 #endif /* SOUND_SUPPORTS_QUICKTIME */
589
590 /* end of quicktime.c ... */
591
592