From: yomguy Date: Thu, 3 Mar 2011 17:36:48 +0000 (+0000) Subject: fix encoding speed and buffer size, enable mp3 and ogg encoder streaming mode, cleanup X-Git-Tag: 0.3.2~73 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=781c6cfd8d859822e8e1a6bbedcc7e6ef809a606;p=timeside.git fix encoding speed and buffer size, enable mp3 and ogg encoder streaming mode, cleanup --- diff --git a/timeside/analyzer/max_level.py b/timeside/analyzer/max_level.py index e60a047..cc1b848 100644 --- a/timeside/analyzer/max_level.py +++ b/timeside/analyzer/max_level.py @@ -50,7 +50,7 @@ class MaxLevel(Processor): return "dB" def process(self, frames, eod=False): - max = numpy.round(20*numpy.log(frames.max()), 3) + max = numpy.round(20*numpy.log(frames.max()), 2) if max > self.value: self.value = max return frames, eod diff --git a/timeside/analyzer/mean_level.py b/timeside/analyzer/mean_level.py index 9d114f0..6d47b86 100644 --- a/timeside/analyzer/mean_level.py +++ b/timeside/analyzer/mean_level.py @@ -52,7 +52,7 @@ class MeanLevel(Processor): return "%s %s" % (str(self.value), unit()) def process(self, frames, eod=False): - value = numpy.round(20*numpy.log10(numpy.mean(numpy.sqrt(numpy.square(frames)))), 3) + value = numpy.round(20*numpy.log10(numpy.mean(numpy.sqrt(numpy.square(frames)))), 2) if value > self.value: self.value = value return frames, eod diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index 15255d2..b5cb9c9 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -55,16 +55,16 @@ class FileDecoder(Processor): def setup(self, channels = None, samplerate = None, nframes = None): # the output data format we want caps = "audio/x-raw-float, width=32" - pipeline = gst.parse_launch('''uridecodebin uri="%s" + self.pipeline = gst.parse_launch('''uridecodebin uri="%s" ! audioconvert ! %s ! appsink name=sink sync=False ''' % (self.uri, caps)) # store a pointer to appsink in our decoder object - self.sink = pipeline.get_by_name('sink') + self.sink = self.pipeline.get_by_name('sink') # adjust length of emitted buffers # self.sink.set_property('blocksize', 0x10000) # start pipeline - pipeline.set_state(gst.STATE_PLAYING) + self.pipeline.set_state(gst.STATE_PLAYING) @interfacedoc def channels(self): @@ -181,7 +181,7 @@ class SubProcessPipe: self.buffer_size = 0xFFFF if not stdin: - stdin = subprocess.PIPE + stdin = subprocess.PIPE self.proc = subprocess.Popen(command.encode('utf-8'), shell = True, diff --git a/timeside/encoder/__init__.py b/timeside/encoder/__init__.py index e53e088..4698489 100644 --- a/timeside/encoder/__init__.py +++ b/timeside/encoder/__init__.py @@ -5,7 +5,5 @@ from ogg import * from wav import * from mp3 import * from m4a import * -from mp3_stream import * -from ogg_stream import * #from timeside.encoder.flac import * diff --git a/timeside/encoder/mp3.py b/timeside/encoder/mp3.py index 9272c1c..9bd27f2 100644 --- a/timeside/encoder/mp3.py +++ b/timeside/encoder/mp3.py @@ -36,24 +36,43 @@ class Mp3Encoder(Processor): """ gstreamer-based mp3 encoder """ implements(IEncoder) - def __init__(self, output): - self.file = None + def __init__(self, output, streaming=False): if isinstance(output, basestring): self.filename = output else: - raise Exception("Streaming not supported") + self.filename = None + self.streaming = streaming + + if not self.filename and self.streaming: + raise Exception('Must give an output') @interfacedoc def setup(self, channels=None, samplerate=None, nframes=None): super(Mp3Encoder, self).setup(channels, samplerate, nframes) #TODO: open file for writing - # the output data format we want - pipeline = gst.parse_launch(''' appsrc name=src - ! audioconvert - ! lame name=enc vbr=0 bitrate=256 ! id3v2mux - ! filesink location=%s ''' % self.filename) + # the output data format we want + pipe = ''' appsrc name=src max-bytes=32768 block=true + ! audioconvert + ! lame name=enc vbr=0 bitrate=256 ! id3v2mux + ''' + if self.filename and self.streaming: + pipe += ''' + ! queue2 name=q0 ! tee name=tee + tee. ! queue name=q1 ! appsink name=app + tee. ! queue name=q2 ! filesink location=%s + ''' % self.filename + + elif self.filename : + pipe += '! filesink location=%s' % self.filename + else: + pipe += '! appsink name=app' + + self.pipeline = gst.parse_launch(pipe) + # store a pointer to appsrc in our encoder object + self.src = self.pipeline.get_by_name('src') # store a pointer to appsink in our encoder object - self.src = pipeline.get_by_name('src') + self.app = self.pipeline.get_by_name('app') + srccaps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, @@ -62,8 +81,7 @@ class Mp3Encoder(Processor): self.src.set_property("caps", srccaps) # start pipeline - pipeline.set_state(gst.STATE_PLAYING) - self.pipeline = pipeline + self.pipeline.set_state(gst.STATE_PLAYING) @staticmethod @interfacedoc @@ -99,6 +117,8 @@ class Mp3Encoder(Processor): def process(self, frames, eod=False): buf = self.numpy_array_to_gst_buffer(frames) self.src.emit('push-buffer', buf) + if self.streaming: + frames = self.app.emit('pull-buffer') if eod: self.src.emit('end-of-stream') return frames, eod diff --git a/timeside/encoder/mp3_stream.py b/timeside/encoder/mp3_stream.py deleted file mode 100644 index 8b3de51..0000000 --- a/timeside/encoder/mp3_stream.py +++ /dev/null @@ -1,117 +0,0 @@ - # -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Parisson SARL -# Copyright (c) 2006-2007 Guillaume Pellerin - -# This file is part of TimeSide. - -# TimeSide is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. - -# TimeSide is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with TimeSide. If not, see . - -# Authors: Guillaume Pellerin -# Paul Brossier - -from timeside.core import Processor, implements, interfacedoc -from timeside.api import IEncoder -from numpy import array, frombuffer, getbuffer, float32 - -import pygst -pygst.require('0.10') -import gst -import gobject -gobject.threads_init() - - -class Mp3EncoderStream(Processor): - """ gstreamer-based streaming mp3 encoder with an appsink tee""" - implements(IEncoder) - - def __init__(self, output=None): - self.filename = output - - @interfacedoc - def setup(self, channels=None, samplerate=None, nframes=None): - super(Mp3EncoderStream, self).setup(channels, samplerate, nframes) - #TODO: open file for writing - # the output data format we want - pipe = '''appsrc name=src ! audioconvert - ! lame name=enc vbr=0 bitrate=256 ! id3v2mux - ''' - if self.filename: - pipe += ''' - ! queue2 name=q0 ! tee name=tee - tee. ! queue name=q1 ! appsink name=app - tee. ! queue name=q2 ! filesink location=%s - ''' % self.filename - else: - pipe += '! appsink name=app' - - pipeline = gst.parse_launch(pipe) - # store a pointer to appsrc in our encoder object - self.src = pipeline.get_by_name('src') - # store a pointer to appsink in our encoder object - self.app = pipeline.get_by_name('app') - - srccaps = gst.Caps("""audio/x-raw-float, - endianness=(int)1234, - channels=(int)%s, - width=(int)32, - rate=(int)%d""" % (int(channels), int(samplerate))) - self.src.set_property("caps", srccaps) - - # start pipeline - pipeline.set_state(gst.STATE_PLAYING) - self.pipeline = pipeline - - @staticmethod - @interfacedoc - def id(): - return "gst_mp3_enc_stream" - - @staticmethod - @interfacedoc - def description(): - return "MP3 GStreamer based encoder and streamer" - - @staticmethod - @interfacedoc - def format(): - return "MP3" - - @staticmethod - @interfacedoc - def file_extension(): - return "mp3" - - @staticmethod - @interfacedoc - def mime_type(): - return "audio/mpeg" - - @interfacedoc - def set_metadata(self, metadata): - #TODO: - pass - - @interfacedoc - def process(self, frames, eod=False): - buf = self.numpy_array_to_gst_buffer(frames) - self.src.emit('push-buffer', buf) - appbuffer = self.app.emit('pull-buffer') - if eod: self.src.emit('end-of-stream') - return appbuffer, eod - - def numpy_array_to_gst_buffer(self, frames): - """ gstreamer buffer to numpy array conversion """ - buf = gst.Buffer(getbuffer(frames)) - return buf diff --git a/timeside/encoder/ogg.py b/timeside/encoder/ogg.py index 39de8dd..25d224d 100644 --- a/timeside/encoder/ogg.py +++ b/timeside/encoder/ogg.py @@ -34,25 +34,44 @@ class VorbisEncoder(Processor): """ gstreamer-based vorbis encoder """ implements(IEncoder) - def __init__(self, output): - self.file = None + def __init__(self, output, streaming=False): if isinstance(output, basestring): self.filename = output else: - raise Exception("Streaming not supported") + self.filename = None + self.streaming = streaming + + if not self.filename and self.streaming: + raise Exception('Must give an output') @interfacedoc def setup(self, channels=None, samplerate=None, nframes=None): super(VorbisEncoder, self).setup(channels, samplerate, nframes) # TODO open file for writing - # the output data format we want - pipeline = gst.parse_launch(''' appsrc name=src - ! audioconvert - ! vorbisenc - ! oggmux - ! filesink location=%s ''' % self.filename) + # the output data format we want + pipe = ''' appsrc name=src max-bytes=32768 block=true + ! audioconvert + ! vorbisenc + ! oggmux + ''' + if self.filename and self.streaming: + pipe += ''' + ! queue2 name=q0 ! tee name=tee + tee. ! queue name=q1 ! appsink name=app + tee. ! queue name=q2 ! filesink location=%s + ''' % self.filename + + elif self.filename : + pipe += '! filesink location=%s' % self.filename + else: + pipe += '! appsink name=app' + + self.pipeline = gst.parse_launch(pipe) + # store a pointer to appsrc in our encoder object + self.src = self.pipeline.get_by_name('src') # store a pointer to appsink in our encoder object - self.src = pipeline.get_by_name('src') + self.app = self.pipeline.get_by_name('app') + srccaps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, @@ -61,8 +80,7 @@ class VorbisEncoder(Processor): self.src.set_property("caps", srccaps) # start pipeline - pipeline.set_state(gst.STATE_PLAYING) - self.pipeline = pipeline + self.pipeline.set_state(gst.STATE_PLAYING) @staticmethod @interfacedoc @@ -91,21 +109,19 @@ class VorbisEncoder(Processor): @interfacedoc def set_metadata(self, metadata): - #TODO + #TODO: pass @interfacedoc def process(self, frames, eod=False): buf = self.numpy_array_to_gst_buffer(frames) self.src.emit('push-buffer', buf) + if self.streaming: + frames = self.app.emit('pull-buffer') if eod: self.src.emit('end-of-stream') return frames, eod - def numpy_array_to_gst_buffer(self, frames): """ gstreamer buffer to numpy array conversion """ buf = gst.Buffer(getbuffer(frames)) return buf - - - diff --git a/timeside/encoder/ogg_stream.py b/timeside/encoder/ogg_stream.py deleted file mode 100644 index 7978f46..0000000 --- a/timeside/encoder/ogg_stream.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2010 Paul Brossier -# Copyright (c) 2010 Guillaume Pellerin - -# This file is part of TimeSide. - -# TimeSide is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. - -# TimeSide is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with TimeSide. If not, see . - - -from timeside.core import Processor, implements, interfacedoc -from timeside.api import IEncoder -from numpy import array, frombuffer, getbuffer, float32 - -import pygst -pygst.require('0.10') -import gst -import gobject -gobject.threads_init() - - -class VorbisEncoderStream(Processor): - """ gstreamer-based vorbis encoder """ - implements(IEncoder) - - def __init__(self, output=None): - self.filename = output - - @interfacedoc - def setup(self, channels=None, samplerate=None, nframes=None): - super(VorbisEncoderStream, self).setup(channels, samplerate, nframes) - # TODO open file for writing - # the output data format we want - pipe = '''appsrc name=src ! audioconvert - ! vorbisenc ! oggmux - ''' - if self.filename: - pipe += ''' - ! queue2 name=q0 ! tee name=tee - tee. ! queue name=q1 ! appsink name=app - tee. ! queue name=q2 ! filesink location=%s - ''' % self.filename - else: - pipe += '! appsink name=app' - - pipeline = gst.parse_launch(pipe) - # store a pointer to appsrc in our encoder object - self.src = pipeline.get_by_name('src') - # store a pointer to appsink in our encoder object - self.app = pipeline.get_by_name('app') - - srccaps = gst.Caps("""audio/x-raw-float, - endianness=(int)1234, - channels=(int)%s, - width=(int)32, - rate=(int)%d""" % (int(channels), int(samplerate))) - self.src.set_property("caps", srccaps) - - # start pipeline - pipeline.set_state(gst.STATE_PLAYING) - self.pipeline = pipeline - - @staticmethod - @interfacedoc - def id(): - return "gst_vorbis_enc_stream" - - @staticmethod - @interfacedoc - def description(): - return "Vorbis GStreamer based encoder and streamer" - - @staticmethod - @interfacedoc - def format(): - return "OGG" - - @staticmethod - @interfacedoc - def file_extension(): - return "ogg" - - @staticmethod - @interfacedoc - def mime_type(): - return "application/ogg" - - @interfacedoc - def set_metadata(self, metadata): - #TODO: - pass - - @interfacedoc - def process(self, frames, eod=False): - buf = self.numpy_array_to_gst_buffer(frames) - self.src.emit('push-buffer', buf) - appbuffer = self.app.emit('pull-buffer') - if eod: self.src.emit('end-of-stream') - return appbuffer, eod - - def numpy_array_to_gst_buffer(self, frames): - """ gstreamer buffer to numpy array conversion """ - buf = gst.Buffer(getbuffer(frames)) - return buf diff --git a/timeside/encoder/wav.py b/timeside/encoder/wav.py index 234763a..b49e5b0 100644 --- a/timeside/encoder/wav.py +++ b/timeside/encoder/wav.py @@ -47,12 +47,12 @@ class WavEncoder(Processor): super(WavEncoder, self).setup(channels, samplerate, nframes) # TODO open file for writing # the output data format we want - pipeline = gst.parse_launch(''' appsrc name=src + self.pipeline = gst.parse_launch(''' appsrc name=src ! audioconvert ! wavenc ! filesink location=%s ''' % self.filename) # store a pointer to appsink in our encoder object - self.src = pipeline.get_by_name('src') + self.src = self.pipeline.get_by_name('src') srccaps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, @@ -61,8 +61,7 @@ class WavEncoder(Processor): self.src.set_property("caps", srccaps) # start pipeline - pipeline.set_state(gst.STATE_PLAYING) - self.pipeline = pipeline + self.pipeline.set_state(gst.STATE_PLAYING) @staticmethod @interfacedoc diff --git a/timeside/tests/api/test_mp3.py b/timeside/tests/api/test_mp3.py index eaf093b..c333067 100644 --- a/timeside/tests/api/test_mp3.py +++ b/timeside/tests/api/test_mp3.py @@ -2,9 +2,10 @@ from timeside.decoder import * from timeside.encoder import * +import os.path -source = "../samples/sweep.wav" -dest = "../results/sweep_wav.mp3" +source = os.path.join(os.path.dirname(__file__), "../samples/sweep.wav") +dest = os.path.join(os.path.dirname(__file__), "../results/sweep_wav.mp3") decoder = FileDecoder(source) encoder = Mp3Encoder(dest) diff --git a/timeside/tests/api/test_vorbis.py b/timeside/tests/api/test_vorbis.py index ba0655a..53818cd 100644 --- a/timeside/tests/api/test_vorbis.py +++ b/timeside/tests/api/test_vorbis.py @@ -2,9 +2,10 @@ from timeside.decoder import * from timeside.encoder import * +import os.path -source = "../samples/sweep.wav" -dest = "../results/sweep_wav.ogg" +source = os.path.join(os.path.dirname(__file__), "../samples/sweep.wav") +dest = os.path.join(os.path.dirname(__file__), "../results/sweep_wav.ogg") decoder = FileDecoder(source) encoder = VorbisEncoder(dest)