From 54c742f5fafec47fc47675a127a8dc7b93554333 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Thu, 24 Dec 2009 10:38:29 -0800 Subject: [PATCH] add support for multi-track FLAC files... add a interleaver to make decoding faster... uses about a third of the cpu as the pure python version did... [git-p4: depot-paths = "//depot/": change = 1418] --- README | 6 +- audioraw.py | 144 ++++++++++++++++++++---- flac.py | 313 +++++++++++++++++++++++++++++++++++++++++++++------ interleave.c | 13 +++ 4 files changed, 421 insertions(+), 55 deletions(-) create mode 100644 interleave.c diff --git a/README b/README index 8d8645d..bc4cc36 100644 --- a/README +++ b/README @@ -106,7 +106,11 @@ v0.x: Add support for WAX/ASX files in shoutcast. Add support for FLAC audio files. If you have libFLAC installed, it will dynamicly decompress the audio. It supports seeking so - goto should also be supported. + goto should also be supported. If you want faster decoding there + is a interleave.c that can be compiled to _interleave.so as + interleaving the data in python code is much slower. + Multi-track FLAC files (ones w/ an embeded cue-sheet are supported) + will appear as a directory with each track in the directory. Files in the root directory of zip archives should now work. Support automatic formating of some attributes. One is duration, diff --git a/audioraw.py b/audioraw.py index 6d8ac47..46b6fdb 100644 --- a/audioraw.py +++ b/audioraw.py @@ -2,8 +2,8 @@ # Copyright 2009 John-Mark Gurney '''Audio Raw Converter''' -from DIDLLite import AudioItem, Resource, ResourceList -from FSStorage import registerklassfun +from DIDLLite import AudioItem, Album, Resource, ResourceList +from FSStorage import FSObject, registerklassfun from twisted.web import resource, server from twisted.internet.interfaces import IPullProducer @@ -21,9 +21,18 @@ mtformat = { # 8: 'audio/l8', unsigned } +def makeaudiomt(bitsps, rate, nchan): + try: + mt = mtformat[bitsps] + except KeyError: + raise KeyError('No mime-type for audio format: %s.' % + `bitsps`) + + return '%s;rate=%d;channels=%d' % (mt, rate, nchan) + def makemtfromdec(dec): - return '%s;rate=%d;channels=%d' % (mtformat[dec.bitspersample], - dec.samplerate, dec.channels) + return makeaudiomt(dec.bitspersample, dec.samplerate, + dec.channels) class DecoderProducer: implements(IPullProducer) @@ -35,6 +44,9 @@ class DecoderProducer: self.skipbytes = skipbytes consumer.registerProducer(self, False) + def pauseProducing(self): + print 'DPpP, doing nothing' + def resumeProducing(self): r = self.decoder.read(8*1024) if r: @@ -62,10 +74,12 @@ class DecoderProducer: class AudioResource(resource.Resource): isLeaf = True - def __init__(self, f, dec): + def __init__(self, f, dec, start, cnt): resource.Resource.__init__(self) self.f = f self.dec = dec + self.start = start + self.cnt = cnt def calcrange(self, rng, l): rng = rng.strip() @@ -84,7 +98,7 @@ class AudioResource(resource.Resource): decoder = self.dec(self.f) request.setHeader('content-type', makemtfromdec(decoder)) bytespersample = decoder.channels * decoder.bitspersample / 8 - tbytes = decoder.totalsamples * bytespersample + tbytes = self.cnt * bytespersample skipbytes = 0 request.setHeader('content-length', tbytes) @@ -98,13 +112,15 @@ class AudioResource(resource.Resource): skipbytes = start % bytespersample print 'going:', start / bytespersample - decoder.goto(start / bytespersample) + decoder.goto(self.start + start / bytespersample) print 'there' request.setHeader('content-length', cnt) request.setHeader('content-range', 'bytes %s-%s/%s' % (start, start + cnt - 1, tbytes)) tbytes = cnt + else: + decoder.goto(self.start) if request.method == 'HEAD': return '' @@ -115,31 +131,110 @@ class AudioResource(resource.Resource): # and make sure the connection doesn't get closed return server.NOT_DONE_YET -class AudioRaw(AudioItem): +# XXX - maybe should be MusicAlbum, but needs to change AudioRaw +class AudioDisc(FSObject, Album): def __init__(self, *args, **kwargs): + self.cuesheet = kwargs.pop('cuesheet') + self.kwargs = kwargs.copy() + self.file = kwargs.pop('file') + nchan = kwargs['channels'] + samprate = kwargs['samplerate'] + bitsps = kwargs['bitspersample'] + samples = kwargs['samples'] + totalbytes = nchan * samples * bitsps / 8 + + FSObject.__init__(self, kwargs['path']) + + # XXX - exclude track 1 pre-gap? + kwargs['content'] = AudioResource(self.file, + kwargs.pop('decoder'), 0, kwargs['samples']) + print 'doing construction' + Album.__init__(self, *args, **kwargs) + + print 'adding resource' + self.url = '%s/%s' % (self.cd.urlbase, self.id) + self.res = ResourceList() + r = Resource(self.url, 'http-get:*:%s:*' % makeaudiomt(bitsps, + samprate, nchan)) + r.size = totalbytes + r.duration = float(samples) / samprate + r.bitrate = nchan * samprate * bitsps / 8 + r.sampleFrequency = samprate + r.bitsPerSample = bitsps + r.nrAudioChannels = nchan + self.res.append(r) + print 'completed' + + def sort(self, fun=lambda x, y: cmp(int(x.title), int(y.title))): + return list.sort(self, fun) + + def genChildren(self): + r = [ str(x['number']) for x in + self.cuesheet['tracks_array'] if x['number'] not in + (170, 255) ] + print 'gC:', `r` + return r + + def findtrackidx(self, trk): + for idx, i in enumerate(self.cuesheet['tracks_array']): + if i['number'] == trk: + return idx + + raise ValueError('track %d not found' % trk) + + @staticmethod + def findindexintrack(trk, idx): + for i in trk['indices_array']: + if i['number'] == idx: + return i + + raise ValueError('index %d not found in: %s' % (idx, trk)) + + def gettrackstart(self, i): + idx = self.findtrackidx(i) + track = self.cuesheet['tracks_array'][idx] + index = self.findindexintrack(track, 1) + return track['offset'] + index['offset'] + + def createObject(self, i, arg=None): + '''This function returns the (class, name, *args, **kwargs) + that will be passed to the addItem method of the + ContentDirectory. arg will be passed the value of the dict + keyed by i if genChildren is a dict.''' + + oi = i + i = int(i) + trkidx = self.findtrackidx(i) + trkarray = self.cuesheet['tracks_array'] + kwargs = self.kwargs.copy() + start = self.gettrackstart(i) + kwargs['start'] = start + kwargs['samples'] = trkarray[trkidx + 1]['offset'] - start + + return AudioRaw, oi, (), kwargs + +class AudioRaw(AudioItem, FSObject): + def __init__(self, *args, **kwargs): + file = kwargs.pop('file') nchan = kwargs.pop('channels') samprate = kwargs.pop('samplerate') bitsps = kwargs.pop('bitspersample') samples = kwargs.pop('samples') - self.totalbytes = nchan * samples * bitsps / 8 + startsamp = kwargs.pop('start', 0) + totalbytes = nchan * samples * bitsps / 8 - try: - mt = mtformat[bitsps] - except KeyError: - raise KeyError('No mime-type for audio format: %s.' % - `origfmt`) - - self.mt = '%s;rate=%d;channels=%d' % (mt, samprate, nchan) + FSObject.__init__(self, kwargs['path']) - kwargs['content'] = AudioResource(self.file, - kwargs.pop('decoder')) + kwargs['content'] = AudioResource(file, + kwargs.pop('decoder'), startsamp, startsamp + samples) AudioItem.__init__(self, *args, **kwargs) self.url = '%s/%s' % (self.cd.urlbase, self.id) self.res = ResourceList() - r = Resource(self.url, 'http-get:*:%s:*' % self.mt) - r.size = self.totalbytes + r = Resource(self.url, 'http-get:*:%s:*' % makeaudiomt(bitsps, + samprate, nchan)) + r.size = totalbytes r.duration = float(samples) / samprate r.bitrate = nchan * samprate * bitsps / 8 r.sampleFrequency = samprate @@ -155,7 +250,8 @@ def detectaudioraw(origpath, fobj): if obj.bitspersample not in (8, 16): continue - return AudioRaw, { + args = { + 'path': origpath, 'decoder': i, 'file': origpath, 'channels': obj.channels, @@ -163,6 +259,12 @@ def detectaudioraw(origpath, fobj): 'bitspersample': obj.bitspersample, 'samples': obj.totalsamples, } + + if obj.cuesheet is not None: + args['cuesheet'] = obj.cuesheet + return AudioDisc, args + + return AudioRaw, args except: #import traceback #traceback.print_exc() diff --git a/flac.py b/flac.py index 10fb84f..9974992 100644 --- a/flac.py +++ b/flac.py @@ -1,4 +1,6 @@ import array +import string +import UserDict from ctypes import * @@ -10,21 +12,55 @@ else: is_little_endian = True del t +interleavelib = CDLL('./_interleave.so') +inter = {} + +for i in (16, ): + f = getattr(interleavelib, 'interleave%d' % i) + f.restype = None + # bigendian, nchan, chanmap, chansamps, data, out + outtype = locals()['c_int%d' % i] + f.argtypes = [ c_int, POINTER(c_int), c_int, + POINTER(POINTER(c_int32)), POINTER(outtype), ] + inter[i] = outtype, f + flaclib = CDLL('libFLAC.so') # Defines +FLAC__METADATA_TYPE_STREAMINFO = 0 +FLAC__METADATA_TYPE_PADDING = 1 +FLAC__METADATA_TYPE_APPLICATION = 2 +FLAC__METADATA_TYPE_SEEKTABLE = 3 +FLAC__METADATA_TYPE_VORBIS_COMMENT = 4 +FLAC__METADATA_TYPE_CUESHEET = 5 +FLAC__METADATA_TYPE_PICTURE = 6 +FLAC__METADATA_TYPE_UNDEFINED = 7 + FLAC__MAX_CHANNELS = 8 FLAC__MAX_LPC_ORDER = 32 FLAC__MAX_FIXED_ORDER = 4 # Data +FLAC__StreamDecoderStateString = (c_char_p * 10).in_dll(flaclib, + 'FLAC__StreamDecoderStateString') FLAC__StreamDecoderInitStatusString = (c_char_p * 6).in_dll(flaclib, 'FLAC__StreamDecoderInitStatusString') FLAC__StreamDecoderErrorStatusString = (c_char_p * 4).in_dll(flaclib, 'FLAC__StreamDecoderErrorStatusString') # Enums +FLAC__STREAM_DECODER_SEARCH_FOR_METADATA = 0 +FLAC__STREAM_DECODER_READ_METADATA = 1 +FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC = 2 +FLAC__STREAM_DECODER_READ_FRAME = 3 +FLAC__STREAM_DECODER_END_OF_STREAM = 4 +FLAC__STREAM_DECODER_OGG_ERROR = 5 +FLAC__STREAM_DECODER_SEEK_ERROR = 6 +FLAC__STREAM_DECODER_ABORTED = 7 +FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR = 8 +FLAC__STREAM_DECODER_UNINITIALIZED = 9 + FLAC__STREAM_DECODER_INIT_STATUS_OK = 0 FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER = 0 FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER = 1 @@ -33,12 +69,54 @@ FLAC__SUBFRAME_TYPE_VERBATIM = 1 FLAC__SUBFRAME_TYPE_FIXED = 2 FLAC__SUBFRAME_TYPE_LPC = 3 +OrigStructure = Structure class Structure(Structure): - def __repr__(self): + def getarray(self, base): + base_array = getattr(self, base) + return [ base_array[x] for x in xrange(getattr(self, + 'num_' + base)) ] + + def __getattr__(self, k): + if k[-6:] == '_array': + return self.getarray(k[:-6]) + + raise AttributeError(k) + + def asobj(self): + r = {} + #print 'asobj:', `self` + if hasattr(self, '_material_'): + flds = self._material_ + else: + flds = self._fields_ + + for i in flds: + attr = i[0] + obj = getattr(self, attr) + if attr[-6:] == '_array': + #print 'asarraycnt' + v = [ x.asobj() for x in + self.getarray(attr[:-6]) ] + elif isinstance(obj, Structure): + #print 'asstruct' + v = obj.asobj() + elif isinstance(obj, Array) and obj._type_ != c_char: + #print 'asarray' + v = obj[:] + else: + #print 'asobj' + v = obj + r[attr] = v + + return r + + def __repr__(self, fields=None): cls = self.__class__ + if fields is None: + fields = self._fields_ return '<%s.%s: %s>' % (cls.__module__, cls.__name__, - ', '.join([ '%s: %s' % (x, `getattr(self, x)`) for x, t in - self._fields_ ])) + ', '.join([ '%s: %s' % (x[0], `getattr(self, x[0])`) + for x in fields ])) class FLAC__StreamMetadata_StreamInfo(Structure): _fields_ = [ ('min_blocksize', c_uint), @@ -49,11 +127,117 @@ class FLAC__StreamMetadata_StreamInfo(Structure): ('channels', c_uint), ('bits_per_sample', c_uint), ('total_samples', c_uint64), - ('md5sum', c_byte * 16), + ('md5sum', c_ubyte * 16), ] +class FLAC__StreamMetadata_VorbisComment_Entry(Structure): + _fields_ = [ ('length', c_uint32), + ('entry', POINTER(c_char)), # string bounded by length + ] + + def asstring(self): + return self.entry[:self.length].decode('utf-8') + + def __repr__(self): + return '' % \ + `self.asstring()` + +def makestrrange(a, b): + '''Make a string w/ characters from a through b (inclusive).''' + + return ''.join(chr(x) for x in xrange(a, b + 1)) + +class VorbisComments(UserDict.DictMixin): + def __init__(self, vc=()): + d = self._dict = {} + + for i in vc: + k, v = i.split('=', 1) + try: + self[k].append(v) + except KeyError: + d[self.makevalidkey(k)] = [ v ] + + transtable = string.maketrans(makestrrange(0x41, 0x5a), + makestrrange(0x61, 0x7a)) + delchars = makestrrange(0, 0x1f) + '=' + makestrrange(0x7e, 0x7f) + @staticmethod + def makevalidkey(k): + k = str(k) + origlen = len(k) + k = k.translate(VorbisComments.transtable, + VorbisComments.delchars) + if len(k) != origlen: + raise ValueError('Invalid key') + + return k + + def __getitem__(self, k): + return self._dict[self.makevalidkey(k)] + + def __setitem__(self, k, v): + # XXX - allow? check v? + return self._dict.__setitem__(self.makevalidkey(k), v) + + def __delitem__(self, k): + return self._dict.__delitem__(self.makevalidkey(k)) + + def keys(self): + return self._dict.keys() + +class FLAC__StreamMetadata_VorbisComment(Structure): + _fields_ = [ ('vendor_string', + FLAC__StreamMetadata_VorbisComment_Entry), + ('num_comments', c_uint32), + ('comments', POINTER(FLAC__StreamMetadata_VorbisComment_Entry)), + ] + + _material_ = (('vendor_string', ), ('comments_array', )) + def asobj(self): + return self.vendor_string.asstring(), \ + VorbisComments(x.asstring() for x in + self.getarray('comments')) + + def __repr__(self): + return Structure.__repr__(self, self._material_) + +class FLAC__StreamMetadata_CueSheet_Index(Structure): + _fields_ = [ ('offset', c_uint64), + ('number', c_ubyte), + ] + +class FLAC__StreamMetadata_CueSheet_Track(Structure): + _fields_ = [ ('offset', c_uint64), + ('number', c_ubyte), + ('isrc', c_char * 13), # string + NUL + ('type', c_ubyte, 1), + ('pre_emphasis', c_ubyte, 1), + ('num_indices', c_ubyte), + ('indices', POINTER(FLAC__StreamMetadata_CueSheet_Index)), + ] + _material_ = (('offset', ), ('number', ), ('isrc', ), ('type', ), + ('pre_emphasis', ), ('indices_array', )) + + def __repr__(self): + return Structure.__repr__(self, self._material_) + +class FLAC__StreamMetadata_CueSheet(Structure): + _fields_ = [ ('media_catalog_number', c_char * 129), # string + nul + ('lead_in', c_uint64), + ('is_cd', c_int), + ('num_tracks', c_uint), + ('tracks', POINTER(FLAC__StreamMetadata_CueSheet_Track)), + ] + _material_ = (('media_catalog_number', ), ('lead_in', ), ('is_cd', ), + ('tracks_array', )) + + def __repr__(self): + return Structure.__repr__(self, self._material_) + class FLAC__StreamMetadataData(Union): _fields_ = [ ('stream_info', FLAC__StreamMetadata_StreamInfo), + ('vorbis_comment', FLAC__StreamMetadata_VorbisComment), + ('cue_sheet', FLAC__StreamMetadata_CueSheet), ] class FLAC__StreamMetadata(Structure): @@ -68,7 +252,8 @@ class FLAC__EntropyCodingMethod_PartitionedRiceContents(Structure): class FLAC__EntropyCodingMethod_PartitionedRice(Structure): _fields_ = [ ('order', c_uint), - ('contents', POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents)), + ('contents', + POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents)), ] class FLAC__EntropyCodingMethod(Structure): @@ -77,12 +262,10 @@ class FLAC__EntropyCodingMethod(Structure): ] class FLAC__Subframe_Constant(Structure): - _fields_ = [ ('value', c_int32), - ] + _fields_ = [ ('value', c_int32), ] class FLAC__Subframe_Verbatim(Structure): - _fields_ = [ ('data', POINTER(c_int32)), - ] + _fields_ = [ ('data', POINTER(c_int32)), ] class FLAC__Subframe_Fixed(Structure): _fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod), @@ -150,7 +333,7 @@ FLAC__StreamDecoder_p = POINTER(FLAC__StreamDecoder) # Function typedefs FLAC__StreamDecoderReadCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, - POINTER(c_byte), POINTER(c_size_t), c_void_p) + POINTER(c_ubyte), POINTER(c_size_t), c_void_p) FLAC__StreamDecoderSeekCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, c_uint64, c_void_p) FLAC__StreamDecoderTellCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, @@ -171,8 +354,12 @@ funs = { 'FLAC__stream_decoder_delete': (None, [FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_set_md5_checking': (c_int, [ FLAC__StreamDecoder_p, c_int, ]), + 'FLAC__stream_decoder_set_metadata_respond': (c_int, + [ FLAC__StreamDecoder_p, c_int, ]), 'FLAC__stream_decoder_set_metadata_respond_all': (c_int, [ FLAC__StreamDecoder_p, ]), + 'FLAC__stream_decoder_get_state': (c_int, + [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_get_total_samples': (c_uint64, [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_get_channels': (c_uint, @@ -192,9 +379,9 @@ funs = { FLAC__StreamDecoderMetadataCallback, FLAC__StreamDecoderErrorCallback, ]), 'FLAC__stream_decoder_init_file': (c_int, [ FLAC__StreamDecoder_p, - FLAC__StreamDecoderWriteCallback, + c_char_p, FLAC__StreamDecoderWriteCallback, FLAC__StreamDecoderMetadataCallback, - FLAC__StreamDecoderErrorCallback, ]), + FLAC__StreamDecoderErrorCallback, c_void_p, ]), 'FLAC__stream_decoder_finish': (c_int, [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_flush': (c_int, [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_reset': (c_int, [ FLAC__StreamDecoder_p, ]), @@ -233,10 +420,6 @@ class FLACDec(object): self.flacdec = None self._lasterror = None - self.flacdec = flaclib.FLAC__stream_decoder_new() - if self.flacdec == 0: - raise RuntimeError('allocating decoded') - # We need to keep references to the callback functions # around so they won't be garbage collected. self.write_wrap = FLAC__StreamDecoderWriteCallback( @@ -246,9 +429,16 @@ class FLACDec(object): self.error_wrap = FLAC__StreamDecoderErrorCallback( clientdatawrapper(self.cb_error)) + self.flacdec = flaclib.FLAC__stream_decoder_new() + if self.flacdec == 0: + raise RuntimeError('allocating decoded') + flaclib.FLAC__stream_decoder_set_md5_checking(self.flacdec, True) - + flaclib.FLAC__stream_decoder_set_metadata_respond(self.flacdec, + FLAC__METADATA_TYPE_VORBIS_COMMENT) + flaclib.FLAC__stream_decoder_set_metadata_respond(self.flacdec, + FLAC__METADATA_TYPE_CUESHEET) status = flaclib.FLAC__stream_decoder_init_file(self.flacdec, file, self.write_wrap, self.metadata_wrap, @@ -257,15 +447,19 @@ class FLACDec(object): raise ValueError( FLAC__StreamDecoderInitStatusString[status]) - print 'init' - flaclib.FLAC__stream_decoder_process_until_end_of_metadata(self.flacdec) - print 'end of meta' + self.vorbis = None + self.cuesheet = None + + flaclib.FLAC__stream_decoder_process_until_end_of_metadata( + self.flacdec) if self._lasterror is not None: raise ValueError( FLAC__StreamDecoderErrorStatusString[ self._lasterror]) self.curcnt = 0 self.cursamp = 0 + if self.vorbis is None: + self.vorbis = '', VorbisComments() def close(self): if self.flacdec is None: @@ -294,9 +488,19 @@ class FLACDec(object): nchan = frame.header.channels #print 'sample number:', frame.header.number.sample_number - if frame.header.bits_per_sample == 16: - self.cursamp = frame.header.number.sample_number - self.curcnt = frame.header.blocksize + self.cursamp = frame.header.number.sample_number + self.curcnt = frame.header.blocksize + if True: + outtype, fun = inter[frame.header.bits_per_sample] + outbuf = (outtype * (nchan * frame.header.blocksize))() + # nchan, chanmap, chansamps, data, out + assert nchan == 2 + fun(nchan, (c_int * 2)(0, 1), + frame.header.blocksize, buffer_pp, outbuf) + self.curdata = array.array('h', outbuf) + if is_little_endian: + self.curdata.byteswap() + elif frame.header.bits_per_sample == 16: self.curdata = array.array('h', [ buffer_pp[x % nchan][x / nchan] for x in xrange(frame.header.blocksize * nchan)]) @@ -310,33 +514,58 @@ class FLACDec(object): def cb_metadata(self, metadata_p): md = metadata_p[0] print 'metadata:', `md` - if md.type == 0: + if md.type == FLAC__METADATA_TYPE_STREAMINFO: si = md.data.stream_info self._channels = si.channels self._samplerate = si.sample_rate self._bitspersample = si.bits_per_sample self._totalsamples = si.total_samples - self._bytespersample = si.channels * si.bits_per_sample / 8 + self._bytespersample = si.channels * \ + si.bits_per_sample / 8 print `si` + elif md.type == FLAC__METADATA_TYPE_VORBIS_COMMENT: + self.vorbis = md.data.vorbis_comment.asobj() + print 'vc:', `md.data.vorbis_comment` + print 'v:', `self.vorbis` + elif md.type == FLAC__METADATA_TYPE_CUESHEET: + self.cuesheet = md.data.cue_sheet.asobj() + print 'cs:', `md.data.cue_sheet` + print 'c:', `self.cuesheet` + else: + print 'unknown metatype:', md.type def cb_error(self, errstatus): + #print 'error:', `errstatus` self._lasterror = errstatus + def getstate(self): + state = flaclib.FLAC__stream_decoder_get_state(self.flacdec) + return state + + state = property(getstate) + def goto(self, pos): if self.flacdec is None: raise ValueError('closed') - pos = min(self.totalsamples, pos) - if flaclib.FLAC__stream_decoder_seek_absolute(self.flacdec, pos): + pos = min(self.totalsamples - 1, pos) + if flaclib.FLAC__stream_decoder_seek_absolute(self.flacdec, + pos): return # the slow way - if self.cursamp > pos: + state = self.state + if state == FLAC__STREAM_DECODER_SEEK_ERROR: + print 'WARNING: possibly invalid file!' + + if self.cursamp > pos or \ + state == FLAC__STREAM_DECODER_SEEK_ERROR: if not flaclib.FLAC__stream_decoder_reset(self.flacdec): raise RuntimeError('unable to seek to beginin') flaclib.FLAC__stream_decoder_process_until_end_of_metadata(self.flacdec) - flaclib.FLAC__stream_decoder_process_single(self.flacdec) + flaclib.FLAC__stream_decoder_process_single( + self.flacdec) read = pos - self.cursamp while read: @@ -344,17 +573,19 @@ class FLACDec(object): self.read(tread) read -= tread - def read(self, nsamp): + def read(self, nsamp=16*1024): if self.flacdec is None: raise ValueError('closed') r = [] nsamp = min(nsamp, self.totalsamples - self.cursamp) + nchan = self.channels while nsamp: cnt = min(nsamp, self.curcnt) - sampcnt = cnt * self.channels + sampcnt = cnt * nchan if cnt == 0 and self.curcnt == 0: - flaclib.FLAC__stream_decoder_process_single(self.flacdec) + flaclib.FLAC__stream_decoder_process_single( + self.flacdec) continue r.append(self.curdata[:sampcnt].tostring()) @@ -366,6 +597,21 @@ class FLACDec(object): return ''.join(r) +def doprofile(d): + def readtoend(): + while d.read(): + pass + + import hotshot.stats + + prof = hotshot.Profile('decread.prof') + prof.runcall(readtoend) + prof.close() + stats = hotshot.stats.load('decread.prof') + stats.strip_dirs() + stats.sort_stats('time', 'calls') + stats.print_stats(20) + if __name__ == '__main__': import sys @@ -376,6 +622,7 @@ if __name__ == '__main__': print 'bps:', d.bitspersample print `d.read(10)` print 'going' - d.goto(d.totalsamples - 128) + d.goto(d.totalsamples - 1000*1000) print 'here' print `d.read(10)` + doprofile(d) diff --git a/interleave.c b/interleave.c new file mode 100644 index 0000000..9019310 --- /dev/null +++ b/interleave.c @@ -0,0 +1,13 @@ +#include + +#define INTERLEAVEFUN(nbits) void \ +interleave ## nbits (int nchan, int chanmap[], int chansamps, int32_t *data[], int ## nbits ## _t *out) \ +{ \ + int i; \ + \ + for (i = 0; i < chansamps * nchan; i++) { \ + out[i] = (int ## nbits ## _t)data[chanmap[i % nchan]][i / nchan]; \ + } \ +} + +INTERLEAVEFUN(16)