diff options
author | Daniel Drake <dsd@laptop.org> | 2010-04-21 17:30:36 (GMT) |
---|---|---|
committer | Daniel Drake <dan@reactivated.net> | 2010-04-29 14:49:22 (GMT) |
commit | f6de5db915f077f4d90e68d5ca65b6c986080784 (patch) | |
tree | 0cf8946860bc12446eb6a862322c412ae2043f3c /glive.py | |
parent | 5abd5b671874f9db596aac97a5e47ec31a9a5bd8 (diff) |
Restore v60 rework
Restore the v60 rewrite to use a single pipeline, tweaking at runtime
when necessary.
This improves responsivity of the application, and results in the
activity behaving reliably again.
Diffstat (limited to 'glive.py')
-rw-r--r-- | glive.py | 828 |
1 files changed, 470 insertions, 358 deletions
@@ -18,47 +18,36 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -import re + import os import gtk import pygtk pygtk.require('2.0') +import sys import gst +import gst.interfaces import pygst pygst.require('0.10') import time +import threading import gobject gobject.threads_init() -import logging -logger = logging.getLogger('record:glive.py') - -from sugar.activity.activity import get_activity_root, get_bundle_path +from sugar.activity.activity import get_bundle_path from instance import Instance from constants import Constants +import record import utils import ui -TMP_OGV = os.path.join(get_activity_root(), 'instance', 'output.ogv') -TMP_OGG = os.path.join(get_activity_root(), 'instance', 'output.ogg') -TMP_WAV = os.path.join(get_activity_root(), 'instance', 'output.wav') - -PLAYBACK_WIDTH = 640 -PLAYBACK_HEIGHT = 480 - OGG_TRAITS = { 0: { 'width': 160, 'height': 120, 'quality': 16 }, 1: { 'width': 400, 'height': 300, 'quality': 16 }, 2: { 'width': 640, 'height': 480, 'quality': 32 } } THUMB_STUB = gtk.gdk.pixbuf_new_from_file( - os.path.join(get_bundle_path(), 'gfx', 'stub.png')) - -if os.path.isdir('/ofw'): - AUDIO_FILTER = '! volume volume=10' -else: - AUDIO_FILTER = '' + os.path.join(get_bundle_path(), 'gfx', 'stub.png')) def _does_camera_present(): v4l2src = gst.element_factory_make('v4l2src') @@ -67,335 +56,497 @@ def _does_camera_present(): camera_presents = _does_camera_present() class Glive: - def play(self): - if not camera_presents: - return + def __init__(self, pca): + self.window = None + self.ca = pca + self._eos_cb = None - logger.debug('play') - - if not self.play_pipe: - self.src_str = \ - 'v4l2src ' \ - '! video/x-raw-yuv,width=%s,height=%s ' \ - % (PLAYBACK_WIDTH, PLAYBACK_HEIGHT) - self.play_str = \ - 'xvimagesink force-aspect-ratio=true name=xsink' - - self.play_pipe = gst.parse_launch( - '%s ' \ - '! valve name=valve ' \ - '! queue name=queue ' \ - '! %s' \ - % (self.src_str, self.play_str)) - self.valve = self.play_pipe.get_by_name('valve') - - def message_cb(bus, message): - if message.type == gst.MESSAGE_ERROR: - err, debug = message.parse_error() - logger.error('play_pipe: %s %s' % (err, debug)) - - if not self.fallback: - logger.warning('use fallback_bin') - self.fallback = True - - self.play_str = \ - 'ffmpegcolorspace ' \ - '! ximagesink force-aspect-ratio=true ' \ - ' name=xsink' - - self.play_pipe.remove( - self.play_pipe.get_by_name('xsink')) - - c = gst.element_factory_make('ffmpegcolorspace') - s = gst.element_factory_make('ximagesink', 'xsink') - s.props.force_aspect_ratio = True - - self.play_pipe.add(c, s) - gst.element_link_many( - self.play_pipe.get_by_name('queue'), c, s) - - if [i for i in self.pipeline.get_state() \ - if id(i) == id(gst.STATE_PLAYING)]: - self.pipeline = None - self._switch_pipe(self.play_pipe) - - bus = self.play_pipe.get_bus() - bus.add_signal_watch() - bus.connect('message', message_cb) - - self._switch_pipe(self.play_pipe) - - def thumb_play(self, use_fallback=False): - if not camera_presents: - return + self.playing = False + self.picExposureOpen = False - if not self.fallback and not use_fallback: - # use xv to scale video - self.play() - return + self.AUDIO_TRANSCODE_ID = 0 + self.TRANSCODE_ID = 0 + self.VIDEO_TRANSCODE_ID = 0 - logger.debug('thumb_play') + self.PHOTO_MODE_PHOTO = 0 + self.PHOTO_MODE_AUDIO = 1 - if not self.fallback_pipe: - self.fallback_pipe = gst.parse_launch( - '%s ' \ - '! queue ' \ - '! videoscale ' \ - '! video/x-raw-yuv,width=%s,height=%s ' \ - '! ffmpegcolorspace ' \ - '! ximagesink force-aspect-ratio=true name=xsink' \ - % (self.src_str, ui.UI.dim_PIPW, ui.UI.dim_PIPH)) + self.TRANSCODE_UPDATE_INTERVAL = 200 - def message_cb(bus, message): - if message.type == gst.MESSAGE_ERROR: - err, debug = message.parse_error() - logger.error('fallback_pipe: %s %s' % (err, debug)) - bus = self.fallback_pipe.get_bus() - bus.add_signal_watch() - bus.connect('message', message_cb) + self.VIDEO_WIDTH_SMALL = 160 + self.VIDEO_HEIGHT_SMALL = 120 + self.VIDEO_FRAMERATE_SMALL = 10 - self._switch_pipe(self.fallback_pipe) + self.VIDEO_WIDTH_LARGE = 200 + self.VIDEO_HEIGHT_LARGE = 150 + self.VIDEO_FRAMERATE_SMALL = 10 - def pause(self): - logger.debug('pause') - if self.pipeline: - self.pipeline.set_state(gst.STATE_PAUSED) + self.pipeline = gst.Pipeline("my-pipeline") + self.createPhotoBin() + self.createAudioBin() + self.createVideoBin() + self.createPipeline() - def stop(self): - logger.debug('stop') - if self.pipeline: - self.pipeline.set_state(gst.STATE_NULL) + self.thumbPipes = [] + self.muxPipes = [] - def takePhoto(self, after_photo_cb=None): - if not camera_presents: - return + bus = self.pipeline.get_bus() + bus.enable_sync_message_emission() + bus.add_signal_watch() + self.SYNC_ID = bus.connect('sync-message::element', self._onSyncMessageCb) + self.MESSAGE_ID = bus.connect('message', self._onMessageCb) - logger.debug('takePhoto') - - if not self.photo: - def sink_handoff(sink, buffer, pad, self): - sink.props.signal_handoffs = False - - pixbuf = gtk.gdk.pixbuf_loader_new_with_mime_type('image/jpeg') - pixbuf.write(buffer) - pixbuf.close() - - structure = gst.Structure('record.photo') - structure['pixbuf'] = pixbuf.get_pixbuf() - msg = gst.message_new_custom(gst.MESSAGE_APPLICATION, sink, - structure) - self.play_pipe.get_bus().post(msg) - - self.photo = gst.element_factory_make('ffmpegcolorspace') - self.photo_jpegenc = gst.element_factory_make('jpegenc') - self.photo_sink = gst.element_factory_make('fakesink') - self.photo_sink.connect('handoff', sink_handoff, self) - - def message_cb(bus, message, self): - if message.type == gst.MESSAGE_APPLICATION \ - and message.structure.get_name() == 'record.photo': - self.valve.props.drop = True - self.play_pipe.remove(self.photo) - self.play_pipe.remove(self.photo_jpegenc) - self.play_pipe.remove(self.photo_sink) - self.valve.link(self.play_pipe.get_by_name('queue')) - self.valve.props.drop = False - self.after_photo_cb(self, message.structure['pixbuf']) - - bus = self.play_pipe.get_bus() - bus.add_signal_watch() - bus.connect('message', message_cb, self) - - def process_cb(self, pixbuf): - self.ca.m.savePhoto(pixbuf) - self._switch_pipe(self.play_pipe) + def createPhotoBin ( self ): + queue = gst.element_factory_make("queue", "pbqueue") + queue.set_property("leaky", True) + queue.set_property("max-size-buffers", 1) - self.after_photo_cb = after_photo_cb and after_photo_cb or process_cb + colorspace = gst.element_factory_make("ffmpegcolorspace", "pbcolorspace") + jpeg = gst.element_factory_make("jpegenc", "pbjpeg") - self.valve.props.drop = True - self.valve.unlink(self.play_pipe.get_by_name('queue')) - self.play_pipe.add(self.photo, self.photo_jpegenc, self.photo_sink) - gst.element_link_many(self.valve, self.photo, self.photo_jpegenc, - self.photo_sink) - self.photo_sink.props.signal_handoffs = True - self.valve.props.drop = False + sink = gst.element_factory_make("fakesink", "pbsink") + self.HANDOFF_ID = sink.connect("handoff", self.copyPic) + sink.set_property("signal-handoffs", True) - self._switch_pipe(self.play_pipe) + self.photobin = gst.Bin("photobin") + self.photobin.add(queue, colorspace, jpeg, sink) - def startRecordingVideo(self, quality): - if not camera_presents: - return + gst.element_link_many(queue, colorspace, jpeg, sink) - logger.debug('startRecordingVideo quality=%s' % quality) - - if not self.video_pipe or quality != self.ogg_quality: - self.video_pipe = gst.parse_launch( \ - '%s ' \ - '! tee name=tee ' \ - 'tee.! queue ! %s ' \ - 'tee.! queue ' \ - '! ffmpegcolorspace ' \ - '! videorate skip_to_first=true ' \ - '! video/x-raw-yuv,framerate=10/1 ' \ - '! videoscale ' \ - '! video/x-raw-yuv,width=%s,height=%s ' \ - '! theoraenc quality=%s ' \ - '! oggmux ' \ - '! filesink location=%s ' \ - 'alsasrc ' \ - '! queue ' \ - '! audio/x-raw-int,rate=16000,channels=1,depth=16 ' \ - '! wavenc ' \ - '! filesink location=%s ' \ - % (self.src_str, self.play_str, - OGG_TRAITS[quality]['width'], - OGG_TRAITS[quality]['height'], - OGG_TRAITS[quality]['quality'], - TMP_OGV, TMP_WAV)) - - def message_cb(bus, message, self): - if message.type == gst.MESSAGE_ERROR: - err, debug = message.parse_error() - logger.error('video_pipe: %s %s' % (err, debug)) - - bus = self.video_pipe.get_bus() - bus.add_signal_watch() - bus.connect('message', message_cb, self) - - def process_cb(self, pixbuf): - self.pixbuf = pixbuf - self._switch_pipe(self.video_pipe) - - self.ogg_quality = quality - # take photo first - self.takePhoto(process_cb) + pad = queue.get_static_pad("sink") + self.photobin.add_pad(gst.GhostPad("sink", pad)) - def stopRecordingVideo(self): + def createAudioBin ( self ): + src = gst.element_factory_make("alsasrc", "absrc") + srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16") + + enc = gst.element_factory_make("wavenc", "abenc") + + sink = gst.element_factory_make("filesink", "absink") + sink.set_property("location", os.path.join(Instance.instancePath, "output.wav")) + + self.audiobin = gst.Bin("audiobin") + self.audiobin.add(src, enc, sink) + + src.link(enc, srccaps) + enc.link(sink) + + def createVideoBin ( self ): + queue = gst.element_factory_make("queue", "vbqueue") + + scale = gst.element_factory_make("videoscale", "vbscale") + + scalecapsfilter = gst.element_factory_make("capsfilter", "scalecaps") + + scalecaps = gst.Caps('video/x-raw-yuv,width='+str(self.VIDEO_WIDTH_SMALL)+',height='+str(self.VIDEO_HEIGHT_SMALL)) + scalecapsfilter.set_property("caps", scalecaps) + + colorspace = gst.element_factory_make("ffmpegcolorspace", "vbcolorspace") + + enc = gst.element_factory_make("theoraenc", "vbenc") + enc.set_property("quality", 16) + + mux = gst.element_factory_make("oggmux", "vbmux") + + sink = gst.element_factory_make("filesink", "vbfile") + sink.set_property("location", os.path.join(Instance.instancePath, "output.ogg")) + + self.videobin = gst.Bin("videobin") + self.videobin.add(queue, scale, scalecapsfilter, colorspace, enc, mux, sink) + + queue.link(scale) + scale.link_pads(None, scalecapsfilter, "sink") + scalecapsfilter.link_pads("src", colorspace, None) + gst.element_link_many(colorspace, enc, mux, sink) + + pad = queue.get_static_pad("sink") + self.videobin.add_pad(gst.GhostPad("sink", pad)) + + def cfgVideoBin (self, quality, width, height): + vbenc = self.videobin.get_by_name("vbenc") + vbenc.set_property("quality", 16) + scaps = self.videobin.get_by_name("scalecaps") + scaps.set_property("caps", gst.Caps("video/x-raw-yuv,width=%d,height=%d" % (width, height))) + + def createPipeline ( self ): + src = gst.element_factory_make("v4l2src", "camsrc") + try: + # old gst-plugins-good does not have this property + src.set_property("queue-size", 2) + except: + pass + + rate = gst.element_factory_make("videorate", "vbrate") + ratecaps = gst.Caps('video/x-raw-yuv,framerate='+str(self.VIDEO_FRAMERATE_SMALL)+'/1') + + tee = gst.element_factory_make("tee", "tee") + queue = gst.element_factory_make("queue", "dispqueue") + xvsink = gst.element_factory_make("xvimagesink", "xvsink") + self.pipeline.add(src, rate, tee, queue, xvsink) + src.link(rate) + rate.link(tee, ratecaps) + gst.element_link_many(tee, queue, xvsink) + + def thumbPipe(self): + return self.thumbPipes[ len(self.thumbPipes)-1 ] + + + def thumbEl(self, name): + return self.thumbPipe().get_by_name(name) + + + def muxPipe(self): + return self.muxPipes[ len(self.muxPipes)-1 ] + + + def muxEl(self, name): + return self.muxPipe().get_by_name(name) + + + def play(self): if not camera_presents: return - logger.debug('stopRecordingVideo') + self.pipeline.set_state(gst.STATE_PLAYING) + self.playing = True - self._switch_pipe(self.play_pipe) + def pause(self): + self.pipeline.set_state(gst.STATE_PAUSED) + self.playing = False - if not os.path.exists(TMP_OGV) \ - or not os.path.exists(TMP_WAV): + + def stop(self): + self.pipeline.set_state(gst.STATE_NULL) + self.playing = False + + def is_playing(self): + return self.playing + + def idlePlayElement(self, element): + element.set_state(gst.STATE_PLAYING) + return False + + def stopRecordingAudio( self ): + self.audiobin.set_state(gst.STATE_NULL) + self.pipeline.remove(self.audiobin) + gobject.idle_add( self.stoppedRecordingAudio ) + + + def stoppedRecordingVideo(self): + if ( len(self.thumbPipes) > 0 ): + thumbline = self.thumbPipes[len(self.thumbPipes)-1] + thumbline.get_by_name('thumbFakesink').disconnect(self.THUMB_HANDOFF_ID) + + oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv + if (not os.path.exists(oggFilepath)): + self.record = False self.ca.m.cannotSaveVideo() self.ca.m.stoppedRecordingVideo() return - - if os.path.getsize(TMP_OGV) <= 0 \ - or os.path.getsize(TMP_WAV) <= 0: + oggSize = os.path.getsize(oggFilepath) + if (oggSize <= 0): + self.record = False self.ca.m.cannotSaveVideo() self.ca.m.stoppedRecordingVideo() return - if self.mux_pipe: - self.mux_pipe.set_state(gst.STATE_NULL) - del self.mux_pipe - - self.mux_pipe = gst.parse_launch( \ - 'filesrc location=%s ' \ - '! oggdemux ' \ - '! theoraparse ' \ - '! oggmux name=mux ' \ - '! filesink location=%s ' \ - 'filesrc location=%s ' \ - '! wavparse ' \ - '! audioconvert ' \ - '%s ' \ - '! vorbisenc name=vorbisenc ' \ - '! mux.' \ - % (TMP_OGV, TMP_OGG, TMP_WAV, AUDIO_FILTER)) - - taglist = self.getTags(Constants.TYPE_VIDEO) - vorbisenc = self.mux_pipe.get_by_name('vorbisenc') - vorbisenc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL) - - def done(bus, message, self): - if message.type == gst.MESSAGE_ERROR: - err, debug = message.parse_error() - logger.error('audio_pipe: %s %s' % (err, debug)) + line = 'filesrc location=' + str(oggFilepath) + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumbTee ! queue name=thumbQueue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumbFakesink' + thumbline = gst.parse_launch(line) + thumbQueue = thumbline.get_by_name('thumbQueue') + thumbQueue.set_property("leaky", True) + thumbQueue.set_property("max-size-buffers", 1) + thumbTee = thumbline.get_by_name('thumbTee') + thumbFakesink = thumbline.get_by_name('thumbFakesink') + self.THUMB_HANDOFF_ID = thumbFakesink.connect("handoff", self.copyThumbPic) + thumbFakesink.set_property("signal-handoffs", True) + self.thumbPipes.append(thumbline) + self.thumbExposureOpen = True + gobject.idle_add( self.idlePlayElement, thumbline ) + + + def stoppedRecordingAudio( self ): + record.Record.log.debug("stoppedRecordingAudio") + if (self.audioPixbuf != None): + audioFilepath = os.path.join(Instance.instancePath, "output.wav")#self.el("audioFilesink").get_property("location") + if (not os.path.exists(audioFilepath)): + self.record = False + self.audio = False + self.ca.m.cannotSaveVideo() return - elif message.type != gst.MESSAGE_EOS: + wavSize = os.path.getsize(audioFilepath) + if (wavSize <= 0): + self.record = False + self.ca.m.cannotSaveVideo() return - logger.debug('stopRecordingVideo.done') - self.mux_pipe.set_state(gst.STATE_NULL) + self.ca.ui.setPostProcessPixBuf(self.audioPixbuf) - os.remove(TMP_OGV) - os.remove(TMP_WAV) + line = 'filesrc location=' + str(audioFilepath) + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink' + audioline = gst.parse_launch(line) - ogg_w = OGG_TRAITS[self.ogg_quality]['width'] - ogg_h = OGG_TRAITS[self.ogg_quality]['height'] + taglist = self.getTags(Constants.TYPE_AUDIO) + base64AudioSnapshot = utils.getStringFromPixbuf(self.audioPixbuf) + taglist[gst.TAG_EXTENDED_COMMENT] = "coverart="+str(base64AudioSnapshot) + vorbisEnc = audioline.get_by_name('audioVorbisenc') + vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL) - thumb = self.pixbuf.scale_simple(ogg_w, ogg_h, - gtk.gdk.INTERP_HYPER) + audioFilesink = audioline.get_by_name('audioFilesink') + audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg") + audioFilesink.set_property("location", audioOggFilepath ) - self.ca.ui.setPostProcessPixBuf(thumb) - self.ca.m.saveVideo(thumb, TMP_OGG, ogg_w, ogg_h) - self.ca.m.stoppedRecordingVideo() - self.ca.ui.updateVideoComponents() + audioBus = audioline.get_bus() + audioBus.add_signal_watch() + self.AUDIO_TRANSCODE_ID = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline) + self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, audioline) + gobject.idle_add( self.idlePlayElement, audioline ) + else: + self.record = False + self.audio = False + self.ca.m.cannotSaveVideo() - bus = self.mux_pipe.get_bus() - bus.add_signal_watch() - bus.connect('message', done, self) - self.mux_pipe.set_state(gst.STATE_PLAYING) + def getTags( self, type ): + tl = gst.TagList() + tl[gst.TAG_ARTIST] = str(Instance.nickName) + tl[gst.TAG_COMMENT] = "olpc" + #this is unfortunately, unreliable + #record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) ) + tl[gst.TAG_ALBUM] = "olpc" #self.ca.metadata['title'] + tl[gst.TAG_DATE] = utils.getDateString(int(time.time())) + stringType = Constants.mediaTypes[type][Constants.keyIstr] + tl[gst.TAG_TITLE] = Constants.istrBy % {"1":stringType, "2":str(Instance.nickName)} + return tl - def startRecordingAudio(self): - logger.debug('startRecordingAudio') - - audio_pipe = \ - 'alsasrc ' \ - '! audioconvert ' \ - '! audio/x-raw-int,rate=16000,channels=1,depth=16 ' \ - '! queue ' \ - '%s ' \ - '! speexenc quality=2 name=vorbisenc ' \ - '! oggmux ' \ - '! filesink location=%s ' \ - % (AUDIO_FILTER, TMP_OGG) - - self.audio_pipe = gst.parse_launch(audio_pipe) - - def process_cb(self, pixbuf): - # XXX setting tags on fc9 crashes Record - #taglist = self.getTags(Constants.TYPE_AUDIO) - #cover = utils.getStringFromPixbuf(pixbuf) - #taglist[gst.TAG_EXTENDED_COMMENT] = 'coverart=%s' % cover - #vorbisenc = self.audio_pipe.get_by_name('vorbisenc') - #orbisenc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL) - - self.pixbuf = pixbuf - if camera_presents: - self._switch_pipe(self.play_pipe) - self.audio_pipe.set_state(gst.STATE_PLAYING) - - if camera_presents: - # take photo first - self.takePhoto(process_cb) - else: - process_cb(self, THUMB_STUB) + def blockedCb(self, x, y, z): + pass - def stopRecordingAudio( self ): - logger.debug('stopRecordingAudio') + def _takePhoto(self): + if self.picExposureOpen: + return - self.audio_pipe.set_state(gst.STATE_NULL) + self.picExposureOpen = True + pad = self.photobin.get_static_pad("sink") + pad.set_blocked_async(True, self.blockedCb, None) + self.pipeline.add(self.photobin) + self.photobin.set_state(gst.STATE_PLAYING) + self.pipeline.get_by_name("tee").link(self.photobin) + pad.set_blocked_async(False, self.blockedCb, None) - if (not os.path.exists(TMP_OGG)): - self.ca.m.cannotSaveVideo() + def takePhoto(self): + if not camera_presents: return - if (os.path.getsize(TMP_OGG) <= 0): - self.ca.m.cannotSaveVideo() + + self.photoMode = self.PHOTO_MODE_PHOTO + self._takePhoto() + + def copyPic(self, fsink, buffer, pad, user_data=None): + if not self.picExposureOpen: + return + + pad = self.photobin.get_static_pad("sink") + pad.set_blocked_async(True, self.blockedCb, None) + self.pipeline.get_by_name("tee").unlink(self.photobin) + self.pipeline.remove(self.photobin) + pad.set_blocked_async(False, self.blockedCb, None) + + self.picExposureOpen = False + pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg") + pic.write( buffer ) + pic.close() + pixBuf = pic.get_pixbuf() + del pic + + self.savePhoto( pixBuf ) + + + def savePhoto(self, pixbuf): + if self.photoMode == self.PHOTO_MODE_AUDIO: + self.audioPixbuf = pixbuf + else: + self.ca.m.savePhoto(pixbuf) + + + def startRecordingVideo(self, quality): + if not camera_presents: + return + + self.record = True + self.audio = True + + self.cfgVideoBin (OGG_TRAITS[quality]['quality'], + OGG_TRAITS[quality]['width'], + OGG_TRAITS[quality]['height']) + + pad = self.videobin.get_static_pad("sink") + pad.set_blocked_async(True, self.blockedCb, None) + self.pipeline.add(self.videobin) + self.videobin.set_state(gst.STATE_PLAYING) + self.pipeline.get_by_name("tee").link(self.videobin) + pad.set_blocked_async(False, self.blockedCb, None) + self.pipeline.add(self.audiobin) + self.audiobin.set_state(gst.STATE_PLAYING) + + def startRecordingAudio(self): + self.audioPixbuf = None + + self.photoMode = self.PHOTO_MODE_AUDIO + self._takePhoto() + + self.record = True + self.pipeline.add(self.audiobin) + self.audiobin.set_state(gst.STATE_PLAYING) + + def stopRecordingVideo(self): + if not camera_presents: return - self.ca.ui.setPostProcessPixBuf(self.pixbuf) - self.ca.m.saveAudio(TMP_OGG, self.pixbuf) + # We stop the pipeline while we are adjusting the pipeline to stop + # recording because if we do it on-the-fly, the following video live + # feed to the screen becomes several seconds delayed. Weird! + # FIXME: retest on F11 + self._eos_cb = self.stopRecordingVideoEOS + self.pipeline.get_by_name('camsrc').send_event(gst.event_new_eos()) + self.audiobin.get_by_name('absrc').send_event(gst.event_new_eos()) + + def stopRecordingVideoEOS(self): + self.pipeline.set_state(gst.STATE_NULL) + self.pipeline.get_by_name("tee").unlink(self.videobin) + self.pipeline.remove(self.videobin) + self.pipeline.remove(self.audiobin) + self.pipeline.set_state(gst.STATE_PLAYING) + gobject.idle_add( self.stoppedRecordingVideo ) + + + def copyThumbPic(self, fsink, buffer, pad, user_data=None): + if (self.thumbExposureOpen): + self.thumbExposureOpen = False + pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg") + pic.write(buffer) + pic.close() + self.thumbBuf = pic.get_pixbuf() + del pic + self.thumbEl('thumbTee').unlink(self.thumbEl('thumbQueue')) + + oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv + if (self.audio): + self.ca.ui.setPostProcessPixBuf(self.thumbBuf) + + wavFilepath = os.path.join(Instance.instancePath, "output.wav") + muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv + + muxline = gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoradec name=muxTheoradec ! theoraenc name=muxTheoraenc ! oggmux name=muxOggmux ! filesink location=' + str(muxFilepath) + ' name=muxFilesink filesrc location=' + str(wavFilepath) + ' name=muxAudioFilesrc ! wavparse name=muxWavparse ! audioconvert name=muxAudioconvert ! vorbisenc name=muxVorbisenc ! muxOggmux.') + taglist = self.getTags(Constants.TYPE_VIDEO) + vorbisEnc = muxline.get_by_name('muxVorbisenc') + vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL) + + muxBus = muxline.get_bus() + muxBus.add_signal_watch() + self.VIDEO_TRANSCODE_ID = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline) + self.muxPipes.append(muxline) + #add a listener here to monitor % of transcoding... + self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, muxline) + muxline.set_state(gst.STATE_PLAYING) + else: + self.record = False + self.audio = False + self.ca.m.saveVideo(self.thumbBuf, str(oggFilepath), self.VIDEO_WIDTH_SMALL, self.VIDEO_HEIGHT_SMALL) + self.ca.m.stoppedRecordingVideo() + + + def _transcodeUpdateCb( self, pipe ): + position, duration = self.queryPosition( pipe ) + if position != gst.CLOCK_TIME_NONE: + value = position * 100.0 / duration + value = value/100.0 + self.ca.ui.progressWindow.updateProgress(value, Constants.istrSaving) + return True + + + def queryPosition( self, pipe ): + 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 _onMuxedVideoMessageCb(self, bus, message, pipe): + t = message.type + if (t == gst.MESSAGE_EOS): + self.record = False + self.audio = False + gobject.source_remove(self.VIDEO_TRANSCODE_ID) + self.VIDEO_TRANSCODE_ID = 0 + gobject.source_remove(self.TRANSCODE_ID) + self.TRANSCODE_ID = 0 + pipe.set_state(gst.STATE_NULL) + pipe.get_bus().remove_signal_watch() + pipe.get_bus().disable_sync_message_emission() + + wavFilepath = os.path.join(Instance.instancePath, "output.wav") + oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv + muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv + os.remove( wavFilepath ) + os.remove( oggFilepath ) + self.ca.m.saveVideo(self.thumbBuf, str(muxFilepath), self.VIDEO_WIDTH_SMALL, self.VIDEO_HEIGHT_SMALL) + self.ca.m.stoppedRecordingVideo() + return False + else: + return True + + + def _onMuxedAudioMessageCb(self, bus, message, pipe): + t = message.type + if (t == gst.MESSAGE_EOS): + record.Record.log.debug("audio gst.MESSAGE_EOS") + self.record = False + self.audio = False + gobject.source_remove(self.AUDIO_TRANSCODE_ID) + self.AUDIO_TRANSCODE_ID = 0 + gobject.source_remove(self.TRANSCODE_ID) + self.TRANSCODE_ID = 0 + pipe.set_state(gst.STATE_NULL) + pipe.get_bus().remove_signal_watch() + pipe.get_bus().disable_sync_message_emission() + + wavFilepath = os.path.join(Instance.instancePath, "output.wav") + oggFilepath = os.path.join(Instance.instancePath, "output.ogg") + os.remove( wavFilepath ) + self.ca.m.saveAudio(oggFilepath, self.audioPixbuf) + return False + else: + return True + + + def _onSyncMessageCb(self, bus, message): + if message.structure is None: + return + if message.structure.get_name() == 'prepare-xwindow-id': + self.window.set_sink(message.src) + message.src.set_property('force-aspect-ratio', True) + + + def _onMessageCb(self, bus, message): + 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 def abandonMedia(self): - logger.debug('abandonMedia') self.stop() if (self.AUDIO_TRANSCODE_ID != 0): @@ -408,70 +559,22 @@ class Glive: gobject.source_remove(self.VIDEO_TRANSCODE_ID) self.VIDEO_TRANSCODE_ID = 0 - if (os.path.exists(TMP_OGG)): - os.remove(TMP_OGG) - - def __init__(self, pca): - self.window = None - self.ca = pca + wavFilepath = os.path.join(Instance.instancePath, "output.wav") + if (os.path.exists(wavFilepath)): + os.remove(wavFilepath) + oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv + if (os.path.exists(oggFilepath)): + os.remove(oggFilepath) + muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv + if (os.path.exists(muxFilepath)): + os.remove(muxFilepath) - self.pipeline = None - self.play_pipe = None - self.fallback_pipe = None - self.photo = None - self.video_pipe = None - self.mux_pipe = None - self.audio_pipe = None - - self.src_str = 'fakesrc' - self.play_str = 'fakesink' - - self.fallback = False - - # XXX since sugar doesn't control capture volumes (see #800) - # we have to do it by ourselves - alsasrc = gst.element_factory_make('alsasrc') - alsasrc.set_state(gst.STATE_PAUSED) - for i in alsasrc.list_tracks(): - if i.props.flags & gst.interfaces.MIXER_TRACK_INPUT \ - and re.search('capture', i.label, flags=re.IGNORECASE): - alsasrc.set_record(i, True) - volume = i.props.min_volume \ - + int((i.props.max_volume - i.props.min_volume) \ - / 100. * 90.) - alsasrc.set_volume(i, tuple([volume] * i.props.num_channels)) - logger.debug('Set volume %s to %s' % (volume, i.label)) - alsasrc.set_state(gst.STATE_NULL) - del alsasrc - - def _switch_pipe(self, new_pipe): - if self.pipeline != new_pipe: - if self.pipeline: - self.pipeline.set_state(gst.STATE_NULL) - self.pipeline = new_pipe - - if self.pipeline: - xsink = new_pipe.get_by_name('xsink') - if xsink: - xsink.set_xwindow_id(self.window.window.xid) - self.pipeline.set_state(gst.STATE_PLAYING) - - def getTags( self, type ): - tl = gst.TagList() - tl[gst.TAG_ARTIST] = str(Instance.nickName) - tl[gst.TAG_COMMENT] = "sugar" - #this is unfortunately, unreliable - #record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) ) - tl[gst.TAG_ALBUM] = "sugar" #self.ca.metadata['title'] - tl[gst.TAG_DATE] = utils.getDateString(int(time.time())) - stringType = Constants.mediaTypes[type][Constants.keyIstr] - tl[gst.TAG_TITLE] = Constants.istrBy % {"1":stringType, "2":str(Instance.nickName)} - return tl class LiveVideoWindow(gtk.Window): def __init__(self, bgd ): gtk.Window.__init__(self) + self.imagesink = None self.glive = None self.modify_bg( gtk.STATE_NORMAL, bgd ) @@ -482,3 +585,12 @@ class LiveVideoWindow(gtk.Window): def set_glive(self, pglive): self.glive = pglive self.glive.window = self + + def set_sink(self, sink): + if (self.imagesink != None): + assert self.window.xid + self.imagesink = None + del self.imagesink + + self.imagesink = sink + self.imagesink.set_xwindow_id(self.window.xid) |