diff options
Diffstat (limited to 'olpcgames/camera.py')
-rw-r--r--[-rwxr-xr-x] | olpcgames/camera.py | 186 |
1 files changed, 100 insertions, 86 deletions
diff --git a/olpcgames/camera.py b/olpcgames/camera.py index 249f295..b51a394 100755..100644 --- a/olpcgames/camera.py +++ b/olpcgames/camera.py @@ -2,27 +2,38 @@ Depends upon: pygame - gstreamer (particularly gst-launch) - -Activity demonstrating usage: - - http://dev.laptop.org/git?p=projects/games-misc;a=tree;f=cameratest.activity;hb=HEAD - - + python-gstreamer """ -import threading, subprocess +import threading import logging -import olpcgames import time import os import pygame +import gst from olpcgames.util import get_activity_root log = logging.getLogger( 'olpcgames.camera' ) #log.setLevel( logging.DEBUG ) -CAMERA_LOAD, CAMERA_LOAD_FAIL = olpcgames.CAMERA_LOAD, olpcgames.CAMERA_LOAD +CAMERA_LOAD = 9917 +CAMERA_LOAD_FAIL = 9918 +class CameraSprite(object): + """Create gstreamer surface for the camera.""" + def __init__(self, x, y): + import olpcgames + if olpcgames.WIDGET: + self._init_video(olpcgames.WIDGET, x, y) + + def _init_video(self, widget, x, y): + from olpcgames import video + self._vid = video.VideoWidget() + widget._fixed.put(self._vid, x, y) + self._vid.show() + + self.player = video.Player(self._vid) + self.player.play() + class Camera(object): """A class representing a still-picture camera @@ -40,18 +51,11 @@ class Camera(object): Note: The Camera class is simply a convenience wrapper around a fairly - straightforward gst-launch bus. If you have more involved + straightforward gstreamer bus. If you have more involved requirements for your camera manipulations you will probably find it easier to write your own camera implementation than to use this one. Basically we provide here the "normal" use case of snapping a picture into a pygame image. - - Note: - - With the current camera implementation taking a single photograph - requires about 6 seconds! Obviously we'll need to figure out what's - taking gstreamer so long to process the pipe and fix that. - """ _aliases = { 'camera': 'v4l2src', @@ -61,7 +65,7 @@ class Camera(object): 'jpeg': 'jpegenc', 'jpg': 'jpegenc', } - def __init__(self, source='camera', format='png', filename=None, directory = None): + def __init__(self, source='camera', format='png', filename='snap.png', directory = None): """Initialises the Camera's internal description source -- the gstreamer source for the video to capture, useful values: @@ -70,22 +74,17 @@ class Camera(object): format -- the gstreamer encoder to use for the capture, useful values: 'pngenc','png' -- PNG format graphic 'jpegenc','jpg','jpeg' -- JPEG format graphic - filename -- the filename to use for the capture, if not specified defaults - to a random UUID + '.' + format + filename -- the filename to use for the capture directory -- the directory in which to create the temporary file, defaults to get_activity_root() + 'tmp' """ - log.info( 'Creating camera' ) - if not filename: - import uuid - filename = '%s.%s'%( uuid.uuid4(), format ) self.source = self._aliases.get( source, source ) self.format = self._aliases.get( format, format ) self.filename = filename self.directory = directory - SNAP_PIPELINE = 'gst-launch','%(source)s','!','ffmpegcolorspace','!','%(format)s','!','filesink','location="%(filename)s"' - def _create_subprocess( self ): - """Method to create the gstreamer subprocess from our settings""" + SNAP_PIPELINE = '%(source)s ! ffmpegcolorspace ! %(format)s ! filesink location="%(filename)s"' + def _create_pipe( self ): + """Method to create the cstreamer pipe from our settings""" if not self.directory: path = os.path.join( get_activity_root(), 'tmp' ) try: @@ -98,27 +97,35 @@ class Camera(object): filename = os.path.join( path, self.filename ) format = self.format source = self.source - pipeline = [s%locals() for s in self.SNAP_PIPELINE ] - return filename, subprocess.Popen( - pipeline,stderr = subprocess.PIPE - ) - + pipeline = self.SNAP_PIPELINE % locals() + log.debug( 'Background thread processing: %s', pipeline ) + return filename, gst.parse_launch(pipeline) + def snap(self): """Snap a picture via the camera by iterating gstreamer until finished Note: this is an unsafe implementation, it will cause the whole - activity to hang until the capture finishes. Time to finish is often - measured in whole seconds (3-6s). - - It is *strongly* recommended that you use snap_async instead of snap! + activity to hang if the operation happens to fail! It is strongly + recommended that you use snap_async instead of snap! """ log.debug( 'Starting snap' ) - filename, pipe = self._create_subprocess() - if not pipe.wait(): - log.debug( 'Ending snap, loading: %s', filename ) - return self._load_and_clean( filename ) - else: - raise IOError( """Unable to complete snapshot: %s""", pipe.stderr.read() ) + filename, pipe = self._create_pipe() + pipe.set_state(gst.STATE_PLAYING) + bus = pipe.get_bus() + tmp = False + while True: + event = self.bus.poll(gst.MESSAGE_STATE_CHANGED, 5) + if event: + old, new, pending = event.parse_state_changed() + if pending == gst.STATE_VOID_PENDING: + if tmp: + break + else: + tmp = True + else: + break + log.log( 'Ending snap, loading: %s', filename ) + return self._load_and_clean( filename ) def _load_and_clean( self, filename ): """Use pygame to load given filename, delete after loading/attempt""" try: @@ -135,33 +142,23 @@ class Camera(object): token -- passed back as attribute of the event which signals that capture is finished - We return events of type CAMERA_LOAD with an attribute "succeed" + We return two types of events CAMERA_LOAD and CAMERA_LOAD_FAIL, depending on whether we succeed or not. Attributes of the events which are returned: - success -- whether the loading process succeeded token -- as passed to this method - image -- pygame image.load result if successful, None otherwise filename -- the filename in our temporary directory we used to store - the file temporarily (this file will be deleted before the event - is sent, the name is for informational purposes only). + the file temporarily + image -- pygame image.load result if successful, None otherwise err -- Exception instance if failed, None otherwise Basically identical to the snap method, save that it posts a message - to the event bus in pygame.event instead of blocking and returning... - - Example: - if event == pygame.MOUSEBUTTONDOWN: - camera = Camera( source='test', filename = 'picture32' ) - camera.snap_async( myIdentifier ) - ... - elif event.type == olpcgames.CAMERA_LOAD: - if event.token == myIdentifier: - doSomething( event.image ) + to the event bus in eventwrap instead of blocking and returning... """ log.debug( 'beginning async snap') - t = threading.Thread(target=self._background_snap, args=[token]) + t = threading.Thread(target=self._background_snap, args=(token,)) t.start() + log.debug( 'background thread started for gstreamer' ) return token def _background_snap( @@ -178,33 +175,50 @@ class Camera(object): we begin playing, the second for when we finish. """ log.debug( 'Background thread kicking off gstreamer capture begun' ) - from pygame.event import Event, post - filename, pipe = self._create_subprocess() - if not pipe.wait(): - success = True - log.debug( 'Ending capture, loading: %s', filename ) - try: - image = self._load_and_clean( filename ) - except Exception, err: - image = None - success = False - else: - err = None - else: - success = False - err = pipe.stderr.read() - image = None - evt = Event( - CAMERA_LOAD, - dict( - filename=filename, - success = success, - token = token, - image=image, - err=err - ) - ) - post( evt ) + from olpcgames import eventwrap + from pygame.event import Event + filename, pipe = self._create_pipe() + bus = pipe.get_bus() + bus.add_signal_watch() + def _background_snap_onmessage( bus, message ): + """Handle messages from the picture-snapping bus""" + log.debug( 'Message handler for gst messages: %s', message ) + t = message.type + if t == gst.MESSAGE_EOS: + pipe.set_state(gst.STATE_NULL) + try: + image = self._load_and_clean( filename ) + success = True + except Exception, err: + success = False + image = None + else: + err = None + log.debug( 'Success loading file %r', token ) + eventwrap.post(Event( + CAMERA_LOAD, + filename=filename, + success = success, + token = token, + image=image, + err=err + )) + + elif t == gst.MESSAGE_ERROR: + log.warn( 'Failure loading file %r: %s', token, message ) + pipe.set_state(gst.STATE_NULL) + err, debug = message.parse_error() + eventwrap.post(Event( + CAMERA_LOAD_FAIL, + filename=filename, + success = False, + token = token, + image=None, + err=err + )) + return False + bus.connect('message', _background_snap_onmessage) + pipe.set_state(gst.STATE_PLAYING) def snap(): """Dump a snapshot from the camera to a pygame surface in background thread |