Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/pysamples/grecord.py
diff options
context:
space:
mode:
Diffstat (limited to 'pysamples/grecord.py')
-rw-r--r--pysamples/grecord.py227
1 files changed, 227 insertions, 0 deletions
diff --git a/pysamples/grecord.py b/pysamples/grecord.py
new file mode 100644
index 0000000..84b1a5c
--- /dev/null
+++ b/pysamples/grecord.py
@@ -0,0 +1,227 @@
+#Copyright (c) 2008, Media Modifications Ltd.
+#Copyright (c) 2011, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
+
+# Usage: Import this code into a Python (user-definable) block; Pass
+# it 'start' to start recording; 'stop' to stop recording; 'play' to
+# play back your recording; or 'save' to save your recording to the
+# Sugar Journal.
+
+
+def myblock(tw, arg):
+ ''' Record and playback a sound (Sugar only) '''
+ import os
+ import time
+
+ import gtk
+ import gst
+
+ import gobject
+ gobject.threads_init()
+
+ from TurtleArt.tautils import get_path
+ from TurtleArt.tagplay import play_audio_from_file
+ from sugar.datastore import datastore
+ from sugar import profile
+
+ from gettext import gettext as _
+
+ class Grecord:
+ ''' A class for creating a gstreamer session for recording audio. '''
+
+ def __init__(self, tw):
+ ''' Set up the stream. We save to a raw .wav file and then
+ convert the sound to .ogg for saving. '''
+ datapath = get_path(tw.parent, 'instance')
+ self.capture_file = os.path.join(datapath, 'output.wav')
+ self.save_file = os.path.join(datapath, 'output.ogg')
+ self._eos_cb = None
+
+ self._can_limit_framerate = False
+ self._recording = False
+
+ self._audio_transcode_handler = None
+ self._transcode_id = None
+
+ self._pipeline = gst.Pipeline("Record")
+ self._create_audiobin()
+ self._pipeline.add(self._audiobin)
+
+ bus = self._pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', self._bus_message_handler)
+
+ def _create_audiobin(self):
+ ''' Assemble all the pieces we need. '''
+ src = gst.element_factory_make("alsasrc", "absrc")
+
+ # attempt to use direct access to the 0,0 device, solving some A/V
+ # sync issues
+ src.set_property("device", "plughw:0,0")
+ hwdev_available = src.set_state(gst.STATE_PAUSED) != \
+ gst.STATE_CHANGE_FAILURE
+ src.set_state(gst.STATE_NULL)
+ if not hwdev_available:
+ src.set_property("device", "default")
+
+ srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16")
+
+ # guarantee perfect stream, important for A/V sync
+ rate = gst.element_factory_make("audiorate")
+
+ # without a buffer here, gstreamer struggles at the start of the
+ # recording and then the A/V sync is bad for the whole video
+ # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it
+ # should be able to resync without problem)
+ queue = gst.element_factory_make("queue", "audioqueue")
+ queue.set_property("leaky", True) # prefer fresh data
+ queue.set_property("max-size-time", 5000000000) # 5 seconds
+ queue.set_property("max-size-buffers", 500)
+ queue.connect("overrun", self._log_queue_overrun)
+
+ enc = gst.element_factory_make("wavenc", "abenc")
+
+ sink = gst.element_factory_make("filesink", "absink")
+ sink.set_property("location", self.capture_file)
+
+ self._audiobin = gst.Bin("audiobin")
+ self._audiobin.add(src, rate, queue, enc, sink)
+
+ src.link(rate, srccaps)
+ gst.element_link_many(rate, queue, enc, sink)
+
+ def _log_queue_overrun(self, queue):
+ ''' We use a buffer, which may overflow. '''
+ cbuffers = queue.get_property("current-level-buffers")
+ cbytes = queue.get_property("current-level-bytes")
+ ctime = queue.get_property("current-level-time")
+
+ def is_recording(self):
+ ''' Are we recording? '''
+ return self._recording
+
+ def _get_state(self):
+ ''' What is the state of our gstreamer pipeline? '''
+ return self._pipeline.get_state()[1]
+
+ def start_recording_audio(self):
+ ''' Start the stream in order to start recording. '''
+ if self._get_state() == gst.STATE_PLAYING:
+ return
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._recording = True
+
+ def stop_recording_audio(self):
+ ''' Stop recording and then convert the results into a
+ .ogg file using a new stream. '''
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._recording = False
+
+ if not os.path.exists(self.capture_file) or \
+ os.path.getsize(self.capture_file) <= 0:
+ return
+
+ # Remove previous transcoding results.
+ if os.path.exists(self.save_file):
+ os.remove(self.save_file)
+
+ line = 'filesrc location=' + self.capture_file + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
+ audioline = gst.parse_launch(line)
+
+ vorbis_enc = audioline.get_by_name('audioVorbisenc')
+
+ audioFilesink = audioline.get_by_name('audioFilesink')
+ audioFilesink.set_property("location", self.save_file)
+
+ audioBus = audioline.get_bus()
+ audioBus.add_signal_watch()
+ self._audio_transcode_handler = audioBus.connect(
+ 'message', self._onMuxedAudioMessageCb, audioline)
+ self._transcode_id = gobject.timeout_add(
+ 200, self._transcodeUpdateCb, audioline)
+ audioline.set_state(gst.STATE_PLAYING)
+
+ def _transcodeUpdateCb(self, pipe):
+ ''' Where are we in the transcoding process? '''
+ position, duration = self._query_position(pipe)
+ if position != gst.CLOCK_TIME_NONE:
+ value = position * 100.0 / duration
+ value = value/100.0
+ return True
+
+ def _query_position(self, pipe):
+ ''' Where are we in the stream? '''
+ try:
+ position, format = pipe.query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = pipe.query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+
+ return (position, duration)
+
+ def _onMuxedAudioMessageCb(self, bus, message, pipe):
+ ''' Clean up at end of stream.'''
+ if message.type != gst.MESSAGE_EOS:
+ return True
+
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ os.remove(self.capture_file)
+ return False
+
+ def _bus_message_handler(self, bus, message):
+ ''' Handle any messages associated with the stream. '''
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ if self._eos_cb:
+ cb = self._eos_cb
+ self._eos_cb = None
+ cb()
+ elif t == gst.MESSAGE_ERROR:
+ # TODO: if we come out of suspend/resume with errors, then
+ # get us back up and running... TODO: handle "No space
+ # left on the resource.gstfilesink.c" err, debug =
+ # message.parse_error()
+ pass
+
+ # We store the audio-record stream instance as tw.grecord so that
+ # we can use it repeatedly.
+ if not hasattr(tw, 'grecord'):
+ tw.grecord = Grecord(tw)
+
+ # Sometime we need to parse multiple arguments, e.g., save, savename
+ save_name = _('Turtle Art') + ' ' + _('sound')
+ if type(arg) == type([]):
+ cmd = arg[0].lower()
+ if len(arg) > 1:
+ save_name = str(arg[1])
+ else:
+ cmd = arg.lower()
+
+ if cmd == 'start' or cmd == _('start').lower():
+ tw.grecord.start_recording_audio()
+ elif cmd == 'stop' or cmd == _('stop').lower():
+ tw.grecord.stop_recording_audio()
+ elif cmd == 'play' or cmd == _('play').lower():
+ play_audio_from_file(tw.lc, tw.grecord.save_file)
+ elif cmd == 'save' or cmd == _('save').lower():
+ if os.path.exists(tw.grecord.save_file) and tw.running_sugar:
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = save_name
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'audio/ogg'
+ dsobject.set_file_path(tw.grecord.save_file)
+ datastore.write(dsobject)
+ dsobject.destroy()