Browse Source

add a raw audio transcoder... This currently supports flac...

it supports seeking, but only 16bit audio currently...

[git-p4: depot-paths = "//depot/": change = 1411]
main
John-Mark Gurney 15 years ago
parent
commit
8fb11a4bdd
3 changed files with 555 additions and 0 deletions
  1. +173
    -0
      audioraw.py
  2. +381
    -0
      flac.py
  3. +1
    -0
      pymeds.py

+ 173
- 0
audioraw.py View File

@@ -0,0 +1,173 @@
#!/usr/bin/env python
# Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
'''Audio Raw Converter'''

from DIDLLite import AudioItem, Resource, ResourceList
from FSStorage import registerklassfun

from twisted.web import resource, server
from twisted.internet.interfaces import IPullProducer
from zope.interface import implements

decoders = {}
try:
import flac
decoders['flac'] = flac.FLACDec
except ImportError:
pass

mtformat = {
16: 'audio/l16', # BE signed
# 8: 'audio/l8', unsigned
}

def makemtfromdec(dec):
return '%s;rate=%d;channels=%d' % (mtformat[dec.bitspersample],
dec.samplerate, dec.channels)

class DecoderProducer:
implements(IPullProducer)

def __init__(self, consumer, decoder, tbytes, skipbytes):
self.decoder = decoder
self.consumer = consumer
self.tbytes = tbytes
self.skipbytes = skipbytes
consumer.registerProducer(self, False)

def resumeProducing(self):
r = self.decoder.read(8*1024)
if r:
#print 'DPrP:', len(r)
if self.skipbytes:
cnt = min(self.skipbytes, len(r))
r = r[cnt:]
self.skipbytes -= cnt

send = min(len(r), self.tbytes)
r = r[:send]
self.tbytes -= len(r)
self.consumer.write(r)
if self.tbytes:
return

print 'DPurP'
self.consumer.unregisterProducer()

def stopProducing(self):
print 'DPsP'
self.decoder.close()
self.decoder = None

class AudioResource(resource.Resource):
isLeaf = True

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

def calcrange(self, rng, l):
rng = rng.strip()
unit, rangeset = rng.split('=')
assert unit == 'bytes', `unit`
start, end = rangeset.split('-')
start = int(start)
if end:
end = int(end)
else:
end = l

return start, end - start + 1

def render(self, request):
decoder = self.dec(self.f)
request.setHeader('content-type', makemtfromdec(decoder))
bytespersample = decoder.channels * decoder.bitspersample / 8
tbytes = decoder.totalsamples * bytespersample
skipbytes = 0

request.setHeader('content-length', tbytes)
request.setHeader('accept-ranges', 'bytes')

if request.requestHeaders.hasHeader('range'):
print 'range req:', `request.requestHeaders.getRawHeaders('range')`
start, cnt = self.calcrange(
request.requestHeaders.getRawHeaders('range')[0],
tbytes)
skipbytes = start % bytespersample

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

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

if request.method == 'HEAD':
return ''

DecoderProducer(request, decoder, tbytes, skipbytes)
print 'producing render'

# and make sure the connection doesn't get closed
return server.NOT_DONE_YET

class AudioRaw(AudioItem):
def __init__(self, *args, **kwargs):
self.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

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)

kwargs['content'] = AudioResource(self.file,
kwargs.pop('decoder'))
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.duration = float(samples) / samprate
r.bitrate = nchan * samprate * bitsps / 8
r.sampleFrequency = samprate
r.bitsPerSample = bitsps
r.nrAudioChannels = nchan
self.res.append(r)

def detectaudioraw(origpath, fobj):
for i in decoders.itervalues():
try:
obj = i(origpath)
# XXX - don't support down sampling yet
if obj.bitspersample not in (8, 16):
continue

return AudioRaw, {
'decoder': i,
'file': origpath,
'channels': obj.channels,
'samplerate': obj.samplerate,
'bitspersample': obj.bitspersample,
'samples': obj.totalsamples,
}
except:
#import traceback
#traceback.print_exc()
pass

return None, None

registerklassfun(detectaudioraw, True)

+ 381
- 0
flac.py View File

@@ -0,0 +1,381 @@
import array

from ctypes import *

# Find out if we need to endian swap the buffer
t = array.array('H', '\x00\x01')
if t[0] == 1:
is_little_endian = False
else:
is_little_endian = True
del t

flaclib = CDLL('libFLAC.so')

# Defines

FLAC__MAX_CHANNELS = 8
FLAC__MAX_LPC_ORDER = 32
FLAC__MAX_FIXED_ORDER = 4

# Data
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_INIT_STATUS_OK = 0
FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER = 0
FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER = 1
FLAC__SUBFRAME_TYPE_CONSTANT = 0
FLAC__SUBFRAME_TYPE_VERBATIM = 1
FLAC__SUBFRAME_TYPE_FIXED = 2
FLAC__SUBFRAME_TYPE_LPC = 3

class Structure(Structure):
def __repr__(self):
cls = self.__class__
return '<%s.%s: %s>' % (cls.__module__, cls.__name__,
', '.join([ '%s: %s' % (x, `getattr(self, x)`) for x, t in
self._fields_ ]))

class FLAC__StreamMetadata_StreamInfo(Structure):
_fields_ = [ ('min_blocksize', c_uint),
('max_blocksize', c_uint),
('min_framesize', c_uint),
('max_framesize', c_uint),
('sample_rate', c_uint),
('channels', c_uint),
('bits_per_sample', c_uint),
('total_samples', c_uint64),
('md5sum', c_byte * 16),
]

class FLAC__StreamMetadataData(Union):
_fields_ = [ ('stream_info', FLAC__StreamMetadata_StreamInfo),
]

class FLAC__StreamMetadata(Structure):
_fields_ = [ ('type', c_int),
('is_last', c_int),
('length', c_uint),
('data', FLAC__StreamMetadataData),
]

class FLAC__EntropyCodingMethod_PartitionedRiceContents(Structure):
pass

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

class FLAC__EntropyCodingMethod(Structure):
_fields_ = [ ('type', c_int),
('partitioned_rice', FLAC__EntropyCodingMethod_PartitionedRice),
]

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

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

class FLAC__Subframe_Fixed(Structure):
_fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod),
('order', c_uint),
('warmup', c_int32 * FLAC__MAX_FIXED_ORDER),
('residual', POINTER(c_int32)),
]

class FLAC__Subframe_LPC(Structure):
_fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod),
('order', c_uint),
('qlp_coeff_precision', c_uint),
('quantization_level', c_int),
('qlp_coeff', c_int32 * FLAC__MAX_LPC_ORDER),
('warmup', c_int32 * FLAC__MAX_LPC_ORDER),
('residual', POINTER(c_int32)),
]

class FLAC__SubframeUnion(Union):
_fields_ = [ ('constant', FLAC__Subframe_Constant),
('fixed', FLAC__Subframe_Fixed),
('lpc', FLAC__Subframe_LPC),
('verbatim', FLAC__Subframe_Verbatim),
]

class FLAC__Subframe(Structure):
_fields_ = [ ('type', c_int),
('data', FLAC__SubframeUnion),
('wasted_bits', c_uint),
]

class number_union(Union):
_fields_ = [ ('frame_number', c_uint32),
('sample_number', c_uint64),
]

class FLAC__FrameHeader(Structure):
_fields_ = [ ('blocksize', c_uint),
('sample_rate', c_uint),
('channels', c_uint),
('channel_assignment', c_int),
('bits_per_sample', c_uint),
('number_type', c_int),
('number', number_union),
('crc', c_uint8),
]

class FLAC__FrameFooter(Structure):
_fields_ = [ ('crc', c_uint16), ]

class FLAC__Frame(Structure):
_fields_ = [ ('header', FLAC__FrameHeader),
('subframes', FLAC__Subframe * FLAC__MAX_CHANNELS),
('footer', FLAC__FrameFooter),
]

class FLAC__StreamDecoder(Structure):
pass

# Types

FLAC__StreamMetadata_p = POINTER(FLAC__StreamMetadata)
FLAC__Frame_p = POINTER(FLAC__Frame)
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)
FLAC__StreamDecoderSeekCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
c_uint64, c_void_p)
FLAC__StreamDecoderTellCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
POINTER(c_uint64), c_void_p)
FLAC__StreamDecoderLengthCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
POINTER(c_uint64), c_void_p)
FLAC__StreamDecoderEofCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
c_void_p)
FLAC__StreamDecoderWriteCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p,
FLAC__Frame_p, POINTER(POINTER(c_int32)), c_void_p)
FLAC__StreamDecoderMetadataCallback = CFUNCTYPE(None, FLAC__StreamDecoder_p,
FLAC__StreamMetadata_p, c_void_p)
FLAC__StreamDecoderErrorCallback = CFUNCTYPE(None, FLAC__StreamDecoder_p,
c_int, c_void_p)

funs = {
'FLAC__stream_decoder_new': (FLAC__StreamDecoder_p, []),
'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_all': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_total_samples': (c_uint64,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_channels': (c_uint,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_channel_assignment': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_bits_per_sample': (c_uint,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_sample_rate': (c_uint,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_get_blocksize': (c_uint,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_init_stream': (c_int, [ FLAC__StreamDecoder_p,
FLAC__StreamDecoderReadCallback, FLAC__StreamDecoderSeekCallback,
FLAC__StreamDecoderTellCallback, FLAC__StreamDecoderLengthCallback,
FLAC__StreamDecoderEofCallback, FLAC__StreamDecoderWriteCallback,
FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback, ]),
'FLAC__stream_decoder_init_file': (c_int, [ FLAC__StreamDecoder_p,
FLAC__StreamDecoderWriteCallback,
FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback, ]),
'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, ]),
'FLAC__stream_decoder_process_single': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_process_until_end_of_metadata': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_process_until_end_of_stream': (c_int,
[ FLAC__StreamDecoder_p, ]),
'FLAC__stream_decoder_seek_absolute': (c_int,
[ FLAC__StreamDecoder_p, c_uint64, ]),
}

for i in funs:
f = getattr(flaclib, i)
f.restype, f.argtypes = funs[i]

def clientdatawrapper(fun):
def newfun(*args):
#print 'nf:', `args`
return fun(*args[1:-1])

return newfun

class FLACDec(object):
channels = property(lambda x: x._channels)
samplerate = property(lambda x: x._samplerate)
bitspersample = property(lambda x: x._bitspersample)
totalsamples = property(lambda x: x._totalsamples)
bytespersample = property(lambda x: x._bytespersample)

def __len__(self):
return self._total_samps

def __init__(self, file):
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(
clientdatawrapper(self.cb_write))
self.metadata_wrap = FLAC__StreamDecoderMetadataCallback(
clientdatawrapper(self.cb_metadata))
self.error_wrap = FLAC__StreamDecoderErrorCallback(
clientdatawrapper(self.cb_error))

flaclib.FLAC__stream_decoder_set_md5_checking(self.flacdec,
True)


status = flaclib.FLAC__stream_decoder_init_file(self.flacdec,
file, self.write_wrap, self.metadata_wrap,
self.error_wrap, None)
if status != FLAC__STREAM_DECODER_INIT_STATUS_OK:
raise ValueError(
FLAC__StreamDecoderInitStatusString[status])

print 'init'
flaclib.FLAC__stream_decoder_process_until_end_of_metadata(self.flacdec)
print 'end of meta'
if self._lasterror is not None:
raise ValueError(
FLAC__StreamDecoderErrorStatusString[
self._lasterror])
self.curcnt = 0
self.cursamp = 0

def close(self):
if self.flacdec is None:
return

print 'delete called'
if not flaclib.FLAC__stream_decoder_finish(self.flacdec):
md5invalid = True
else:
md5invalid = False

flaclib.FLAC__stream_decoder_delete(self.flacdec)
self.flacdec = None
self.write_wrap = None
self.metadata_wrap = None
self.error_wrap = None
if md5invalid:
pass
#raise ValueError('invalid md5')

def cb_write(self, frame_p, buffer_pp):
frame = frame_p[0]
#print 'write:', `frame`
#for i in xrange(frame.header.channels):
# print '%d:' % i, `frame.subframes[i]`

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.curdata = array.array('h',
[ buffer_pp[x % nchan][x / nchan] for x in
xrange(frame.header.blocksize * nchan)])
if is_little_endian:
self.curdata.byteswap()
else:
print 'ERROR!'

return 0

def cb_metadata(self, metadata_p):
md = metadata_p[0]
print 'metadata:', `md`
if md.type == 0:
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
print `si`

def cb_error(self, errstatus):
self._lasterror = errstatus

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):
return

# the slow way

if self.cursamp > pos:
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)

read = pos - self.cursamp
while read:
tread = min(read, 512*1024)
self.read(tread)
read -= tread

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

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

r.append(self.curdata[:sampcnt].tostring())

self.cursamp += cnt
self.curcnt -= cnt
self.curdata = self.curdata[sampcnt:]
nsamp -= cnt

return ''.join(r)

if __name__ == '__main__':
import sys

d = FLACDec(sys.argv[1])
print 'total samples:', d.totalsamples
print 'channels:', d.channels
print 'rate:', d.samplerate
print 'bps:', d.bitspersample
print `d.read(10)`
print 'going'
d.goto(d.totalsamples - 128)
print 'here'
print `d.read(10)`

+ 1
- 0
pymeds.py View File

@@ -32,6 +32,7 @@ modules = [
'audio',
'Clip',
'pyvr',
'audioraw',
'item',
'slinkemod',
'dvd',


Loading…
Cancel
Save