| # -*- coding: utf-8 -*- |
| # |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| |
| # DESCRIPTION : |
| # |
| # This test looks in "/dev/video0" for a v4l2 video capture device, |
| # and starts streaming captured frames on the monitor. |
| # The observer then decides if the captured image looks good or defective, |
| # pressing enter key to let it pass or tab key to fail. |
| # |
| # Then the test will start to test the LED indicator located near the webcam. |
| # The LED test will be repeated for a fixed number (=5 at time of writing) |
| # of rounds, each round it will randomly decide whether to capture from the |
| # cam (the LED turns on when capturing). The captured image will NOT be |
| # shown on the monitor, so the observer must answer what he really sees. |
| # The test passes only if the answer for all rounds are correct. |
| |
| import gtk |
| import glib |
| import pango |
| import numpy |
| |
| from gtk import gdk |
| from random import randrange |
| |
| from autotest_lib.client.bin import factory |
| from autotest_lib.client.bin import factory_ui_lib as ful |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| |
| import v4l2 |
| |
| |
| DEVICE_NAME = '/dev/video0' |
| PREFERRED_WIDTH = 320 |
| PREFERRED_HEIGHT = 240 |
| PREFERRED_BUFFER_COUNT = 4 |
| |
| KEY_GOOD = gdk.keyval_from_name('Return') |
| KEY_BAD = gdk.keyval_from_name('Tab') |
| |
| LABEL_FONT = pango.FontDescription('courier new condensed 16') |
| |
| MESSAGE_STR = ('hit TAB to fail and ENTER to pass\n' + |
| '錯誤請按 TAB,成功請按 ENTER\n') |
| MESSAGE_STR2 = ('hit TAB if the LED is off and ENTER if the LED is on\n' + |
| '請檢查攝像頭 LED 指示燈, 沒亮請按 TAB, 燈亮請按 ENTER\n') |
| |
| |
| class factory_Camera(test.test): |
| version = 1 |
| |
| @staticmethod |
| def get_best_frame_size(dev, pixel_format, width, height): |
| '''Given the preferred frame size, find a reasonable frame size the |
| capture device is capable of. |
| |
| currently it returns the smallest frame size that is equal or bigger |
| than the preferred size in both axis. this does not conform to |
| chrome browser's behavior, but is easier for testing purpose. |
| ''' |
| sizes = [(w, h) for w, h in dev.enum_framesizes(pixel_format) |
| if type(w) is int or type(w) is long] |
| if not sizes: |
| return (width, height) |
| if False: # see doc string above |
| for w, h in sizes: |
| if w >= width and h >= height: |
| return (w,h) |
| good_sizes = [(w, h) for w, h in sizes if w >= width and h >= height] |
| if good_sizes: |
| return min(good_sizes, key=lambda x: x[0] * x[1]) |
| return max(sizes, key=lambda x: x[0] * x[1]) |
| |
| def render(self, pixels): |
| numpy.maximum(pixels, 0, pixels) |
| numpy.minimum(pixels, 255, pixels) |
| self.pixels[:] = pixels |
| self.img.queue_draw() |
| |
| def key_release_callback(self, widget, event): |
| factory.log('key_release_callback %s(%s)' % |
| (event.keyval, gdk.keyval_name(event.keyval))) |
| if event.keyval == KEY_GOOD or event.keyval == KEY_BAD: |
| if self.stage == 0: |
| self.dev.capture_mmap_stop() |
| if event.keyval == KEY_BAD: |
| gtk.main_quit() |
| self.img.hide() |
| self.label.set_text(MESSAGE_STR2) |
| else: |
| if self.ledstats & 1: |
| self.dev.capture_mmap_stop() |
| if bool(self.ledstats & 1) != (event.keyval == KEY_GOOD): |
| self.ledfail = True |
| self.ledstats >>= 1 |
| if self.stage == self.led_rounds: |
| self.fail = False |
| gtk.main_quit() |
| self.stage += 1 |
| if self.ledstats & 1: |
| self.dev.capture_mmap_start() |
| self.label.hide() |
| glib.timeout_add(1000, lambda *x: self.label.show()) |
| return True |
| |
| def register_callbacks(self, w): |
| w.connect('key-release-event', self.key_release_callback) |
| w.add_events(gdk.KEY_RELEASE_MASK) |
| |
| def run_once(self, |
| led_rounds=5): |
| '''Run the camera test |
| |
| Parameter |
| led_rounds: 0 to disable the LED test, |
| 1 to check if the LED turns on, |
| 2 or higher to have multiple random turn on/off |
| (at least one on round and one off round is guranteed) |
| ''' |
| factory.log('%s run_once' % self.__class__) |
| |
| self.fail = True |
| self.ledfail = False |
| self.led_rounds = led_rounds |
| self.ledstats = 0 |
| if led_rounds == 1: |
| # always on if only one round |
| self.ledstats = 1 |
| elif led_rounds > 1: |
| # ensure one on round and one off round |
| self.ledstats = randrange(2 ** led_rounds - 2) + 1 |
| self.stage = 0 |
| |
| self.label = label = gtk.Label(MESSAGE_STR) |
| label.modify_font(LABEL_FONT) |
| label.modify_fg(gtk.STATE_NORMAL, gdk.color_parse('light green')) |
| |
| test_widget = gtk.VBox() |
| test_widget.modify_bg(gtk.STATE_NORMAL, gdk.color_parse('black')) |
| test_widget.add(label) |
| self.test_widget = test_widget |
| |
| self.img = None |
| |
| self.dev = dev = v4l2.Device(DEVICE_NAME) |
| if not dev.cap.capabilities & v4l2.V4L2_CAP_VIDEO_CAPTURE: |
| raise ValueError('%s does not support video capture interface' |
| % (DEVICE_NAME, )) |
| if not dev.cap.capabilities & v4l2.V4L2_CAP_STREAMING: |
| raise ValueError('%s does not support streaming I/O' |
| % (DEVICE_NAME, )) |
| glib.io_add_watch(dev.fd, glib.IO_IN, |
| lambda *x:dev.capture_mmap_shot(self.render) or True, |
| priority=glib.PRIORITY_LOW) |
| |
| frame_size = self.get_best_frame_size(dev, v4l2.V4L2_PIX_FMT_YUYV, |
| PREFERRED_WIDTH, PREFERRED_HEIGHT) |
| |
| adj_fmt = dev.capture_set_format(frame_size[0], frame_size[1], |
| v4l2.V4L2_PIX_FMT_YUYV, v4l2.V4L2_FIELD_INTERLACED) |
| width, height = adj_fmt.fmt.pix.width, adj_fmt.fmt.pix.height |
| |
| self.pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, |
| width, height) |
| self.pixels = self.pixbuf.get_pixels_array() |
| self.img = gtk.image_new_from_pixbuf(self.pixbuf) |
| self.test_widget.add(self.img) |
| self.img.show() |
| |
| dev.capture_mmap_prepare(PREFERRED_BUFFER_COUNT, 2) |
| dev.capture_mmap_start() |
| |
| ful.run_test_widget(self.job, test_widget, |
| window_registration_callback=self.register_callbacks) |
| |
| # we don't call capture_mmap_stop here, |
| # it will be called before returning from main loop. |
| dev.capture_mmap_finish() |
| |
| if self.fail: |
| raise error.TestFail('Camera test failed by user indication\n' \ |
| '品管人員懷疑攝影鏡頭故障,請檢修') |
| if self.ledfail: |
| raise error.TestFail('Camera LED test failed\n' \ |
| '攝影鏡頭 LED 測試不通過,請檢修') |
| |
| factory.log('%s run_once finished' % self.__class__) |