Browse Source

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]
main
John-Mark Gurney 15 years ago
parent
commit
54c742f5fa
4 changed files with 421 additions and 55 deletions
  1. +5
    -1
      README
  2. +123
    -21
      audioraw.py
  3. +280
    -33
      flac.py
  4. +13
    -0
      interleave.c

+ 5
- 1
README View File

@@ -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,


+ 123
- 21
audioraw.py View File

@@ -2,8 +2,8 @@
# Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
'''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()


+ 280
- 33
flac.py View File

@@ -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 '<FLAC__StreamMetadata_VorbisComment_Entry: %s>' % \
`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)

+ 13
- 0
interleave.c View File

@@ -0,0 +1,13 @@
#include <sys/types.h>

#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)

Loading…
Cancel
Save