Line data Source code
1 0 : /*
2 :
3 : OOALSoundDecoder.m
4 :
5 :
6 : OOALSound - OpenAL sound implementation for Oolite.
7 : Copyright (C) 2005-2013 Jens Ayton
8 :
9 : Permission is hereby granted, free of charge, to any person obtaining a copy
10 : of this software and associated documentation files (the "Software"), to deal
11 : in the Software without restriction, including without limitation the rights
12 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 : copies of the Software, and to permit persons to whom the Software is
14 : furnished to do so, subject to the following conditions:
15 :
16 : The above copyright notice and this permission notice shall be included in all
17 : copies or substantial portions of the Software.
18 :
19 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 : SOFTWARE.
26 :
27 : */
28 :
29 : #import "OOALSoundDecoder.h"
30 : #import "NSDataOOExtensions.h"
31 : #import <vorbis/vorbisfile.h>
32 : #import "OOLogging.h"
33 : #import "unzip.h"
34 :
35 0 : enum
36 : {
37 : kMaxDecodeSize = 1 << 20 // 2^20 frames = 4 MB
38 : };
39 :
40 : static size_t OOReadOXZVorbis (void *ptr, size_t size, size_t nmemb, void *datasource);
41 : static int OOCloseOXZVorbis (void *datasource);
42 : // not practical to implement these
43 : //static int OOSeekOXZVorbis (void *datasource, ogg_int64_t offset, int whence);
44 : //static long OOTellOXZVorbis (void *datasource);
45 :
46 0 : @interface OOALSoundVorbisCodec: OOALSoundDecoder
47 : {
48 0 : OggVorbis_File _vf;
49 0 : NSString *_name;
50 0 : BOOL _readStarted;
51 0 : BOOL _seekableStream;
52 : @public
53 0 : unzFile uf;
54 : }
55 :
56 0 : - (NSDictionary *)comments;
57 :
58 : @end
59 :
60 :
61 : @implementation OOALSoundDecoder
62 :
63 : - (id)initWithPath:(NSString *)inPath
64 : {
65 : [self release];
66 : self = nil;
67 :
68 : if ([[inPath pathExtension] isEqual:@"ogg"])
69 : {
70 : self = [[OOALSoundVorbisCodec alloc] initWithPath:inPath];
71 : }
72 :
73 : return self;
74 : }
75 :
76 :
77 : + (OOALSoundDecoder *)codecWithPath:(NSString *)inPath
78 : {
79 : if ([[inPath pathExtension] isEqual:@"ogg"])
80 : {
81 : return [[[OOALSoundVorbisCodec alloc] initWithPath:inPath] autorelease];
82 : }
83 : return nil;
84 : }
85 :
86 :
87 : - (size_t)streamToBuffer:(char *)ioBuffer
88 : {
89 : return 0;
90 : }
91 :
92 :
93 : - (BOOL)readCreatingBuffer:(char **)outBuffer withFrameCount:(size_t *)outSize
94 : {
95 : if (NULL != outBuffer) *outBuffer = NULL;
96 : if (NULL != outSize) *outSize = 0;
97 :
98 : return NO;
99 : }
100 :
101 :
102 : - (size_t)sizeAsBuffer
103 : {
104 : return 0;
105 : }
106 :
107 :
108 : - (BOOL)isStereo
109 : {
110 : return NO;
111 : }
112 :
113 :
114 : - (long)sampleRate
115 : {
116 : return 0;
117 : }
118 :
119 :
120 : - (void) reset
121 : {
122 : // nothing
123 : }
124 :
125 :
126 : - (NSString *)name
127 : {
128 : return @"";
129 : }
130 :
131 : @end
132 :
133 :
134 : @implementation OOALSoundVorbisCodec
135 :
136 0 : - (id)initWithPath:(NSString *)path
137 : {
138 : if ((self = [super init]))
139 : {
140 : BOOL OK = NO;
141 :
142 : _name = [[path lastPathComponent] retain];
143 :
144 : NSUInteger i, cl;
145 : NSArray *components = [path pathComponents];
146 : cl = [components count];
147 : for (i = 0 ; i < cl ; i++)
148 : {
149 : NSString *component = [components objectAtIndex:i];
150 : if ([[[component pathExtension] lowercaseString] isEqualToString:@"oxz"])
151 : {
152 : break;
153 : }
154 : }
155 : // if i == cl then the path is entirely uncompressed
156 : if (i == cl)
157 : {
158 : /* Get vorbis data from a standard file stream */
159 : int err;
160 : FILE *file;
161 :
162 : _seekableStream = YES;
163 :
164 : if (nil != path)
165 : {
166 : file = fopen([path UTF8String], "rb");
167 : if (NULL != file)
168 : {
169 : err = ov_open_callbacks(file, &_vf, NULL, 0, OV_CALLBACKS_DEFAULT);
170 : if (0 == err)
171 : {
172 : OK = YES;
173 : }
174 : }
175 : }
176 :
177 : if (!OK)
178 : {
179 : [self release];
180 : self = nil;
181 : }
182 :
183 : }
184 : else
185 : {
186 : _seekableStream = NO;
187 :
188 : NSRange range;
189 : range.location = 0; range.length = i+1;
190 : NSString *zipFile = [NSString pathWithComponents:[components subarrayWithRange:range]];
191 : range.location = i+1; range.length = cl-(i+1);
192 : NSString *containedFile = [NSString pathWithComponents:[components subarrayWithRange:range]];
193 :
194 :
195 : const char* zipname = [zipFile UTF8String];
196 : if (zipname != NULL)
197 : {
198 : uf = unzOpen64(zipname);
199 : }
200 : if (uf == NULL)
201 : {
202 : OOLog(kOOLogFileNotFound, @"Could not unzip OXZ at %@", zipFile);
203 : [self release];
204 : self = nil;
205 : }
206 : else
207 : {
208 : const char* filename = [containedFile UTF8String];
209 : // unzLocateFile(*, *, 1) = case-sensitive extract
210 : if (unzLocateFile(uf, filename, 1) != UNZ_OK)
211 : {
212 : unzClose(uf);
213 : [self release];
214 : self = nil;
215 : }
216 : else
217 : {
218 : int err = UNZ_OK;
219 : unz_file_info64 file_info = {0};
220 : err = unzGetCurrentFileInfo64(uf, &file_info, NULL, 0, NULL, 0, NULL, 0);
221 : if (err != UNZ_OK)
222 : {
223 : unzClose(uf);
224 : OOLog(kOOLogFileNotFound, @"Could not get properties of %@ within OXZ at %@", containedFile, zipFile);
225 : [self release];
226 : self = nil;
227 : }
228 : else
229 : {
230 : err = unzOpenCurrentFile(uf);
231 : if (err != UNZ_OK)
232 : {
233 : unzClose(uf);
234 : OOLog(kOOLogFileNotFound, @"Could not read %@ within OXZ at %@", containedFile, zipFile);
235 : [self release];
236 : self = nil;
237 : }
238 : else
239 : {
240 : ov_callbacks _callbacks = {
241 : OOReadOXZVorbis, // read sequentially
242 : NULL, // no seek
243 : OOCloseOXZVorbis, // close file
244 : NULL, // no tell
245 : };
246 : err = ov_open_callbacks(self, &_vf, NULL, 0, _callbacks);
247 : if (0 == err)
248 : {
249 : OK = YES;
250 : _readStarted = NO;
251 : }
252 : if (!OK)
253 : {
254 : unzClose(uf);
255 : [self release];
256 : self = nil;
257 : }
258 : }
259 : }
260 : }
261 : }
262 : }
263 : }
264 : #ifdef OOLITE_DEBUG_SOUND_FILE_OPENING
265 : if (self != nil)
266 : {
267 : OOLog(@"sound.retain",@"%@",_name);
268 : }
269 : #endif
270 : return self;
271 : }
272 :
273 :
274 0 : - (void)dealloc
275 : {
276 : #ifdef OOLITE_DEBUG_SOUND_FILE_OPENING
277 : if (self != nil)
278 : {
279 : OOLog(@"sound.release",@"%@",_name);
280 : }
281 : #endif
282 :
283 : [_name release];
284 : ov_clear(&_vf);
285 : unzClose(uf);
286 :
287 : [super dealloc];
288 : }
289 :
290 :
291 : - (NSDictionary *)comments
292 : {
293 : vorbis_comment *comments;
294 : unsigned i, count;
295 : NSMutableDictionary *result = nil;
296 : NSString *comment, *key, *value;
297 : NSRange range;
298 :
299 : comments = ov_comment(&_vf, -1);
300 : if (NULL != comments)
301 : {
302 : count = comments->comments;
303 : if (0 != count)
304 : {
305 : result = [NSMutableDictionary dictionaryWithCapacity:count];
306 : for (i = 0; i != count; ++i)
307 : {
308 : comment = [[NSString alloc] initWithBytesNoCopy:comments->user_comments[i] length:comments->comment_lengths[i] encoding:NSUTF8StringEncoding freeWhenDone:NO];
309 : range = [comment rangeOfString:@"="];
310 : if (0 != range.length)
311 : {
312 : key = [comment substringToIndex:range.location];
313 : value = [comment substringFromIndex:range.location + 1];
314 : }
315 : else
316 : {
317 : key = comment;
318 : value = @"";
319 : }
320 : [result setObject:value forKey:key];
321 :
322 : [comment release];
323 : }
324 : }
325 : }
326 :
327 : return result;
328 : }
329 :
330 :
331 0 : - (BOOL)readCreatingBuffer:(char **)outBuffer withFrameCount:(size_t *)outSize
332 : {
333 : char *buffer = NULL, *dst;
334 : size_t sizeInFrames = 0;
335 : int remaining;
336 : long framesRead;
337 : // 16-bit samples, either two or one track
338 : int frameSize = [self isStereo] ? 4 : 2;
339 : ogg_int64_t totalSizeInFrames;
340 : BOOL OK = YES;
341 :
342 : if (NULL != outBuffer) *outBuffer = NULL;
343 : if (NULL != outSize) *outSize = 0;
344 : if (NULL == outBuffer || NULL == outSize) OK = NO;
345 :
346 : if (OK)
347 : {
348 : totalSizeInFrames = ov_pcm_total(&_vf, -1);
349 : assert ((uint64_t)totalSizeInFrames < (uint64_t)SIZE_MAX); // Should have been checked by caller
350 : sizeInFrames = (size_t)totalSizeInFrames;
351 : }
352 :
353 : if (OK)
354 : {
355 : buffer = malloc(sizeof (char) * frameSize * sizeInFrames);
356 : if (!buffer) OK = NO;
357 : }
358 :
359 : if (OK && sizeInFrames)
360 : {
361 : remaining = (int)MIN(frameSize * sizeInFrames, (size_t)INT_MAX);
362 : dst = buffer;
363 :
364 : char pcmout[4096];
365 :
366 : do
367 : {
368 : int toRead = sizeof(pcmout);
369 : if (remaining < toRead)
370 : {
371 : toRead = remaining;
372 : }
373 : framesRead = ov_read(&_vf, pcmout, toRead, 0, 2, 1, NULL);
374 : if (framesRead <= 0)
375 : {
376 : if (OV_HOLE == framesRead) continue;
377 : //else:
378 : break;
379 : }
380 :
381 : memcpy(dst, &pcmout, sizeof (char) * framesRead);
382 :
383 : remaining -= framesRead;
384 : dst += framesRead;
385 : } while (0 < remaining);
386 :
387 : sizeInFrames -= remaining; // In case we stopped at an error
388 : }
389 :
390 : if (OK)
391 : {
392 : *outBuffer = buffer;
393 : *outSize = sizeInFrames*frameSize;
394 : }
395 : else
396 : {
397 : if (buffer) free(buffer);
398 : }
399 : return OK;
400 : }
401 :
402 :
403 0 : - (size_t)streamToBuffer:(char *)buffer
404 : {
405 :
406 : int remaining = OOAL_STREAM_CHUNK_SIZE;
407 : size_t streamed = 0;
408 : long framesRead;
409 :
410 : char *dst = buffer;
411 : char pcmout[4096];
412 : _readStarted = YES;
413 : do
414 : {
415 : int toRead = sizeof(pcmout);
416 : if (remaining < toRead)
417 : {
418 : toRead = remaining;
419 : }
420 : framesRead = ov_read(&_vf, pcmout, toRead, 0, 2, 1, NULL);
421 : if (framesRead <= 0)
422 : {
423 : if (OV_HOLE == framesRead) continue;
424 : //else:
425 : break;
426 : }
427 : memcpy(dst, &pcmout, sizeof (char) * framesRead);
428 : remaining -= sizeof(char) * framesRead;
429 : dst += sizeof(char) * framesRead;
430 : streamed += sizeof(char) * framesRead;
431 : } while (0 < remaining);
432 :
433 : return streamed;
434 : }
435 :
436 :
437 0 : - (size_t)sizeAsBuffer
438 : {
439 : ogg_int64_t size;
440 : size = ov_pcm_total(&_vf, -1);
441 : size *= sizeof(char) * ([self isStereo] ? 4 : 2);
442 : if ((uint64_t)SIZE_MAX < (uint64_t)size) size = (ogg_int64_t)SIZE_MAX;
443 : return (size_t)size;
444 : }
445 :
446 :
447 0 : - (BOOL)isStereo
448 : {
449 : return 1 < ov_info(&_vf, -1)->channels;
450 : }
451 :
452 :
453 0 : - (NSString *)description
454 : {
455 : return [NSString stringWithFormat:@"<%@ %p>{\"%@\", comments=%@}", [self className], self, _name, [self comments]];
456 : }
457 :
458 :
459 0 : - (long)sampleRate
460 : {
461 : return ov_info(&_vf, -1)->rate;
462 : }
463 :
464 :
465 :
466 0 : - (void) reset
467 : {
468 : if (!_readStarted)
469 : {
470 : return; // don't need to do anything
471 : }
472 : if (_seekableStream)
473 : {
474 : ov_pcm_seek(&_vf, 0);
475 : return;
476 : }
477 : // reset current file pointer in OXZ
478 : unzOpenCurrentFile(uf);
479 : // reopen OGG streamer
480 : ov_clear(&_vf);
481 : ov_callbacks _callbacks = {
482 : OOReadOXZVorbis, // read sequentially
483 : NULL, // no seek
484 : OOCloseOXZVorbis, // close file
485 : NULL, // no tell
486 : };
487 : ov_open_callbacks(self, &_vf, NULL, 0, _callbacks);
488 : _readStarted = NO;
489 : }
490 :
491 :
492 0 : - (NSString *)name
493 : {
494 : return [[_name retain] autorelease];
495 : }
496 :
497 : @end
498 :
499 :
500 0 : static size_t OOReadOXZVorbis (void *ptr, size_t size, size_t nmemb, void *datasource)
501 : {
502 : OOALSoundVorbisCodec *src = (OOALSoundVorbisCodec *)datasource;
503 : size_t toRead = size*nmemb;
504 : void *buf = (void*)malloc(toRead);
505 : int err = UNZ_OK;
506 : err = unzReadCurrentFile(src->uf, buf, toRead);
507 : // OOLog(@"sound.replay",@"Read %d blocks, got %d",toRead,err);
508 : if (err > 0)
509 : {
510 : memcpy(ptr, buf, err);
511 : }
512 : if (err < 0)
513 : {
514 : return OV_EREAD;
515 : }
516 : return err;
517 : }
518 :
519 :
520 0 : static int OOCloseOXZVorbis (void *datasource)
521 : {
522 : // doing this prevents replaying
523 : // OOALSoundVorbisCodec *src = (OOALSoundVorbisCodec *)datasource;
524 : // unzClose(src->uf);
525 : return 0;
526 : }
527 :
|