1 # Copyright 2017 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 5 from __future__ import division 6 7 import base64 8 import logging 9 import os 10 import subprocess 11 import tempfile 12 import time 13 14 from autotest_lib.client.bin import test, utils 15 from autotest_lib.client.common_lib import error, file_utils 16 from autotest_lib.client.common_lib import utils as common_utils 17 from autotest_lib.client.common_lib.cros import chrome 18 from autotest_lib.client.cros.video import constants 19 from autotest_lib.client.cros.video import device_capability 20 from autotest_lib.client.cros.video import helper_logger 21 from autotest_lib.client.cros.video import histogram_verifier 22 23 DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com/' 24 'chromiumos-test-assets-public/crowd/') 25 26 FAKE_FILE_ARG = '--use-file-for-fake-video-capture="%s"' 27 FAKE_STREAM_ARG = '--use-fake-device-for-media-stream="fps=%d"' 28 EXTRA_BROWSER_ARGS = ['--use-fake-ui-for-media-stream', 29 '--enable-experimental-web-platform-features'] 30 DISABLE_HW_ENCODE_ARGS = ['--disable-accelerated-video-encode'] 31 JS_INVOCATION = 'startRecording("%s");' 32 JS_VIDEO_BUFFER = 'videoBuffer' 33 JS_VIDEO_BUFFER_SIZE = 'videoBufferSize' 34 TEST_PROGRESS = 'testProgress' 35 ELAPSED_TIME = 'elapsedTime' 36 LOOPBACK_PAGE = 'loopback_mediarecorder.html' 37 TMP_VIDEO_FILE = '/tmp/test_video.webm' 38 RECORDING_TIMEOUT = 30 39 WAIT_FOR_IDLE_CPU_TIMEOUT = 10.0 40 CPU_IDLE_USAGE = 0.1 41 42 STABILIZATION_DURATION = 3 43 MEASUREMENT_DURATION = 10 44 45 FRAME_PROCESSING_TIME = 'frame_processing_time' 46 CPU_USAGE = 'cpu_usage' 47 HW_PREFIX = 'hw_' 48 SW_PREFIX = 'sw_' 49 PERCENT = 'percent' 50 TIME_UNIT = 'millisecond' 51 52 53 class video_MediaRecorderPerf(test.test): 54 """This test measures the performance of MediaRecorder.""" 55 version = 1 56 57 def get_mkv_result_listener(self): 58 # We want to import mkvparse in a method instead of in the global scope 59 # because when emerging the test, global import of mkvparse would fail 60 # since host environment does not have the path to mkvparse library set 61 # up. 62 import mkvparse 63 class MatroskaResultListener(mkvparse.MatroskaHandler): 64 def __init__(self): 65 self.all_timestamps_ = set() 66 self.video_track_num_ = 0 67 68 def tracks_available(self): 69 for k in self.tracks: 70 t = self.tracks[k] 71 if t['type'] == 'video': 72 self.video_track_num_ = t['TrackNumber'][1] 73 break 74 75 def frame(self, track_id, timestamp, data, more_laced_frames, duration, 76 keyframe, invisible, discardable): 77 if track_id == self.video_track_num_: 78 timestamp = round(timestamp, 6) 79 self.all_timestamps_.add(timestamp) 80 81 def get_num_frames(self): 82 return len(self.all_timestamps_) 83 84 return MatroskaResultListener() 85 86 def video_record_completed(self): 87 """Checks if MediaRecorder has recorded videos. 88 89 @returns True if videos have been recorded, False otherwise. 90 """ 91 def video_recorded(): 92 """Check the testProgress variable in HTML page.""" 93 94 # Wait for TEST_PROGRESS appearing on web page. 95 test_progress = self.tab.EvaluateJavaScript(TEST_PROGRESS) 96 return test_progress == 1 97 98 try: 99 common_utils.poll_for_condition( 100 video_recorded, timeout=RECORDING_TIMEOUT, 101 exception=error.TestError('Cannot find testProgress.'), 102 sleep_interval=1) 103 except error.TestError: 104 return False 105 else: 106 return True 107 108 def compute_cpu_usage(self): 109 time.sleep(STABILIZATION_DURATION) 110 cpu_usage_start = utils.get_cpu_usage() 111 time.sleep(MEASUREMENT_DURATION) 112 cpu_usage_end = utils.get_cpu_usage() 113 return utils.compute_active_cpu_time(cpu_usage_start, 114 cpu_usage_end) * 100 115 116 def wait_for_idle_cpu(self): 117 if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT, 118 CPU_IDLE_USAGE): 119 logging.warning('Could not get idle CPU post login.') 120 if not utils.wait_for_cool_machine(): 121 logging.warning('Could not get cold machine post login.') 122 123 def get_record_performance(self, cr, codec, is_hw_encode): 124 histogram_differ = histogram_verifier.HistogramDiffer( 125 cr, constants.MEDIA_RECORDER_VEA_USED_HISTOGRAM) 126 self.wait_for_idle_cpu() 127 cr.browser.platform.SetHTTPServerDirectories(self.bindir) 128 self.tab = cr.browser.tabs.New() 129 self.tab.Navigate(cr.browser.platform.http_server.UrlOf( 130 os.path.join(self.bindir, LOOPBACK_PAGE))) 131 self.tab.WaitForDocumentReadyStateToBeComplete() 132 self.tab.EvaluateJavaScript(JS_INVOCATION % codec) 133 cpu_usage = self.compute_cpu_usage() 134 if not self.video_record_completed(): 135 raise error.TestFail('Video record did not complete') 136 137 histogram_verifier.expect_sole_bucket( 138 histogram_differ, 139 constants.MEDIA_RECORDER_VEA_USED_BUCKET if is_hw_encode else 140 constants.MEDIA_RECORDER_VEA_NOT_USED_BUCKET, 141 'VEA used (1)' if is_hw_encode else 'VEA not used (0)') 142 143 video_buffer = self.tab.EvaluateJavaScript(JS_VIDEO_BUFFER) 144 elapsed_time = self.tab.EvaluateJavaScript(ELAPSED_TIME) 145 video_buffer_size = self.tab.EvaluateJavaScript(JS_VIDEO_BUFFER_SIZE) 146 video_buffer = video_buffer.encode('ascii', 'ignore') 147 if video_buffer_size != len(video_buffer): 148 raise error.TestFail('Video buffer from JS is truncated: Was %d.\ 149 Got %d' % (video_buffer_size, len(video_buffer))) 150 151 import mkvparse 152 video_byte_array = bytearray(base64.b64decode(video_buffer)) 153 mkv_listener = self.get_mkv_result_listener() 154 with tempfile.TemporaryFile() as video_file: 155 video_file.write(video_byte_array) 156 video_file.seek(0) 157 mkvparse.mkvparse(video_file, mkv_listener) 158 logging.info('(%s) Number of frames: %d time: %d', 159 'hw' if is_hw_encode else 'sw', 160 mkv_listener.get_num_frames(), 161 elapsed_time) 162 return (elapsed_time / mkv_listener.get_num_frames(), cpu_usage) 163 164 @helper_logger.video_log_wrapper 165 def run_once(self, codec, fps, video_file, capability): 166 """ Report cpu usage and frame processing time with HW and SW encode. 167 168 Use MediaRecorder to record a videos with HW encode and SW encode, and 169 report the frame processing time and CPU usage of both. 170 171 @param codec: a string indicating the codec used to encode video stream, 172 ex. 'h264'. 173 @param fps: an integer specifying FPS of the fake input video stream. 174 @param video_file: a string specifying the name of the video file to be 175 used as fake input video stream. 176 @param capability: The capability required for running this test. 177 """ 178 device_capability.DeviceCapability().ensure_capability(capability) 179 180 # Download test video. 181 url = DOWNLOAD_BASE + video_file 182 local_path = os.path.join(self.bindir, video_file) 183 file_utils.download_file(url, local_path) 184 fake_file_arg = (FAKE_FILE_ARG % local_path) 185 fake_stream_arg = (FAKE_STREAM_ARG % fps) 186 187 processing_time_sw = 0 188 processing_time_hw = 0 189 cpu_usage_sw = 0 190 cpu_usage_hw = 0 191 with chrome.Chrome( 192 extra_browser_args=EXTRA_BROWSER_ARGS + 193 [fake_file_arg, fake_stream_arg] + 194 DISABLE_HW_ENCODE_ARGS + 195 [helper_logger.chrome_vmodule_flag()], 196 init_network_controller=True) as cr: 197 (processing_time_sw, cpu_usage_sw) = self.get_record_performance( 198 cr, codec, False) 199 self.output_perf_value(description=(SW_PREFIX + FRAME_PROCESSING_TIME), 200 value=processing_time_sw, units=TIME_UNIT, higher_is_better=False) 201 self.output_perf_value(description=(SW_PREFIX + CPU_USAGE), 202 value=cpu_usage_sw, units=PERCENT, higher_is_better=False) 203 204 with chrome.Chrome( 205 extra_browser_args=EXTRA_BROWSER_ARGS + 206 [fake_file_arg, fake_stream_arg] + 207 [helper_logger.chrome_vmodule_flag()], 208 init_network_controller=True) as cr: 209 (processing_time_hw, cpu_usage_hw) = self.get_record_performance( 210 cr, codec, True) 211 self.output_perf_value(description=(HW_PREFIX + FRAME_PROCESSING_TIME), 212 value=processing_time_hw, units=TIME_UNIT, higher_is_better=False) 213 self.output_perf_value(description=(HW_PREFIX + CPU_USAGE), 214 value=cpu_usage_hw, units=PERCENT, higher_is_better=False) 215 216 log = 'sw processing_time=%f cpu=%d hw processing_time=%f cpu=%d' % ( 217 processing_time_sw, 218 cpu_usage_sw, 219 processing_time_hw, 220 cpu_usage_hw) 221 logging.info(log) 222