示例#1
0
文件: live.py 项目: ma4ank/TimeSide
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
示例#2
0
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)
示例#3
0
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())