Home | History | Annotate | Download | only in video_MediaRecorderPerf
      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