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 WAX/ASX files in shoutcast.
Add support for FLAC audio files. If you have libFLAC installed, Add support for FLAC audio files. If you have libFLAC installed,
it will dynamicly decompress the audio. It supports seeking so 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 Files in the root directory of zip archives should now
work. work.
Support automatic formating of some attributes. One is duration, 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> # Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
'''Audio Raw Converter''' '''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.web import resource, server
from twisted.internet.interfaces import IPullProducer from twisted.internet.interfaces import IPullProducer
@@ -21,9 +21,18 @@ mtformat = {
# 8: 'audio/l8', unsigned # 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): 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: class DecoderProducer:
implements(IPullProducer) implements(IPullProducer)
@@ -35,6 +44,9 @@ class DecoderProducer:
self.skipbytes = skipbytes self.skipbytes = skipbytes
consumer.registerProducer(self, False) consumer.registerProducer(self, False)


def pauseProducing(self):
print 'DPpP, doing nothing'

def resumeProducing(self): def resumeProducing(self):
r = self.decoder.read(8*1024) r = self.decoder.read(8*1024)
if r: if r:
@@ -62,10 +74,12 @@ class DecoderProducer:
class AudioResource(resource.Resource): class AudioResource(resource.Resource):
isLeaf = True isLeaf = True


def __init__(self, f, dec):
def __init__(self, f, dec, start, cnt):
resource.Resource.__init__(self) resource.Resource.__init__(self)
self.f = f self.f = f
self.dec = dec self.dec = dec
self.start = start
self.cnt = cnt


def calcrange(self, rng, l): def calcrange(self, rng, l):
rng = rng.strip() rng = rng.strip()
@@ -84,7 +98,7 @@ class AudioResource(resource.Resource):
decoder = self.dec(self.f) decoder = self.dec(self.f)
request.setHeader('content-type', makemtfromdec(decoder)) request.setHeader('content-type', makemtfromdec(decoder))
bytespersample = decoder.channels * decoder.bitspersample / 8 bytespersample = decoder.channels * decoder.bitspersample / 8
tbytes = decoder.totalsamples * bytespersample
tbytes = self.cnt * bytespersample
skipbytes = 0 skipbytes = 0


request.setHeader('content-length', tbytes) request.setHeader('content-length', tbytes)
@@ -98,13 +112,15 @@ class AudioResource(resource.Resource):
skipbytes = start % bytespersample skipbytes = start % bytespersample


print 'going:', start / bytespersample print 'going:', start / bytespersample
decoder.goto(start / bytespersample)
decoder.goto(self.start + start / bytespersample)
print 'there' print 'there'


request.setHeader('content-length', cnt) request.setHeader('content-length', cnt)
request.setHeader('content-range', 'bytes %s-%s/%s' % request.setHeader('content-range', 'bytes %s-%s/%s' %
(start, start + cnt - 1, tbytes)) (start, start + cnt - 1, tbytes))
tbytes = cnt tbytes = cnt
else:
decoder.goto(self.start)


if request.method == 'HEAD': if request.method == 'HEAD':
return '' return ''
@@ -115,31 +131,110 @@ class AudioResource(resource.Resource):
# and make sure the connection doesn't get closed # and make sure the connection doesn't get closed
return server.NOT_DONE_YET 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): def __init__(self, *args, **kwargs):
self.cuesheet = kwargs.pop('cuesheet')
self.kwargs = kwargs.copy()

self.file = kwargs.pop('file') 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') nchan = kwargs.pop('channels')
samprate = kwargs.pop('samplerate') samprate = kwargs.pop('samplerate')
bitsps = kwargs.pop('bitspersample') bitsps = kwargs.pop('bitspersample')
samples = kwargs.pop('samples') 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) AudioItem.__init__(self, *args, **kwargs)


self.url = '%s/%s' % (self.cd.urlbase, self.id) self.url = '%s/%s' % (self.cd.urlbase, self.id)
self.res = ResourceList() 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.duration = float(samples) / samprate
r.bitrate = nchan * samprate * bitsps / 8 r.bitrate = nchan * samprate * bitsps / 8
r.sampleFrequency = samprate r.sampleFrequency = samprate
@@ -155,7 +250,8 @@ def detectaudioraw(origpath, fobj):
if obj.bitspersample not in (8, 16): if obj.bitspersample not in (8, 16):
continue continue


return AudioRaw, {
args = {
'path': origpath,
'decoder': i, 'decoder': i,
'file': origpath, 'file': origpath,
'channels': obj.channels, 'channels': obj.channels,
@@ -163,6 +259,12 @@ def detectaudioraw(origpath, fobj):
'bitspersample': obj.bitspersample, 'bitspersample': obj.bitspersample,
'samples': obj.totalsamples, 'samples': obj.totalsamples,
} }

if obj.cuesheet is not None:
args['cuesheet'] = obj.cuesheet
return AudioDisc, args

return AudioRaw, args
except: except:
#import traceback #import traceback
#traceback.print_exc() #traceback.print_exc()


+ 280
- 33
flac.py View File

@@ -1,4 +1,6 @@
import array import array
import string
import UserDict


from ctypes import * from ctypes import *


@@ -10,21 +12,55 @@ else:
is_little_endian = True is_little_endian = True
del t 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') flaclib = CDLL('libFLAC.so')


# Defines # 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_CHANNELS = 8
FLAC__MAX_LPC_ORDER = 32 FLAC__MAX_LPC_ORDER = 32
FLAC__MAX_FIXED_ORDER = 4 FLAC__MAX_FIXED_ORDER = 4


# Data # Data
FLAC__StreamDecoderStateString = (c_char_p * 10).in_dll(flaclib,
'FLAC__StreamDecoderStateString')
FLAC__StreamDecoderInitStatusString = (c_char_p * 6).in_dll(flaclib, FLAC__StreamDecoderInitStatusString = (c_char_p * 6).in_dll(flaclib,
'FLAC__StreamDecoderInitStatusString') 'FLAC__StreamDecoderInitStatusString')
FLAC__StreamDecoderErrorStatusString = (c_char_p * 4).in_dll(flaclib, FLAC__StreamDecoderErrorStatusString = (c_char_p * 4).in_dll(flaclib,
'FLAC__StreamDecoderErrorStatusString') 'FLAC__StreamDecoderErrorStatusString')


# Enums # 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__STREAM_DECODER_INIT_STATUS_OK = 0
FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER = 0 FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER = 0
FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER = 1 FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER = 1
@@ -33,12 +69,54 @@ FLAC__SUBFRAME_TYPE_VERBATIM = 1
FLAC__SUBFRAME_TYPE_FIXED = 2 FLAC__SUBFRAME_TYPE_FIXED = 2
FLAC__SUBFRAME_TYPE_LPC = 3 FLAC__SUBFRAME_TYPE_LPC = 3


OrigStructure = Structure
class Structure(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__ cls = self.__class__
if fields is None:
fields = self._fields_
return '<%s.%s: %s>' % (cls.__module__, cls.__name__, 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): class FLAC__StreamMetadata_StreamInfo(Structure):
_fields_ = [ ('min_blocksize', c_uint), _fields_ = [ ('min_blocksize', c_uint),
@@ -49,11 +127,117 @@ class FLAC__StreamMetadata_StreamInfo(Structure):
('channels', c_uint), ('channels', c_uint),
('bits_per_sample', c_uint), ('bits_per_sample', c_uint),
('total_samples', c_uint64), ('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): class FLAC__StreamMetadataData(Union):
_fields_ = [ ('stream_info', FLAC__StreamMetadata_StreamInfo), _fields_ = [ ('stream_info', FLAC__StreamMetadata_StreamInfo),
('vorbis_comment', FLAC__StreamMetadata_VorbisComment),
('cue_sheet', FLAC__StreamMetadata_CueSheet),
] ]


class FLAC__StreamMetadata(Structure): class FLAC__StreamMetadata(Structure):
@@ -68,7 +252,8 @@ class FLAC__EntropyCodingMethod_PartitionedRiceContents(Structure):


class FLAC__EntropyCodingMethod_PartitionedRice(Structure): class FLAC__EntropyCodingMethod_PartitionedRice(Structure):
_fields_ = [ ('order', c_uint), _fields_ = [ ('order', c_uint),
('contents', POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents)),
('contents',
POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents)),
] ]


class FLAC__EntropyCodingMethod(Structure): class FLAC__EntropyCodingMethod(Structure):
@@ -77,12 +262,10 @@ class FLAC__EntropyCodingMethod(Structure):
] ]


class FLAC__Subframe_Constant(Structure): class FLAC__Subframe_Constant(Structure):
_fields_ = [ ('value', c_int32),
]
_fields_ = [ ('value', c_int32), ]


class FLAC__Subframe_Verbatim(Structure): class FLAC__Subframe_Verbatim(Structure):
_fields_ = [ ('data', POINTER(c_int32)),
]
_fields_ = [ ('data', POINTER(c_int32)), ]


class FLAC__Subframe_Fixed(Structure): class FLAC__Subframe_Fixed(Structure):
_fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod), _fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod),
@@ -150,7 +333,7 @@ FLAC__StreamDecoder_p = POINTER(FLAC__StreamDecoder)


# Function typedefs # Function typedefs
FLAC__StreamDecoderReadCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, 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, FLAC__StreamDecoderSeekCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
c_uint64, c_void_p) c_uint64, c_void_p)
FLAC__StreamDecoderTellCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_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_delete': (None, [FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_set_md5_checking': (c_int, 'FLAC__stream_decoder_set_md5_checking': (c_int,
[ FLAC__StreamDecoder_p, 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__stream_decoder_set_metadata_respond_all': (c_int,
[ FLAC__StreamDecoder_p, ]), [ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_state': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_total_samples': (c_uint64, 'FLAC__stream_decoder_get_total_samples': (c_uint64,
[ FLAC__StreamDecoder_p, ]), [ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_channels': (c_uint, 'FLAC__stream_decoder_get_channels': (c_uint,
@@ -192,9 +379,9 @@ funs = {
FLAC__StreamDecoderMetadataCallback, FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback, ]), FLAC__StreamDecoderErrorCallback, ]),
'FLAC__stream_decoder_init_file': (c_int, [ FLAC__StreamDecoder_p, 'FLAC__stream_decoder_init_file': (c_int, [ FLAC__StreamDecoder_p,
FLAC__StreamDecoderWriteCallback,
c_char_p, FLAC__StreamDecoderWriteCallback,
FLAC__StreamDecoderMetadataCallback, FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback, ]),
FLAC__StreamDecoderErrorCallback, c_void_p, ]),
'FLAC__stream_decoder_finish': (c_int, [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_finish': (c_int, [ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_flush': (c_int, [ FLAC__StreamDecoder_p, ]), 'FLAC__stream_decoder_flush': (c_int, [ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_reset': (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.flacdec = None
self._lasterror = 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 # We need to keep references to the callback functions
# around so they won't be garbage collected. # around so they won't be garbage collected.
self.write_wrap = FLAC__StreamDecoderWriteCallback( self.write_wrap = FLAC__StreamDecoderWriteCallback(
@@ -246,9 +429,16 @@ class FLACDec(object):
self.error_wrap = FLAC__StreamDecoderErrorCallback( self.error_wrap = FLAC__StreamDecoderErrorCallback(
clientdatawrapper(self.cb_error)) 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, flaclib.FLAC__stream_decoder_set_md5_checking(self.flacdec,
True) 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, status = flaclib.FLAC__stream_decoder_init_file(self.flacdec,
file, self.write_wrap, self.metadata_wrap, file, self.write_wrap, self.metadata_wrap,
@@ -257,15 +447,19 @@ class FLACDec(object):
raise ValueError( raise ValueError(
FLAC__StreamDecoderInitStatusString[status]) 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: if self._lasterror is not None:
raise ValueError( raise ValueError(
FLAC__StreamDecoderErrorStatusString[ FLAC__StreamDecoderErrorStatusString[
self._lasterror]) self._lasterror])
self.curcnt = 0 self.curcnt = 0
self.cursamp = 0 self.cursamp = 0
if self.vorbis is None:
self.vorbis = '', VorbisComments()


def close(self): def close(self):
if self.flacdec is None: if self.flacdec is None:
@@ -294,9 +488,19 @@ class FLACDec(object):


nchan = frame.header.channels nchan = frame.header.channels
#print 'sample number:', frame.header.number.sample_number #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', self.curdata = array.array('h',
[ buffer_pp[x % nchan][x / nchan] for x in [ buffer_pp[x % nchan][x / nchan] for x in
xrange(frame.header.blocksize * nchan)]) xrange(frame.header.blocksize * nchan)])
@@ -310,33 +514,58 @@ class FLACDec(object):
def cb_metadata(self, metadata_p): def cb_metadata(self, metadata_p):
md = metadata_p[0] md = metadata_p[0]
print 'metadata:', `md` print 'metadata:', `md`
if md.type == 0:
if md.type == FLAC__METADATA_TYPE_STREAMINFO:
si = md.data.stream_info si = md.data.stream_info
self._channels = si.channels self._channels = si.channels
self._samplerate = si.sample_rate self._samplerate = si.sample_rate
self._bitspersample = si.bits_per_sample self._bitspersample = si.bits_per_sample
self._totalsamples = si.total_samples 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` 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): def cb_error(self, errstatus):
#print 'error:', `errstatus`
self._lasterror = 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): def goto(self, pos):
if self.flacdec is None: if self.flacdec is None:
raise ValueError('closed') 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 return


# the slow way # 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): if not flaclib.FLAC__stream_decoder_reset(self.flacdec):
raise RuntimeError('unable to seek to beginin') raise RuntimeError('unable to seek to beginin')
flaclib.FLAC__stream_decoder_process_until_end_of_metadata(self.flacdec) 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 read = pos - self.cursamp
while read: while read:
@@ -344,17 +573,19 @@ class FLACDec(object):
self.read(tread) self.read(tread)
read -= tread read -= tread


def read(self, nsamp):
def read(self, nsamp=16*1024):
if self.flacdec is None: if self.flacdec is None:
raise ValueError('closed') raise ValueError('closed')


r = [] r = []
nsamp = min(nsamp, self.totalsamples - self.cursamp) nsamp = min(nsamp, self.totalsamples - self.cursamp)
nchan = self.channels
while nsamp: while nsamp:
cnt = min(nsamp, self.curcnt) cnt = min(nsamp, self.curcnt)
sampcnt = cnt * self.channels
sampcnt = cnt * nchan
if cnt == 0 and self.curcnt == 0: if cnt == 0 and self.curcnt == 0:
flaclib.FLAC__stream_decoder_process_single(self.flacdec)
flaclib.FLAC__stream_decoder_process_single(
self.flacdec)
continue continue


r.append(self.curdata[:sampcnt].tostring()) r.append(self.curdata[:sampcnt].tostring())
@@ -366,6 +597,21 @@ class FLACDec(object):


return ''.join(r) 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__': if __name__ == '__main__':
import sys import sys


@@ -376,6 +622,7 @@ if __name__ == '__main__':
print 'bps:', d.bitspersample print 'bps:', d.bitspersample
print `d.read(10)` print `d.read(10)`
print 'going' print 'going'
d.goto(d.totalsamples - 128)
d.goto(d.totalsamples - 1000*1000)
print 'here' print 'here'
print `d.read(10)` 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