| Index: build/android/devil/android/tools/video_recorder.py
|
| diff --git a/build/android/devil/android/tools/video_recorder.py b/build/android/devil/android/tools/video_recorder.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..685b682ea866c952f3d563ff389d9a0059324438
|
| --- /dev/null
|
| +++ b/build/android/devil/android/tools/video_recorder.py
|
| @@ -0,0 +1,173 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Captures a video from an Android device."""
|
| +
|
| +import argparse
|
| +import logging
|
| +import os
|
| +import threading
|
| +import time
|
| +import sys
|
| +
|
| +if __name__ == '__main__':
|
| + sys.path.append(os.path.abspath(os.path.join(
|
| + os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)))
|
| +from devil.android import device_signal
|
| +from devil.android import device_utils
|
| +from devil.android.tools import script_common
|
| +from devil.utils import cmd_helper
|
| +from devil.utils import reraiser_thread
|
| +from devil.utils import timeout_retry
|
| +
|
| +
|
| +class VideoRecorder(object):
|
| + """Records a screen capture video from an Android Device (KitKat or newer)."""
|
| +
|
| + def __init__(self, device, megabits_per_second=4, size=None,
|
| + rotate=False):
|
| + """Creates a VideoRecorder instance.
|
| +
|
| + Args:
|
| + device: DeviceUtils instance.
|
| + host_file: Path to the video file to store on the host.
|
| + megabits_per_second: Video bitrate in megabits per second. Allowed range
|
| + from 0.1 to 100 mbps.
|
| + size: Video frame size tuple (width, height) or None to use the device
|
| + default.
|
| + rotate: If True, the video will be rotated 90 degrees.
|
| + """
|
| + self._bit_rate = megabits_per_second * 1000 * 1000
|
| + self._device = device
|
| + self._device_file = (
|
| + '%s/screen-recording.mp4' % device.GetExternalStoragePath())
|
| + self._recorder_thread = None
|
| + self._rotate = rotate
|
| + self._size = size
|
| + self._started = threading.Event()
|
| +
|
| + def __enter__(self):
|
| + self.Start()
|
| +
|
| + def Start(self, timeout=None):
|
| + """Start recording video."""
|
| + def screenrecord_started():
|
| + return bool(self._device.GetPids('screenrecord'))
|
| +
|
| + if screenrecord_started():
|
| + raise Exception("Can't run multiple concurrent video captures.")
|
| +
|
| + self._started.clear()
|
| + self._recorder_thread = reraiser_thread.ReraiserThread(self._Record)
|
| + self._recorder_thread.start()
|
| + timeout_retry.WaitFor(
|
| + screenrecord_started, wait_period=1, max_tries=timeout)
|
| + self._started.wait(timeout)
|
| +
|
| + def _Record(self):
|
| + cmd = ['screenrecord', '--verbose', '--bit-rate', str(self._bit_rate)]
|
| + if self._rotate:
|
| + cmd += ['--rotate']
|
| + if self._size:
|
| + cmd += ['--size', '%dx%d' % self._size]
|
| + cmd += [self._device_file]
|
| + for line in self._device.adb.IterShell(
|
| + ' '.join(cmd_helper.SingleQuote(i) for i in cmd), None):
|
| + if line.startswith('Content area is '):
|
| + self._started.set()
|
| +
|
| + def __exit__(self, _exc_type, _exc_value, _traceback):
|
| + self.Stop()
|
| +
|
| + def Stop(self):
|
| + """Stop recording video."""
|
| + if not self._device.KillAll('screenrecord', signum=device_signal.SIGINT,
|
| + quiet=True):
|
| + logging.warning('Nothing to kill: screenrecord was not running')
|
| + self._recorder_thread.join()
|
| +
|
| + def Pull(self, host_file=None):
|
| + """Pull resulting video file from the device.
|
| +
|
| + Args:
|
| + host_file: Path to the video file to store on the host.
|
| + Returns:
|
| + Output video file name on the host.
|
| + """
|
| + # TODO(jbudorick): Merge filename generation with the logic for doing so in
|
| + # DeviceUtils.
|
| + host_file_name = (
|
| + host_file
|
| + or 'screen-recording-%s-%s.mp4' % (
|
| + str(self._device),
|
| + time.strftime('%Y%m%dT%H%M%S', time.localtime())))
|
| + host_file_name = os.path.abspath(host_file_name)
|
| + self._device.PullFile(self._device_file, host_file_name)
|
| + self._device.RunShellCommand('rm -f "%s"' % self._device_file)
|
| + return host_file_name
|
| +
|
| +
|
| +def main():
|
| + # Parse options.
|
| + parser = argparse.ArgumentParser(description=__doc__)
|
| + parser.add_argument('-d', '--device', dest='devices', action='append',
|
| + help='Serial number of Android device to use.')
|
| + parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
|
| + parser.add_argument('-f', '--file', metavar='FILE',
|
| + help='Save result to file instead of generating a '
|
| + 'timestamped file name.')
|
| + parser.add_argument('-v', '--verbose', action='store_true',
|
| + help='Verbose logging.')
|
| + parser.add_argument('-b', '--bitrate', default=4, type=float,
|
| + help='Bitrate in megabits/s, from 0.1 to 100 mbps, '
|
| + '%default mbps by default.')
|
| + parser.add_argument('-r', '--rotate', action='store_true',
|
| + help='Rotate video by 90 degrees.')
|
| + parser.add_argument('-s', '--size', metavar='WIDTHxHEIGHT',
|
| + help='Frame size to use instead of the device '
|
| + 'screen size.')
|
| + parser.add_argument('host_file', nargs='?',
|
| + help='File to which the video capture will be written.')
|
| +
|
| + args = parser.parse_args()
|
| +
|
| + host_file = args.host_file or args.file
|
| +
|
| + if args.verbose:
|
| + logging.getLogger().setLevel(logging.DEBUG)
|
| +
|
| + size = (tuple(int(i) for i in args.size.split('x'))
|
| + if args.size
|
| + else None)
|
| +
|
| + def record_video(device, stop_recording):
|
| + recorder = VideoRecorder(
|
| + device, megabits_per_second=args.bitrate, size=size, rotate=args.rotate)
|
| + with recorder:
|
| + stop_recording.wait()
|
| +
|
| + f = None
|
| + if host_file:
|
| + root, ext = os.path.splitext(host_file)
|
| + f = '%s_%s%s' % (root, str(device), ext)
|
| + f = recorder.Pull(f)
|
| + print 'Video written to %s' % os.path.abspath(f)
|
| +
|
| + parallel_devices = device_utils.DeviceUtils.parallel(
|
| + script_common.GetDevices(args.devices, args.blacklist_file),
|
| + async=True)
|
| + stop_recording = threading.Event()
|
| + running_recording = parallel_devices.pMap(record_video, stop_recording)
|
| + print 'Recording. Press Enter to stop.',
|
| + sys.stdout.flush()
|
| + raw_input()
|
| + stop_recording.set()
|
| +
|
| + running_recording.pGet(None)
|
| + return 0
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|