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