1# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import atexit 6import httplib 7import logging 8import os 9import socket 10import time 11import xmlrpclib 12from contextlib import contextmanager 13 14try: 15 from PIL import Image 16except ImportError: 17 Image = None 18 19from autotest_lib.client.bin import utils 20from autotest_lib.client.common_lib import error 21from autotest_lib.client.cros.chameleon import audio_board 22from autotest_lib.client.cros.chameleon import edid as edid_lib 23from autotest_lib.client.cros.chameleon import usb_controller 24 25 26CHAMELEON_PORT = 9992 27CHAMELEOND_LOG_REMOTE_PATH = '/var/log/chameleond' 28CHAMELEON_READY_TEST = 'GetSupportedPorts' 29 30 31class ChameleonConnectionError(error.TestError): 32 """Indicates that connecting to Chameleon failed. 33 34 It is fatal to the test unless caught. 35 """ 36 pass 37 38 39class _Method(object): 40 """Class to save the name of the RPC method instead of the real object. 41 42 It keeps the name of the RPC method locally first such that the RPC method 43 can be evaluated to a real object while it is called. Its purpose is to 44 refer to the latest RPC proxy as the original previous-saved RPC proxy may 45 be lost due to reboot. 46 47 The call_server is the method which does refer to the latest RPC proxy. 48 49 This class and the re-connection mechanism in ChameleonConnection is 50 copied from third_party/autotest/files/server/cros/faft/rpc_proxy.py 51 52 """ 53 def __init__(self, call_server, name): 54 """Constructs a _Method. 55 56 @param call_server: the call_server method 57 @param name: the method name or instance name provided by the 58 remote server 59 60 """ 61 self.__call_server = call_server 62 self._name = name 63 64 65 def __getattr__(self, name): 66 """Support a nested method. 67 68 For example, proxy.system.listMethods() would need to use this method 69 to get system and then to get listMethods. 70 71 @param name: the method name or instance name provided by the 72 remote server 73 74 @return: a callable _Method object. 75 76 """ 77 return _Method(self.__call_server, "%s.%s" % (self._name, name)) 78 79 80 def __call__(self, *args, **dargs): 81 """The call method of the object. 82 83 @param args: arguments for the remote method. 84 @param kwargs: keyword arguments for the remote method. 85 86 @return: the result returned by the remote method. 87 88 """ 89 return self.__call_server(self._name, *args, **dargs) 90 91 92class ChameleonConnection(object): 93 """ChameleonConnection abstracts the network connection to the board. 94 95 When a chameleon board is rebooted, a xmlrpc call would incur a 96 socket error. To fix the error, a client has to reconnect to the server. 97 ChameleonConnection is a wrapper of chameleond proxy created by 98 xmlrpclib.ServerProxy(). ChameleonConnection has the capability to 99 automatically reconnect to the server when such socket error occurs. 100 The nice feature is that the auto re-connection is performed inside this 101 wrapper and is transparent to the caller. 102 103 Note: 104 1. When running chameleon autotests in lab machines, it is 105 ChameleonConnection._create_server_proxy() that is invoked. 106 2. When running chameleon autotests in local chroot, it is 107 rpc_server_tracker.xmlrpc_connect() in server/hosts/chameleon_host.py 108 that is invoked. 109 110 ChameleonBoard and ChameleonPort use it for accessing Chameleon RPC. 111 112 """ 113 114 def __init__(self, hostname, port=CHAMELEON_PORT, proxy_generator=None, 115 ready_test_name=CHAMELEON_READY_TEST): 116 """Constructs a ChameleonConnection. 117 118 @param hostname: Hostname the chameleond process is running. 119 @param port: Port number the chameleond process is listening on. 120 @param proxy_generator: a function to generate server proxy. 121 @param ready_test_name: run this method on the remote server ot test 122 if the server is connected correctly. 123 124 @raise ChameleonConnectionError if connection failed. 125 """ 126 self._hostname = hostname 127 self._port = port 128 129 # Note: it is difficult to put the lambda function as the default 130 # value of the proxy_generator argument. In that case, the binding 131 # of arguments (hostname and port) would be delayed until run time 132 # which requires to pass an instance as an argument to labmda. 133 # That becomes cumbersome since server/hosts/chameleon_host.py 134 # would also pass a lambda without argument to instantiate this object. 135 # Use the labmda function as follows would bind the needed arguments 136 # immediately which is much simpler. 137 self._proxy_generator = proxy_generator or self._create_server_proxy 138 139 self._ready_test_name = ready_test_name 140 self.chameleond_proxy = None 141 142 143 def _create_server_proxy(self): 144 """Creates the chameleond server proxy. 145 146 @param hostname: Hostname the chameleond process is running. 147 @param port: Port number the chameleond process is listening on. 148 149 @return ServerProxy object to chameleond. 150 151 @raise ChameleonConnectionError if connection failed. 152 153 """ 154 remote = 'http://%s:%s' % (self._hostname, self._port) 155 chameleond_proxy = xmlrpclib.ServerProxy(remote, allow_none=True) 156 logging.info('ChameleonConnection._create_server_proxy() called') 157 # Call a RPC to test. 158 try: 159 getattr(chameleond_proxy, self._ready_test_name)() 160 except (socket.error, 161 xmlrpclib.ProtocolError, 162 httplib.BadStatusLine) as e: 163 raise ChameleonConnectionError(e) 164 return chameleond_proxy 165 166 167 def _reconnect(self): 168 """Reconnect to chameleond.""" 169 self.chameleond_proxy = self._proxy_generator() 170 171 172 def __call_server(self, name, *args, **kwargs): 173 """Bind the name to the chameleond proxy and execute the method. 174 175 @param name: the method name or instance name provided by the 176 remote server. 177 @param args: arguments for the remote method. 178 @param kwargs: keyword arguments for the remote method. 179 180 @return: the result returned by the remote method. 181 182 """ 183 try: 184 return getattr(self.chameleond_proxy, name)(*args, **kwargs) 185 except (AttributeError, socket.error): 186 # Reconnect and invoke the method again. 187 logging.info('Reconnecting chameleond proxy: %s', name) 188 self._reconnect() 189 return getattr(self.chameleond_proxy, name)(*args, **kwargs) 190 191 192 def __getattr__(self, name): 193 """Get the callable _Method object. 194 195 @param name: the method name or instance name provided by the 196 remote server 197 198 @return: a callable _Method object. 199 200 """ 201 return _Method(self.__call_server, name) 202 203 204class ChameleonBoard(object): 205 """ChameleonBoard is an abstraction of a Chameleon board. 206 207 A Chameleond RPC proxy is passed to the construction such that it can 208 use this proxy to control the Chameleon board. 209 210 User can use host to access utilities that are not provided by 211 Chameleond XMLRPC server, e.g. send_file and get_file, which are provided by 212 ssh_host.SSHHost, which is the base class of ChameleonHost. 213 214 """ 215 216 def __init__(self, chameleon_connection, chameleon_host=None): 217 """Construct a ChameleonBoard. 218 219 @param chameleon_connection: ChameleonConnection object. 220 @param chameleon_host: ChameleonHost object. None if this ChameleonBoard 221 is not created by a ChameleonHost. 222 """ 223 self.host = chameleon_host 224 self._output_log_file = None 225 self._chameleond_proxy = chameleon_connection 226 self._usb_ctrl = usb_controller.USBController(chameleon_connection) 227 if self._chameleond_proxy.HasAudioBoard(): 228 self._audio_board = audio_board.AudioBoard(chameleon_connection) 229 else: 230 self._audio_board = None 231 logging.info('There is no audio board on this Chameleon.') 232 233 234 def reset(self): 235 """Resets Chameleon board.""" 236 self._chameleond_proxy.Reset() 237 238 239 def setup_and_reset(self, output_dir=None): 240 """Setup and reset Chameleon board. 241 242 @param output_dir: Setup the output directory. 243 None for just reset the board. 244 """ 245 if output_dir and self.host is not None: 246 logging.info('setup_and_reset: dir %s, chameleon host %s', 247 output_dir, self.host.hostname) 248 log_dir = os.path.join(output_dir, 'chameleond', self.host.hostname) 249 # Only clear the chameleon board log and register get log callback 250 # when we first create the log_dir. 251 if not os.path.exists(log_dir): 252 # remove old log. 253 self.host.run('>%s' % CHAMELEOND_LOG_REMOTE_PATH) 254 os.makedirs(log_dir) 255 self._output_log_file = os.path.join(log_dir, 'log') 256 atexit.register(self._get_log) 257 self.reset() 258 259 260 def reboot(self): 261 """Reboots Chameleon board.""" 262 self._chameleond_proxy.Reboot() 263 264 265 def _get_log(self): 266 """Get log from chameleon. It will be registered by atexit. 267 268 It's a private method. We will setup output_dir before using this 269 method. 270 """ 271 self.host.get_file(CHAMELEOND_LOG_REMOTE_PATH, self._output_log_file) 272 273 274 def get_all_ports(self): 275 """Gets all the ports on Chameleon board which are connected. 276 277 @return: A list of ChameleonPort objects. 278 """ 279 ports = self._chameleond_proxy.ProbePorts() 280 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 281 282 283 def get_all_inputs(self): 284 """Gets all the input ports on Chameleon board which are connected. 285 286 @return: A list of ChameleonPort objects. 287 """ 288 ports = self._chameleond_proxy.ProbeInputs() 289 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 290 291 292 def get_all_outputs(self): 293 """Gets all the output ports on Chameleon board which are connected. 294 295 @return: A list of ChameleonPort objects. 296 """ 297 ports = self._chameleond_proxy.ProbeOutputs() 298 return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 299 300 301 def get_label(self): 302 """Gets the label which indicates the display connection. 303 304 @return: A string of the label, like 'hdmi', 'dp_hdmi', etc. 305 """ 306 connectors = [] 307 for port in self._chameleond_proxy.ProbeInputs(): 308 if self._chameleond_proxy.HasVideoSupport(port): 309 connector = self._chameleond_proxy.GetConnectorType(port).lower() 310 connectors.append(connector) 311 # Eliminate duplicated ports. It simplifies the labels of dual-port 312 # devices, i.e. dp_dp categorized into dp. 313 return '_'.join(sorted(set(connectors))) 314 315 316 def get_audio_board(self): 317 """Gets the audio board on Chameleon. 318 319 @return: An AudioBoard object. 320 """ 321 return self._audio_board 322 323 324 def get_usb_controller(self): 325 """Gets the USB controller on Chameleon. 326 327 @return: A USBController object. 328 """ 329 return self._usb_ctrl 330 331 332 def get_bluetooh_hid_mouse(self): 333 """Gets the emulated bluetooth hid mouse on Chameleon. 334 335 @return: A BluetoothHIDMouseFlow object. 336 """ 337 return self._chameleond_proxy.bluetooth_mouse 338 339 340 def get_avsync_probe(self): 341 """Gets the avsync probe device on Chameleon. 342 343 @return: An AVSyncProbeFlow object. 344 """ 345 return self._chameleond_proxy.avsync_probe 346 347 348 def get_motor_board(self): 349 """Gets the motor_board device on Chameleon. 350 351 @return: An MotorBoard object. 352 """ 353 return self._chameleond_proxy.motor_board 354 355 356 def get_mac_address(self): 357 """Gets the MAC address of Chameleon. 358 359 @return: A string for MAC address. 360 """ 361 return self._chameleond_proxy.GetMacAddress() 362 363 364class ChameleonPort(object): 365 """ChameleonPort is an abstraction of a general port of a Chameleon board. 366 367 It only contains some common methods shared with audio and video ports. 368 369 A Chameleond RPC proxy and an port_id are passed to the construction. 370 The port_id is the unique identity to the port. 371 """ 372 373 def __init__(self, chameleond_proxy, port_id): 374 """Construct a ChameleonPort. 375 376 @param chameleond_proxy: Chameleond RPC proxy object. 377 @param port_id: The ID of the input port. 378 """ 379 self.chameleond_proxy = chameleond_proxy 380 self.port_id = port_id 381 382 383 def get_connector_id(self): 384 """Returns the connector ID. 385 386 @return: A number of connector ID. 387 """ 388 return self.port_id 389 390 391 def get_connector_type(self): 392 """Returns the human readable string for the connector type. 393 394 @return: A string, like "VGA", "DVI", "HDMI", or "DP". 395 """ 396 return self.chameleond_proxy.GetConnectorType(self.port_id) 397 398 399 def has_audio_support(self): 400 """Returns if the input has audio support. 401 402 @return: True if the input has audio support; otherwise, False. 403 """ 404 return self.chameleond_proxy.HasAudioSupport(self.port_id) 405 406 407 def has_video_support(self): 408 """Returns if the input has video support. 409 410 @return: True if the input has video support; otherwise, False. 411 """ 412 return self.chameleond_proxy.HasVideoSupport(self.port_id) 413 414 415 def plug(self): 416 """Asserts HPD line to high, emulating plug.""" 417 logging.info('Plug Chameleon port %d', self.port_id) 418 self.chameleond_proxy.Plug(self.port_id) 419 420 421 def unplug(self): 422 """Deasserts HPD line to low, emulating unplug.""" 423 logging.info('Unplug Chameleon port %d', self.port_id) 424 self.chameleond_proxy.Unplug(self.port_id) 425 426 427 def set_plug(self, plug_status): 428 """Sets plug/unplug by plug_status. 429 430 @param plug_status: True to plug; False to unplug. 431 """ 432 if plug_status: 433 self.plug() 434 else: 435 self.unplug() 436 437 438 @property 439 def plugged(self): 440 """ 441 @returns True if this port is plugged to Chameleon, False otherwise. 442 443 """ 444 return self.chameleond_proxy.IsPlugged(self.port_id) 445 446 447class ChameleonVideoInput(ChameleonPort): 448 """ChameleonVideoInput is an abstraction of a video input port. 449 450 It contains some special methods to control a video input. 451 """ 452 453 _DUT_STABILIZE_TIME = 3 454 _DURATION_UNPLUG_FOR_EDID = 5 455 _TIMEOUT_VIDEO_STABLE_PROBE = 10 456 _EDID_ID_DISABLE = -1 457 _FRAME_RATE = 60 458 459 def __init__(self, chameleon_port): 460 """Construct a ChameleonVideoInput. 461 462 @param chameleon_port: A general ChameleonPort object. 463 """ 464 self.chameleond_proxy = chameleon_port.chameleond_proxy 465 self.port_id = chameleon_port.port_id 466 self._original_edid = None 467 468 469 def wait_video_input_stable(self, timeout=None): 470 """Waits the video input stable or timeout. 471 472 @param timeout: The time period to wait for. 473 474 @return: True if the video input becomes stable within the timeout 475 period; otherwise, False. 476 """ 477 is_input_stable = self.chameleond_proxy.WaitVideoInputStable( 478 self.port_id, timeout) 479 480 # If video input of Chameleon has been stable, wait for DUT software 481 # layer to be stable as well to make sure all the configurations have 482 # been propagated before proceeding. 483 if is_input_stable: 484 logging.info('Video input has been stable. Waiting for the DUT' 485 ' to be stable...') 486 time.sleep(self._DUT_STABILIZE_TIME) 487 return is_input_stable 488 489 490 def read_edid(self): 491 """Reads the EDID. 492 493 @return: An Edid object or NO_EDID. 494 """ 495 edid_binary = self.chameleond_proxy.ReadEdid(self.port_id) 496 if edid_binary is None: 497 return edid_lib.NO_EDID 498 # Read EDID without verify. It may be made corrupted as intended 499 # for the test purpose. 500 return edid_lib.Edid(edid_binary.data, skip_verify=True) 501 502 503 def apply_edid(self, edid): 504 """Applies the given EDID. 505 506 @param edid: An Edid object or NO_EDID. 507 """ 508 if edid is edid_lib.NO_EDID: 509 self.chameleond_proxy.ApplyEdid(self.port_id, self._EDID_ID_DISABLE) 510 else: 511 edid_binary = xmlrpclib.Binary(edid.data) 512 edid_id = self.chameleond_proxy.CreateEdid(edid_binary) 513 self.chameleond_proxy.ApplyEdid(self.port_id, edid_id) 514 self.chameleond_proxy.DestroyEdid(edid_id) 515 516 517 def set_edid_from_file(self, filename): 518 """Sets EDID from a file. 519 520 The method is similar to set_edid but reads EDID from a file. 521 522 @param filename: path to EDID file. 523 """ 524 self.set_edid(edid_lib.Edid.from_file(filename)) 525 526 527 def set_edid(self, edid): 528 """The complete flow of setting EDID. 529 530 Unplugs the port if needed, sets EDID, plugs back if it was plugged. 531 The original EDID is stored so user can call restore_edid after this 532 call. 533 534 @param edid: An Edid object. 535 """ 536 plugged = self.plugged 537 if plugged: 538 self.unplug() 539 540 self._original_edid = self.read_edid() 541 542 logging.info('Apply EDID on port %d', self.port_id) 543 self.apply_edid(edid) 544 545 if plugged: 546 time.sleep(self._DURATION_UNPLUG_FOR_EDID) 547 self.plug() 548 self.wait_video_input_stable(self._TIMEOUT_VIDEO_STABLE_PROBE) 549 550 551 def restore_edid(self): 552 """Restores original EDID stored when set_edid was called.""" 553 current_edid = self.read_edid() 554 if (self._original_edid and 555 self._original_edid.data != current_edid.data): 556 logging.info('Restore the original EDID.') 557 self.apply_edid(self._original_edid) 558 559 560 @contextmanager 561 def use_edid(self, edid): 562 """Uses the given EDID in a with statement. 563 564 It sets the EDID up in the beginning and restores to the original 565 EDID in the end. This function is expected to be used in a with 566 statement, like the following: 567 568 with chameleon_port.use_edid(edid): 569 do_some_test_on(chameleon_port) 570 571 @param edid: An EDID object. 572 """ 573 # Set the EDID up in the beginning. 574 self.set_edid(edid) 575 576 try: 577 # Yeild to execute the with statement. 578 yield 579 finally: 580 # Restore the original EDID in the end. 581 self.restore_edid() 582 583 584 def use_edid_file(self, filename): 585 """Uses the given EDID file in a with statement. 586 587 It sets the EDID up in the beginning and restores to the original 588 EDID in the end. This function is expected to be used in a with 589 statement, like the following: 590 591 with chameleon_port.use_edid_file(filename): 592 do_some_test_on(chameleon_port) 593 594 @param filename: A path to the EDID file. 595 """ 596 return self.use_edid(edid_lib.Edid.from_file(filename)) 597 598 599 def fire_hpd_pulse(self, deassert_interval_usec, assert_interval_usec=None, 600 repeat_count=1, end_level=1): 601 602 """Fires one or more HPD pulse (low -> high -> low -> ...). 603 604 @param deassert_interval_usec: The time in microsecond of the 605 deassert pulse. 606 @param assert_interval_usec: The time in microsecond of the 607 assert pulse. If None, then use the same value as 608 deassert_interval_usec. 609 @param repeat_count: The count of HPD pulses to fire. 610 @param end_level: HPD ends with 0 for LOW (unplugged) or 1 for 611 HIGH (plugged). 612 """ 613 self.chameleond_proxy.FireHpdPulse( 614 self.port_id, deassert_interval_usec, 615 assert_interval_usec, repeat_count, int(bool(end_level))) 616 617 618 def fire_mixed_hpd_pulses(self, widths): 619 """Fires one or more HPD pulses, starting at low, of mixed widths. 620 621 One must specify a list of segment widths in the widths argument where 622 widths[0] is the width of the first low segment, widths[1] is that of 623 the first high segment, widths[2] is that of the second low segment... 624 etc. The HPD line stops at low if even number of segment widths are 625 specified; otherwise, it stops at high. 626 627 @param widths: list of pulse segment widths in usec. 628 """ 629 self.chameleond_proxy.FireMixedHpdPulses(self.port_id, widths) 630 631 632 def capture_screen(self): 633 """Captures Chameleon framebuffer. 634 635 @return An Image object. 636 """ 637 return Image.fromstring( 638 'RGB', 639 self.get_resolution(), 640 self.chameleond_proxy.DumpPixels(self.port_id).data) 641 642 643 def get_resolution(self): 644 """Gets the source resolution. 645 646 @return: A (width, height) tuple. 647 """ 648 # The return value of RPC is converted to a list. Convert it back to 649 # a tuple. 650 return tuple(self.chameleond_proxy.DetectResolution(self.port_id)) 651 652 653 def set_content_protection(self, enable): 654 """Sets the content protection state on the port. 655 656 @param enable: True to enable; False to disable. 657 """ 658 self.chameleond_proxy.SetContentProtection(self.port_id, enable) 659 660 661 def is_content_protection_enabled(self): 662 """Returns True if the content protection is enabled on the port. 663 664 @return: True if the content protection is enabled; otherwise, False. 665 """ 666 return self.chameleond_proxy.IsContentProtectionEnabled(self.port_id) 667 668 669 def is_video_input_encrypted(self): 670 """Returns True if the video input on the port is encrypted. 671 672 @return: True if the video input is encrypted; otherwise, False. 673 """ 674 return self.chameleond_proxy.IsVideoInputEncrypted(self.port_id) 675 676 677 def start_monitoring_audio_video_capturing_delay(self): 678 """Starts an audio/video synchronization utility.""" 679 self.chameleond_proxy.StartMonitoringAudioVideoCapturingDelay() 680 681 682 def get_audio_video_capturing_delay(self): 683 """Gets the time interval between the first audio/video cpatured data. 684 685 @return: A floating points indicating the time interval between the 686 first audio/video data captured. If the result is negative, 687 then the first video data is earlier, otherwise the first 688 audio data is earlier. 689 """ 690 return self.chameleond_proxy.GetAudioVideoCapturingDelay() 691 692 693 def start_capturing_video(self, box=None): 694 """ 695 Captures video frames. Asynchronous, returns immediately. 696 697 @param box: int tuple, (x, y, width, height) pixel coordinates. 698 Defines the rectangular boundary within which to capture. 699 """ 700 701 if box is None: 702 self.chameleond_proxy.StartCapturingVideo(self.port_id) 703 else: 704 self.chameleond_proxy.StartCapturingVideo(self.port_id, *box) 705 706 707 def stop_capturing_video(self): 708 """ 709 Stops the ongoing video frame capturing. 710 711 """ 712 self.chameleond_proxy.StopCapturingVideo() 713 714 715 def get_captured_frame_count(self): 716 """ 717 @return: int, the number of frames that have been captured. 718 719 """ 720 return self.chameleond_proxy.GetCapturedFrameCount() 721 722 723 def read_captured_frame(self, index): 724 """ 725 @param index: int, index of the desired captured frame. 726 @return: xmlrpclib.Binary object containing a byte-array of the pixels. 727 728 """ 729 730 frame = self.chameleond_proxy.ReadCapturedFrame(index) 731 return Image.fromstring('RGB', 732 self.get_captured_resolution(), 733 frame.data) 734 735 736 def get_captured_checksums(self, start_index=0, stop_index=None): 737 """ 738 @param start_index: int, index of the frame to start with. 739 @param stop_index: int, index of the frame (excluded) to stop at. 740 @return: a list of checksums of frames captured. 741 742 """ 743 return self.chameleond_proxy.GetCapturedChecksums(start_index, 744 stop_index) 745 746 747 def get_captured_fps_list(self, time_to_start=0, total_period=None): 748 """ 749 @param time_to_start: time in second, support floating number, only 750 measure the period starting at this time. 751 If negative, it is the time before stop, e.g. 752 -2 meaning 2 seconds before stop. 753 @param total_period: time in second, integer, the total measuring 754 period. If not given, use the maximum time 755 (integer) to the end. 756 @return: a list of fps numbers, or [-1] if any error. 757 758 """ 759 checksums = self.get_captured_checksums() 760 761 frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 762 if total_period is None: 763 # The default is the maximum time (integer) to the end. 764 total_period = (len(checksums) - frame_to_start) / self._FRAME_RATE 765 frame_to_stop = frame_to_start + total_period * self._FRAME_RATE 766 767 if frame_to_start >= len(checksums) or frame_to_stop >= len(checksums): 768 logging.error('The given time interval is out-of-range.') 769 return [-1] 770 771 # Only pick the checksum we are interested. 772 checksums = checksums[frame_to_start:frame_to_stop] 773 774 # Count the unique checksums per second, i.e. FPS 775 logging.debug('Output the fps info below:') 776 fps_list = [] 777 for i in xrange(0, len(checksums), self._FRAME_RATE): 778 unique_count = 0 779 debug_str = '' 780 for j in xrange(i, i + self._FRAME_RATE): 781 if j == 0 or checksums[j] != checksums[j - 1]: 782 unique_count += 1 783 debug_str += '*' 784 else: 785 debug_str += '.' 786 fps_list.append(unique_count) 787 logging.debug('%2dfps %s', unique_count, debug_str) 788 789 return fps_list 790 791 792 def search_fps_pattern(self, pattern_diff_frame, pattern_window=None, 793 time_to_start=0): 794 """Search the captured frames and return the time where FPS is greater 795 than given FPS pattern. 796 797 A FPS pattern is described as how many different frames in a sliding 798 window. For example, 5 differnt frames in a window of 60 frames. 799 800 @param pattern_diff_frame: number of different frames for the pattern. 801 @param pattern_window: number of frames for the sliding window. Default 802 is 1 second. 803 @param time_to_start: time in second, support floating number, 804 start to search from the given time. 805 @return: the time matching the pattern. -1.0 if not found. 806 807 """ 808 if pattern_window is None: 809 pattern_window = self._FRAME_RATE 810 811 checksums = self.get_captured_checksums() 812 813 frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 814 first_checksum = checksums[frame_to_start] 815 816 for i in xrange(frame_to_start + 1, len(checksums) - pattern_window): 817 unique_count = 0 818 for j in xrange(i, i + pattern_window): 819 if j == 0 or checksums[j] != checksums[j - 1]: 820 unique_count += 1 821 if unique_count >= pattern_diff_frame: 822 return float(i) / self._FRAME_RATE 823 824 return -1.0 825 826 827 def get_captured_resolution(self): 828 """ 829 @return: (width, height) tuple, the resolution of captured frames. 830 831 """ 832 return self.chameleond_proxy.GetCapturedResolution() 833 834 835 836class ChameleonAudioInput(ChameleonPort): 837 """ChameleonAudioInput is an abstraction of an audio input port. 838 839 It contains some special methods to control an audio input. 840 """ 841 842 def __init__(self, chameleon_port): 843 """Construct a ChameleonAudioInput. 844 845 @param chameleon_port: A general ChameleonPort object. 846 """ 847 self.chameleond_proxy = chameleon_port.chameleond_proxy 848 self.port_id = chameleon_port.port_id 849 850 851 def start_capturing_audio(self): 852 """Starts capturing audio.""" 853 return self.chameleond_proxy.StartCapturingAudio(self.port_id) 854 855 856 def stop_capturing_audio(self): 857 """Stops capturing audio. 858 859 Returns: 860 A tuple (remote_path, format). 861 remote_path: The captured file path on Chameleon. 862 format: A dict containing: 863 file_type: 'raw' or 'wav'. 864 sample_format: 'S32_LE' for 32-bit signed integer in little-endian. 865 Refer to aplay manpage for other formats. 866 channel: channel number. 867 rate: sampling rate. 868 """ 869 remote_path, data_format = self.chameleond_proxy.StopCapturingAudio( 870 self.port_id) 871 return remote_path, data_format 872 873 874class ChameleonAudioOutput(ChameleonPort): 875 """ChameleonAudioOutput is an abstraction of an audio output port. 876 877 It contains some special methods to control an audio output. 878 """ 879 880 def __init__(self, chameleon_port): 881 """Construct a ChameleonAudioOutput. 882 883 @param chameleon_port: A general ChameleonPort object. 884 """ 885 self.chameleond_proxy = chameleon_port.chameleond_proxy 886 self.port_id = chameleon_port.port_id 887 888 889 def start_playing_audio(self, path, data_format): 890 """Starts playing audio. 891 892 @param path: The path to the file to play on Chameleon. 893 @param data_format: A dict containing data format. Currently Chameleon 894 only accepts data format: 895 dict(file_type='raw', sample_format='S32_LE', 896 channel=8, rate=48000). 897 898 """ 899 self.chameleond_proxy.StartPlayingAudio(self.port_id, path, data_format) 900 901 902 def stop_playing_audio(self): 903 """Stops capturing audio.""" 904 self.chameleond_proxy.StopPlayingAudio(self.port_id) 905 906 907def make_chameleon_hostname(dut_hostname): 908 """Given a DUT's hostname, returns the hostname of its Chameleon. 909 910 @param dut_hostname: Hostname of a DUT. 911 912 @return Hostname of the DUT's Chameleon. 913 """ 914 host_parts = dut_hostname.split('.') 915 host_parts[0] = host_parts[0] + '-chameleon' 916 return '.'.join(host_parts) 917 918 919def create_chameleon_board(dut_hostname, args): 920 """Given either DUT's hostname or argments, creates a ChameleonBoard object. 921 922 If the DUT's hostname is in the lab zone, it connects to the Chameleon by 923 append the hostname with '-chameleon' suffix. If not, checks if the args 924 contains the key-value pair 'chameleon_host=IP'. 925 926 @param dut_hostname: Hostname of a DUT. 927 @param args: A string of arguments passed from the command line. 928 929 @return A ChameleonBoard object. 930 931 @raise ChameleonConnectionError if unknown hostname. 932 """ 933 connection = None 934 hostname = make_chameleon_hostname(dut_hostname) 935 if utils.host_is_in_lab_zone(hostname): 936 connection = ChameleonConnection(hostname) 937 else: 938 args_dict = utils.args_to_dict(args) 939 hostname = args_dict.get('chameleon_host', None) 940 port = args_dict.get('chameleon_port', CHAMELEON_PORT) 941 if hostname: 942 connection = ChameleonConnection(hostname, port) 943 else: 944 raise ChameleonConnectionError('No chameleon_host is given in args') 945 946 return ChameleonBoard(connection) 947