Example #1
0
class AndroidCameraPreview(Image):
    """
    Android camera preview widget. Extends Image with a custom shader that 
    displays textures of target GL_TEXTURE_EXTERNAL_OES. Note, this requires 
    Android API 11 because of it's use of SurfaceTexture.
    """
    play = BooleanProperty(False)
    resolution = ListProperty([720, 720])
    camera_id = NumericProperty(-1)


    _camera = None
    _previewCallback = None
    _previewTexture = None

    _previewSurface = None
    _secondary_texture = None


    def __init__(self, **kwargs):
        self.canvas = RenderContext()

        super(AndroidCameraPreview, self).__init__(**kwargs)
        self.bind(resolution=self._resolution_changed)
        self.bind(camera_id=self._camera_id_changed)
        self.bind(play=self._play_changed)


        # This is needed for the default vertex shader.
        self.canvas['projection_mat'] = Window.render_context['projection_mat']


        with self.canvas:   
            Callback(self._draw_callback)
            BindTexture(texture=self._secondary_texture, index=1)
        self.canvas['secondary_texture'] = 1
        self._init_texture(self.resolution)

    def start(self, play=True):
        self._init_camera(self.camera_id)
        self.play = play

    def stop(self):
        self._release_camera()

    def _init_texture(self, resolution):
        width, height = resolution
        # Image looks at self.texture to determine layout, so we create 
        # texture but don't actually display it.
        self.texture = Texture.create(size=(height, width))
        self.texture_size = self.texture.size
        if IsAndroid:
            self._secondary_texture = Texture(width=height, height=width, target=GL_TEXTURE_EXTERNAL_OES, colorfmt='rgba')
            self._secondary_texture.bind()
            self._previewTexture = SurfaceTexture(int(self._secondary_texture.id))

    def _init_camera(self, camera_id):
        Logger.info('Init camera %d' % camera_id)
        if self._camera and self.camera_id == camera_id:
            return
        self._release_camera()
        if IsAndroid:
            self._camera = Camera.open(camera_id)
            parameters = self._camera.getParameters()            

            #print parameters.flatten()
            if parameters is None:
                Logger.warning('Can''t read parameters')
                return

            supportedSizes = parameters.getSupportedVideoSizes()
            if supportedSizes is None:
                supportedSizes = parameters.getSupportedPreviewSizes()
            
            sizes = []
            if supportedSizes is not None:
                  iterator = supportedSizes.iterator()
                  while iterator.hasNext():
                      cameraSize = iterator.next()
                      sizes.append((cameraSize.width, cameraSize.height))

            pickedSize = self._pick_optimal_size(sizes)

            # Update texture according to picked size
            self.resolution = list(pickedSize)

            parameters.setPreviewSize(*pickedSize)
            self._camera.setParameters(parameters)
            self._camera.setPreviewTexture(self._previewTexture)

    def get_camera(self):
        return self._camera

    def _resolution_changed(self, instance, value):
        Logger.info('Resolution changed to ' + str(value))
        self._init_texture(value)

    def _camera_id_changed(self, instance, value):
        Logger.info('Changed camera %d' % value)
        if not IsAndroid:
            return

        # Transform orientation based on selected camera
        if self.camera_id == 0:
            sampleVec = ('1.0-tex_coord0.y','1.0-tex_coord0.x')
        elif self.camera_id == 1:
            sampleVec = ('tex_coord0.y','1.0-tex_coord0.x')
        else:
            raise Exception('Invalid camera id')

        self.canvas.shader.fs = '''

            #extension GL_OES_EGL_image_external : require
            #ifdef GL_ES
                precision highp float;
            #endif

            /* Outputs from the vertex shader */
            varying vec4 frag_color;
            varying vec2 tex_coord0;

            /* uniform texture samplers */
            uniform sampler2D texture0;
            uniform samplerExternalOES texture1;

            void main()
            {
                // Flip & Mirror coordinates to rotate source texture by 90 degress
                gl_FragColor = texture2D(texture1, vec2(%s,%s));
            }
        ''' % (sampleVec[0], sampleVec[1])

    def _play_changed(self, instance, value):
        Logger.info('camera %d _play_changed %d' % (self.camera_id, value))
        if not IsAndroid:
            return

        if value:
            if not self._camera:
                self._init_camera(self.camera_id)        
            self._camera.startPreview()
            Clock.schedule_interval(self._update_canvas, 1.0/30)
        else:
            if self._camera:
                Clock.unschedule(self._update_canvas)
                self._camera.stopPreview()

    def _release_camera(self):
        self.play = False
        if self._camera:
            self._camera.release()
            self._camera = None

    def _pick_optimal_size(self, sizes):        
        # Now just returns the one guaranteed support size
        return sizes[0]

    def _draw_callback(self, instr):
        if self._previewTexture:
            self._previewTexture.updateTexImage()

    def _update_canvas(self, dt):
        self.canvas.ask_update()