class LiveDecoder(FileDecoder): """Live source Decoder based on Gstreamer capturing audio from alsasrc Construct a new LiveDecoder capturing audio from alsasrc Parameters ---------- num_buffers : int, optional Number of buffers to output before sending End Of Stream signal (-1 = unlimited). (Allowed values: >= -1, Default value: -1) input_src : str, optional Gstreamer source element default to 'alsasrc' possible values : 'autoaudiosrc', 'alsasrc', 'osssrc' Examples -------- >>> import timeside >>> from timeside.core import get_processor >>> live_decoder = get_processor('live_decoder')(num_buffers=5) >>> waveform = get_processor('waveform_analyzer')() >>> mp3_encoder = timeside.plugins.encoder.mp3.Mp3Encoder('/tmp/test_live.mp3', ... overwrite=True) >>> pipe = (live_decoder | waveform | mp3_encoder) >>> pipe.run() # doctest: +SKIP >>> # Show the audio as captured by the decoder >>> import matplotlib.pyplot as plt # doctest: +SKIP >>> plt.plot(a.results['waveform_analyzer'].time, # doctest: +SKIP a.results['waveform_analyzer'].data) # doctest: +SKIP >>> plt.show() # doctest: +SKIP """ implements(IDecoder) # IProcessor methods @staticmethod @interfacedoc def id(): return "live_decoder" def __init__(self, num_buffers=-1, input_src='alsasrc'): super(Decoder, self).__init__() self.num_buffers = num_buffers self.uri = None self.uri_start = 0 self.uri_duration = None self.is_segment = False self.input_src = input_src self._sha1 = '' def setup(self, channels=None, samplerate=None, blocksize=None): self.eod = False self.last_buffer = None # a lock to wait wait for gstreamer thread to be ready self.discovered_cond = threading.Condition(threading.Lock()) self.discovered = False # the output data format we want if blocksize: self.output_blocksize = blocksize if samplerate: self.output_samplerate = int(samplerate) if channels: self.output_channels = int(channels) # Create the pipe with standard Gstreamer uridecodbin self.pipe = '''%s num-buffers=%d name=src ! audioconvert name=audioconvert ! audioresample ! appsink name=sink sync=False async=True ''' % (self.input_src, self.num_buffers) self.pipeline = gst.parse_launch(self.pipe) if self.output_channels: caps_channels = int(self.output_channels) else: caps_channels = "[ 1, 2 ]" if self.output_samplerate: caps_samplerate = int(self.output_samplerate) else: caps_samplerate = "{ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000 }" sink_caps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, width=(int)32, rate=(int)%s""" % (caps_channels, caps_samplerate)) self.src = self.pipeline.get_by_name('src') self.conv = self.pipeline.get_by_name('audioconvert') self.conv.get_pad("sink").connect("notify::caps", self._notify_caps_cb) self.sink = self.pipeline.get_by_name('sink') self.sink.set_property("caps", sink_caps) self.sink.set_property('max-buffers', GST_APPSINK_MAX_BUFFERS) self.sink.set_property("drop", False) self.sink.set_property('emit-signals', True) self.sink.connect("new-buffer", self._on_new_buffer_cb) self.bus = self.pipeline.get_bus() self.bus.add_signal_watch() self.bus.connect('message', self._on_message_cb) self.queue = Queue.Queue(QUEUE_SIZE) self.mainloop = gobject.MainLoop() self.mainloopthread = MainloopThread(self.mainloop) self.mainloopthread.start() #self.mainloopthread = get_loop_thread() ##self.mainloop = self.mainloopthread.mainloop # start pipeline self.pipeline.set_state(gst.STATE_PLAYING) self.discovered_cond.acquire() while not self.discovered: # print 'waiting' self.discovered_cond.wait() self.discovered_cond.release() if not hasattr(self, 'input_samplerate'): if hasattr(self, 'error_msg'): raise IOError(self.error_msg) else: raise IOError('no known audio stream found') @interfacedoc def process(self): buf = self.queue.get() if buf == gst.MESSAGE_EOS: return self.last_buffer, True frames, eod = buf return frames, eod def release(self): # TODO : check if stack support is needed here #if self.stack: # self.stack = False # self.from_stack = True pass
class AubioDecoder(Decoder): """ File decoder based on aubio """ implements(IDecoder) output_blocksize = 8 * 1024 def __init__(self, uri, start=0, duration=None, sha1=None): super().__init__(start=start, duration=duration) self.uri = uri # create the source with default settings try: self.source = aubio.source(self.uri, hop_size=self.output_blocksize) except RuntimeError as e: raise IOError(e) self.input_samplerate = self.source.samplerate self.input_channels = self.source.channels # get the original file duration self.input_totalframes = self.source.duration self.input_duration = self.input_totalframes / self.input_samplerate self.uri_duration = self.input_duration self.start = start self.duration = duration # FIXME self.mimetype = mimetypes.guess_type(uri)[0] self.input_width = 8 if sha1 is not None: self._sha1 = sha1 else: self._sha1 = get_sha1(uri) def setup(self, channels=None, samplerate=None, blocksize=None): if self.start or self.duration: if self.start > self.uri_duration: raise ValueError ('Segment start time exceeds media duration') if self.duration is None: self.duration = self.uri_duration - self.start if self.start + self.duration > self.uri_duration: raise ValueError ('Segment duration exceeds media duration') kwargs = {} if channels is not None: kwargs.update ({'channels': channels}) if samplerate is not None: kwargs.update ({'samplerate': samplerate}) if blocksize is not None and blocksize != self.source.hop_size: kwargs.update ({'hop_size': blocksize}) if len(kwargs): self.source = aubio.source(self.uri, **kwargs) self.output_blocksize = self.source.hop_size self.output_channels = self.source.channels self.output_samplerate = self.source.samplerate self.frames_read = 0 self.start_frame = int(self.start * self.output_samplerate) if self.duration: seconds_to_read = self.duration self.frames_to_read = int (seconds_to_read * self.output_samplerate) if self.duration: self.input_duration = self.duration self.input_totalframes = self.frames_to_read if self.start > 0: self.source.seek(self.start_frame) @staticmethod @interfacedoc def id(): return "aubio_decoder" @staticmethod @interfacedoc def version(): return "1.0" @interfacedoc def process(self): frames, read = self.source.do_multi() self.eod = (read < self.output_blocksize) if self.duration and self.frames_read + read >= self.frames_to_read: extra_read = self.frames_read + read - self.frames_to_read read = self.source.hop_size - extra_read self.eod = True self.frames_read += read frames = frames[:, :read].T return frames.copy(), self.eod @interfacedoc def mime_type(self): return self.mimetype @interfacedoc def resolution(self): return 0 @interfacedoc def metadata(self): return {} @interfacedoc def totalframes(self): if self.input_samplerate == self.output_samplerate: return self.input_totalframes else: ratio = self.output_samplerate / self.input_samplerate return int(self.input_totalframes * ratio)
class FileDecoder(Decoder): """ File Decoder based on Gstreamer Parameters ---------- uri : str uri of the media start : float, optional start time of the segment in seconds duration : float, optional duration of the segment in seconds stack : boolean, optional keep decoded data in the stack sha1 : boolean, optional compute the sha1 hash of the data Examples -------- >>> import timeside >>> from timeside.core import get_processor >>> from timeside.core.tools.test_samples import samples >>> audio_source = samples['sweep.wav'] >>> FileDecoder = get_processor('file_decoder') # Get the decoder class >>> # Use decoder with default parameters >>> decoder = FileDecoder(uri=audio_source) >>> analyzer = get_processor('level')() # Pick a arbitrary analyzer >>> pipe =(decoder | analyzer) >>> pipe.run() # Run the pipe for the given audio source """ implements(IDecoder) output_blocksize = 8 * 1024 pipeline = None mainloopthread = None # IProcessor methods @staticmethod @interfacedoc def id(): return "file_decoder" def __init__(self, uri, start=0, duration=None, stack=False, sha1=None): super(FileDecoder, self).__init__(start=start, duration=duration) self.from_stack = False self.stack = stack self.uri = get_uri(uri).encode('utf8') if not sha1: self._sha1 = get_sha1(uri) else: self._sha1 = sha1.encode('utf8') self.uri_total_duration = get_media_uri_info(self.uri)['duration'] self.mimetype = None def setup(self, channels=None, samplerate=None, blocksize=None): self.eod = False self.last_buffer = None if self.from_stack: self._frames_iterator = iter(self.process_pipe.frames_stack) return if self.stack: self.process_pipe.frames_stack = [] if self.uri_duration is None: # Set the duration from the length of the file self.uri_duration = self.uri_total_duration - self.uri_start if self.is_segment: # Check start and duration value if self.uri_start > self.uri_total_duration: raise ValueError( ('Segment start time value exceed media ' + 'duration')) if self.uri_start + self.uri_duration > self.uri_total_duration: raise ValueError("""Segment duration value is too large \ given the media duration""") # a lock to wait wait for gstreamer thread to be ready self.discovered_cond = threading.Condition(threading.Lock()) self.discovered = False # the output data format we want if blocksize: self.output_blocksize = blocksize if samplerate: self.output_samplerate = int(samplerate) if channels: self.output_channels = int(channels) if self.is_segment: # Create the pipe with Gnonlin gnlurisource self.pipe = ''' gnlurisource name=src uri={uri} start=0 duration={uri_duration} media-start={uri_start} media-duration={uri_duration} ! audioconvert name=audioconvert ! audioresample ! appsink name=sink sync=False async=True '''.format( uri=self.uri, uri_start=np.uint64(round(self.uri_start * gst.SECOND)), uri_duration=np.int64(round(self.uri_duration * gst.SECOND))) # convert uri_start and uri_duration to # nanoseconds else: # Create the pipe with standard Gstreamer uridecodebin self.pipe = ''' uridecodebin name=src uri={uri} ! audioconvert name=audioconvert ! audioresample ! appsink name=sink sync=False async=True '''.format(uri=self.uri) self.pipeline = gst.parse_launch(self.pipe) if self.output_channels: caps_channels = int(self.output_channels) else: caps_channels = "[ 1, 2 ]" if self.output_samplerate: caps_samplerate = int(self.output_samplerate) else: caps_samplerate = "{ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000 }" sink_caps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, width=(int)32, rate=(int)%s""" % (caps_channels, caps_samplerate)) self.src = self.pipeline.get_by_name('src') if not self.is_segment: self.src.connect("autoplug-continue", self._autoplug_cb) else: uridecodebin = self.src.get_by_name('internal-uridecodebin') uridecodebin.connect("autoplug-continue", self._autoplug_cb) self.conv = self.pipeline.get_by_name('audioconvert') self.conv.get_pad("sink").connect("notify::caps", self._notify_caps_cb) self.sink = self.pipeline.get_by_name('sink') self.sink.set_property("caps", sink_caps) self.sink.set_property('max-buffers', GST_APPSINK_MAX_BUFFERS) self.sink.set_property("drop", False) self.sink.set_property('emit-signals', True) self.sink.connect("new-buffer", self._on_new_buffer_cb) self.bus = self.pipeline.get_bus() self.bus.add_signal_watch() self.bus.connect('message', self._on_message_cb) self.queue = Queue.Queue(QUEUE_SIZE) self.mainloop = gobject.MainLoop() self.mainloopthread = MainloopThread(self.mainloop) self.mainloopthread.start() #self.mainloopthread = get_loop_thread() ##self.mainloop = self.mainloopthread.mainloop # start pipeline self.pipeline.set_state(gst.STATE_PLAYING) self.discovered_cond.acquire() while not self.discovered: # print 'waiting' self.discovered_cond.wait() self.discovered_cond.release() if not hasattr(self, 'input_samplerate'): if hasattr(self, 'error_msg'): raise IOError(self.error_msg) else: raise IOError('no known audio stream found') def _autoplug_cb(self, src, arg0, arg1): # use the autoplug-continue callback from uridecodebin # to get the mimetype string if not self.mimetype: self.mimetype = arg1.to_string().split(',')[0] return True def _notify_caps_cb(self, pad, args): self.discovered_cond.acquire() caps = pad.get_negotiated_caps() if not caps: pad.info("no negotiated caps available") self.discovered = True self.discovered_cond.notify() self.discovered_cond.release() return # the caps are fixed # We now get the total length of that stream q = gst.query_new_duration(gst.FORMAT_TIME) pad.info("sending duration query") if pad.get_peer().query(q): format, length = q.parse_duration() if format == gst.FORMAT_TIME: pad.info("got duration (time) : %s" % (gst.TIME_ARGS(length), )) else: pad.info("got duration : %d [format:%d]" % (length, format)) else: length = -1 gst.warning("duration query failed") # We store the caps and length in the proper location if "audio" in caps.to_string(): self.input_samplerate = caps[0]["rate"] if not self.output_samplerate: self.output_samplerate = self.input_samplerate self.input_channels = caps[0]["channels"] if not self.output_channels: self.output_channels = self.input_channels self.input_duration = length / gst.SECOND self.input_totalframes = int(self.input_duration * self.input_samplerate) if "x-raw-float" in caps.to_string(): self.input_width = caps[0]["width"] else: self.input_width = caps[0]["depth"] self.discovered = True self.discovered_cond.notify() self.discovered_cond.release() def _on_message_cb(self, bus, message): t = message.type if t == gst.MESSAGE_EOS: self.queue.put(gst.MESSAGE_EOS) self.pipeline.set_state(gst.STATE_NULL) self.mainloop.quit() elif t == gst.MESSAGE_ERROR: self.pipeline.set_state(gst.STATE_NULL) err, debug = message.parse_error() self.discovered_cond.acquire() self.discovered = True self.mainloop.quit() self.error_msg = "Error: %s" % err, debug self.discovered_cond.notify() self.discovered_cond.release() elif t == gst.MESSAGE_TAG: # TODO # msg.parse_tags() pass def _on_new_buffer_cb(self, sink): buf = sink.emit('pull-buffer') new_array = gst_buffer_to_numpy_array(buf, self.output_channels) # print 'processing new buffer', new_array.shape if self.last_buffer is None: self.last_buffer = new_array else: self.last_buffer = np.concatenate((self.last_buffer, new_array), axis=0) while self.last_buffer.shape[0] >= self.output_blocksize: new_block = self.last_buffer[:self.output_blocksize] self.last_buffer = self.last_buffer[self.output_blocksize:] # print 'queueing', new_block.shape, 'remaining', # self.last_buffer.shape self.queue.put([new_block, False]) @interfacedoc @stack def process(self): buf = self.queue.get() if buf == gst.MESSAGE_EOS: return self.last_buffer, True frames, eod = buf return frames, eod @interfacedoc def totalframes(self): if self.input_samplerate == self.output_samplerate: return self.input_totalframes else: ratio = self.output_samplerate / self.input_samplerate return int(self.input_totalframes * ratio) @interfacedoc def release(self): if self.stack: self.stack = False self.from_stack = True # IDecoder methods @interfacedoc def format(self): return self.mime_type() @interfacedoc def mime_type(self): if self.mimetype == 'application/x-id3': self.mimetype = 'audio/mpeg' return self.mimetype @interfacedoc def encoding(self): # TODO check return self.mime_type().split('/')[-1] @interfacedoc def resolution(self): # TODO check: width or depth? return self.input_width @interfacedoc def metadata(self): # TODO check return self.tags def stop(self): self.src.send_event(gst.event_new_eos())