From: Thomas Fillon Date: Fri, 20 Dec 2013 10:42:09 +0000 (+0100) Subject: Decoder FileDecoder : Fixes #8 buy providing a 'stack' argument at the FileDecoder... X-Git-Tag: 0.5.3~35 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=f61fd8fcf8034aea9fa83efe71407d30c537efa3;p=timeside.git Decoder FileDecoder : Fixes #8 buy providing a 'stack' argument at the FileDecoder initialization. It removes the previous changes doing this at the pipe level and using an ArrrayDecoder --- diff --git a/doc/source/tutorial/frames_stack.rst b/doc/source/tutorial/frames_stack.rst index 71644d2..e7207e9 100644 --- a/doc/source/tutorial/frames_stack.rst +++ b/doc/source/tutorial/frames_stack.rst @@ -5,12 +5,17 @@ Running a pipe with previously decoded frames =============================================== -Example of use of the `stack` option in :func:`timeside.core.ProcessPipe.run` to run a pipe with previously decoded frames stacked in memory on a second pass. +Example of use of the `stack` argument in :class:`timeside.decoder.core.FileDecoder` to run a pipe with previously decoded frames stacked in memory on a second pass. + +First, let's import everything and define the audio file source : >>> import timeside >>> import numpy as np >>> audio_file = 'http://github.com/yomguy/timeside-samples/raw/master/samples/sweep.mp3' ->>> decoder = timeside.decoder.FileDecoder(audio_file) + +Then let's setup a :class:`FileDecoder ` with argument `stack=True` (default argument is `stack=False`) : + +>>> decoder = timeside.decoder.FileDecoder(audio_file, stack=True) Setup an arbitrary analyzer to check that decoding process from file and from stack are equivalent: @@ -19,30 +24,29 @@ Setup an arbitrary analyzer to check that decoding process from file and from st >>> print pipe.processors #doctest: +ELLIPSIS [, ] -If the pipe is run with the default argument `stack=False`, the other processes of the pipe are released from the pipe after the run and only the :class:`fileDecoder ` is kept in the pipe: +After the pipe has been run, the other processes of the pipe are removed from the pipe and only the :class:`FileDecoder ` is kept : >>> pipe.run() >>> print pipe.processors #doctest: +ELLIPSIS [] +The processed frames are stored in the pipe attribute `frames_stack` as a list of frames : -If the pipe is run with the argument `stack=True`, the processed frames are stored in the pipe attribute `frames_stack`. -The other processes of the pipe are also released from the pipe after the run but the :class:`fileDecoder ` is replaced by an :class:`ArrayDecoder `: - ->>> pipe = (decoder | pitch_on_file) ->>> pipe.run(stack=True) ->>> print pipe.processors #doctest: +ELLIPSIS -[] +>>> print type(pipe.frames_stack) + -The stack +First frame : ->>> pipe.frames_stack #doctest: +ELLIPSIS -array([[...]], dtype=float32) +>>> print pipe.frames_stack[0] #doctest: +ELLIPSIS +(array([[...]], dtype=float32), False) +Last frame : -Then we can run a second pipe with the previously decoded frames and pass the frames to the same analyzer. +>>> print pipe.frames_stack[-1] #doctest: +ELLIPSIS +(array([[...]], dtype=float32), True) -Define a second analyzer equivalent to the previous one: +If the pipe is used for a second run, the processed frames stored in the stack are passed to the other processors without decoding the audio source again. +Let's define a second analyzer equivalent to the previous one: >>> pitch_on_stack = timeside.analyzer.AubioPitch() @@ -50,10 +54,9 @@ Add it to the pipe: >>> pipe |= pitch_on_stack >>> print pipe.processors #doctest: +ELLIPSIS -[, ] - +[, ] -Run the pipe: +And run the pipe: >>> pipe.run() diff --git a/timeside/core.py b/timeside/core.py index 4f5a54b..e817e25 100644 --- a/timeside/core.py +++ b/timeside/core.py @@ -276,23 +276,15 @@ class ProcessPipe(object): pipe += ' | ' return pipe - def run(self, channels=None, samplerate=None, blocksize=None, stack=None): + def run(self, channels=None, samplerate=None, blocksize=None): """Setup/reset all processors in cascade and stream audio data along - the pipe. Also returns the pipe itself.""" + the pipe.""" source = self.processors[0] items = self.processors[1:] source.setup(channels=channels, samplerate=samplerate, blocksize=blocksize) - if stack is None: - self.stack = False - else: - self.stack = stack - - if self.stack: - self.frames_stack = [] - last = source # setup/reset processors and configure properties throughout the pipe @@ -308,8 +300,6 @@ class ProcessPipe(object): eod = False while not eod: frames, eod = source.process() - if self.stack: - self.frames_stack.append(frames) for item in items: frames, eod = item.process(frames, eod) @@ -318,17 +308,6 @@ class ProcessPipe(object): item.post_process() # Release processors - if self.stack: - if not isinstance(self.frames_stack, numpy.ndarray): - self.frames_stack = numpy.vstack(self.frames_stack) - from timeside.decoder.core import ArrayDecoder - new_source = ArrayDecoder(samples=self.frames_stack, - samplerate=source.samplerate()) - new_source.setup(channels=source.channels(), - samplerate=source.samplerate(), - blocksize=source.blocksize()) - self.processors[0] = new_source - for item in items: item.release() self.processors.remove(item) diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index dbee2a0..5a24530 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -43,6 +43,20 @@ GST_APPSINK_MAX_BUFFERS = 10 QUEUE_SIZE = 10 +def stack(process_func): + + import functools + + @functools.wraps(process_func) + def wrapper(decoder): + # Processing + frames, eod = process_func(decoder) + if decoder.stack: + decoder.process_pipe.frames_stack.append((frames, eod)) + return frames, eod + return wrapper + + class FileDecoder(Processor): """ gstreamer-based decoder """ implements(IDecoder) @@ -62,7 +76,7 @@ class FileDecoder(Processor): def id(): return "gst_dec" - def __init__(self, uri, start=0, duration=None): + def __init__(self, uri, start=0, duration=None, stack=False): """ Construct a new FileDecoder @@ -75,10 +89,14 @@ class FileDecoder(Processor): start time of the segment in seconds duration : float duration of the segment in seconds + """ super(FileDecoder, self).__init__() + self.from_stack = False + self.stack = stack + self.uri = get_uri(uri) self.uri_start = float(start) @@ -87,11 +105,12 @@ class FileDecoder(Processor): else: self.uri_duration = duration - if start==0 and duration is None: + if start == 0 and duration is None: self.is_segment = False else: self.is_segment = True + def set_uri_default_duration(self): # Set the duration from the length of the file uri_total_duration = get_media_uri_info(self.uri)['duration'] @@ -99,6 +118,15 @@ class FileDecoder(Processor): def setup(self, channels=None, samplerate=None, blocksize=None): + self.eod = False + self.last_buffer = None + + if self.from_stack: + return + + if self.stack: + self.process_pipe.frames_stack = [] + if self.uri_duration is None: self.set_uri_default_duration() @@ -184,10 +212,6 @@ class FileDecoder(Processor): #self.mainloopthread = get_loop_thread() ##self.mainloop = self.mainloopthread.mainloop - self.eod = False - - self.last_buffer = None - # start pipeline self.pipeline.set_state(gst.STATE_PLAYING) @@ -282,7 +306,8 @@ class FileDecoder(Processor): self.queue.put([new_block, False]) @interfacedoc - def process(self, frames=None, eod=False): + @stack + def process(self): buf = self.queue.get() if buf == gst.MESSAGE_EOS: return self.last_buffer, True @@ -311,6 +336,9 @@ class FileDecoder(Processor): @interfacedoc def release(self): + if self.stack: + self.stack = False + self.from_stack = True pass @interfacedoc @@ -454,8 +482,7 @@ class ArrayDecoder(Processor): yield (self.samples[nb_frames * self.output_blocksize:], True) @interfacedoc - def process(self, frames=None, eod=False): - + def process(self): return self.frames.next() @interfacedoc