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