Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(961)

Unified Diff: webrtc/tools/run_video_analysis.py

Issue 2704113004: Add video recording wrapper (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webrtc/tools/run_video_analysis.py
diff --git a/webrtc/tools/run_video_analysis.py b/webrtc/tools/run_video_analysis.py
new file mode 100755
index 0000000000000000000000000000000000000000..a1376b896ffc69718d80d1bdeac58ea57da6d1d1
--- /dev/null
+++ b/webrtc/tools/run_video_analysis.py
@@ -0,0 +1,464 @@
+#!/usr/bin/env python
+# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+import optparse
+import os
+import subprocess
+import sys
+import time
+import glob
+import re
+
+# Used to time-stamp output files and directories
+CURRENT_TIME = time.strftime("%d_%m_%Y-%H:%M:%S")
+
+def _parseArgs():
kjellander_webrtc 2017/02/22 12:30:51 Chromium has two exceptions from the standard goog
janssonWebRTC 2017/03/07 14:23:45 In my opinion the linter should catch these things
kjellander_webrtc 2017/03/08 07:31:21 You're right, I filed https://bugs.chromium.org/p/
+ """Registers the command-line options."""
+ usage = 'usage: %prog [options]'
+ parser = optparse.OptionParser(usage=usage)
+
+ parser.add_option('--frame_width', type='string', default='1280',
+ help='Width of the recording. Default: %default')
+ parser.add_option('--frame_height', type='string', default='720',
+ help='Height of the recording. Default: %default')
+ parser.add_option('--framerate', type='string', default='60',
+ help='Recording framerate. Default: %default')
+ parser.add_option('--ref_duration', type='string', default='20',
+ help='Reference recording duration. Default: %default')
+ parser.add_option('--test_duration', type='string', default='10',
+ help='Test recording duration. Default: %default')
+ parser.add_option('--ref_video_device', type='string', default='/dev/video0',
+ help='Reference recording device. Default: %default')
+ parser.add_option('--test_video_device', type='string', default='/dev/video1',
+ help='Test recording device. Default: %default')
+ parser.add_option('--app_name', type='string',
+ help='Name of the app under test.')
+ parser.add_option('--recording_api', type='string', default='Video4Linux2',
+ help='Recording API to use. Default: %default')
+ parser.add_option('--pixel_format', type='string', default='yuv420p',
+ help='Recording pixel format Default: %default')
+ parser.add_option('--ref_ffmpeg', type='string',
kjellander_webrtc 2017/02/22 12:30:50 When do you need two different ffmpeg binaries? Th
janssonWebRTC 2017/03/07 14:23:45 Initially I could not run the same executable at t
kjellander_webrtc 2017/03/08 07:31:20 Acknowledged.
+ help='Path to the ffmpeg executable for the reference \
+ device.')
+ parser.add_option('--test_ffmpeg', type='string',
+ help='Path to the ffmpeg executable for the test device.')
+ parser.add_option('--video_container', type='string', default='yuv',
+ help='Video container for the recordings. \
+ Default: %default')
+ parser.add_option('--compare_videos_script', type='string',
+ default='compare_videos.py',
+ help='Path to script used to compare and generate metrics. \
+ Default: %default')
+ parser.add_option('--frame_analyzer', type='string',
+ default='../../out/Default/frame_analyzer',
+ help='Path to the frame analyzer executable. \
+ Default: %default')
+ parser.add_option('--zxing_path', type='string',
+ help='Path to the zebra xing barcode analyzer.')
+ parser.add_option('--ref_crop_width', type='string', default='950',
+ help='Cropping width. \
+ Default: %default')
+ parser.add_option('--ref_crop_height', type='string', default='420',
+ help='Cropping height. \
+ Default: %default')
+ parser.add_option('--ref_crop_x_axis_start', type='string', default='130',
+ help='y coordinate where cropping starts from. \
+ Default: %default')
+ parser.add_option('--ref_crop_y_axis_start', type='string', default='56',
+ help='x coordinate where cropping starts from. \
+ Default: %default')
+ parser.add_option('--test_crop_width', type='string', default='950',
+ help='Cropping width. \
+ Default: %default')
+ parser.add_option('--test_crop_height', type='string', default='420',
+ help='Cropping height. \
+ Default: %default')
+ parser.add_option('--test_crop_x_axis_start', type='string', default='130',
+ help='y coordinate where cropping starts from. \
+ Default: %default')
+ parser.add_option('--test_crop_y_axis_start', type='string', default='56',
+ help='x coordinate where cropping starts from. \
+ Default: %default')
+ parser.add_option('--ref_rec_dir', type='string', default='ref',
+ help='Path to where reference recordings will be created. \
+ Ideally keep the ref and test directories on separate \
+ drives. Default: %default')
+ parser.add_option('--test_rec_dir', type='string', default='test',
+ help='Path to where test recordings will be created. \
+ Ideally keep the ref and test directories on separate \
+ drives. Default: %default')
+
+ options, _ = parser.parse_args()
+
+ if not options.app_name:
+ parser.error('You must provide an application name!')
+
+ if not options.ref_ffmpeg:
+ parser.error('You most provide location for the reference device ffmpeg \
+ executable.')
+ if not os.path.exists(options.ref_ffmpeg):
kjellander_webrtc 2017/02/22 12:30:50 I usually use os.path.isfile instead, since if you
janssonWebRTC 2017/03/07 14:23:45 Done.
+ parser.error('Cannot find the reference device ffmpeg executable.')
+
+ if not options.test_ffmpeg:
+ parser.error('You most provide location for the test device ffmpeg \
kjellander_webrtc 2017/02/22 12:30:50 Skip the \, it's not needed if you end the string
janssonWebRTC 2017/03/07 14:23:45 Done.
+ executable.')
+ if not os.path.exists(options.test_ffmpeg):
+ parser.error('Cannot find the test device ffmpeg executable.')
+
+ # compare_videos.py dependencies.
+ if not os.path.exists(options.compare_videos_script):
+ parser.warning('Cannot find compare_videos.py script, no metrics will be \
+ generated!')
kjellander_webrtc 2017/02/22 12:30:51 align indent with the above ( https://google.githu
janssonWebRTC 2017/03/07 14:23:45 Done.
+ if not os.path.exists(options.frame_analyzer):
+ parser.warning('Cannot find frame_analyzer, no metrics will be generated!')
+ if not os.path.exists(options.frame_analyzer):
kjellander_webrtc 2017/02/22 12:30:50 options.zxing_path
janssonWebRTC 2017/03/07 14:23:45 Done.
+ parser.warning('Cannot find Zebra Xing, no metrics will be generated!')
+
+ return options
+
+
+def create_recording_dirs(options):
+ """Creates root directories for reference and test recordings and
kjellander_webrtc 2017/02/22 12:30:49 Make the summary line fit on a single line Quotin
janssonWebRTC 2017/03/07 14:23:45 Done.
+ sub-directories using <options.app_name + '_' + CURRENT_TIME>.
+
+ Args:
+ options(object): Contains all the provided command line options.
+ Return:
+ record_paths(dict): key: value pair with reference and test file
+ absolute paths.
kjellander_webrtc 2017/02/22 12:30:50 +4 spaces indent before "absolute" here.
janssonWebRTC 2017/03/07 14:23:45 Done.
+ """
+ # Create root directories for the video recordings.
+ if not os.path.exists(options.ref_rec_dir):
kjellander_webrtc 2017/02/22 12:30:50 Use os.path.isdir
janssonWebRTC 2017/03/07 14:23:44 Done.
+ os.makedirs(options.ref_rec_dir)
+ if not os.path.exists(options.test_rec_dir):
+ os.makedirs(options.test_rec_dir)
+ # Create and time-stamp directories for all the output files.
kjellander_webrtc 2017/02/22 12:30:50 +1 blank line before comment
janssonWebRTC 2017/03/07 14:23:44 Done.
+ ref_rec_dir = os.path.join(options.ref_rec_dir, options.app_name + '_' + \
+ CURRENT_TIME)
+ test_rec_dir = os.path.join(options.test_rec_dir, options.app_name + '_' + \
+ CURRENT_TIME)
+
+ os.makedirs(ref_rec_dir)
+ os.makedirs(test_rec_dir)
+
+ record_paths = {
+ 'ref_rec_location' : os.path.abspath(ref_rec_dir),
+ 'test_rec_location' : os.path.abspath(test_rec_dir)
+ }
+
+ return record_paths
+
+
+def restart_magewell_devices(options):
+ """Tries to find the provided options.ref_video_device and
kjellander_webrtc 2017/02/22 12:30:50 Same comment here about the docstring summary line
janssonWebRTC 2017/03/07 14:23:45 Done.
+ options.test_video_device devices which use video4linux and then do a soft
+ reset by using USB unbind and bind. This is due to Magewell capture devices
+ have proven to be unstable after the first recording attempt.
+
+ Args:
+ options(object): Contains all the provided command line options.
+ """
+ # Get the dev/videoN device name from the command line arguments.
+ ref_magewell = options.ref_video_device.split('/')[2]
+ test_magewell = options.test_video_device.split('/')[2]
+
+ # Find the device location including USB and USB Bus ID's.
+ ref_magewell_device = \
+ glob.glob('/sys/bus/usb/devices/usb*/**/**/video4linux/' + ref_magewell)
+ test_magewell_device = \
+ glob.glob('/sys/bus/usb/devices/usb*/**/**/video4linux/' + test_magewell)
+
+ magewell_usb_ports = []
+ # Figure out the USB bus and port ID for each device.
+ ref_magewell_path = str(ref_magewell_device).split('/')
+ for directory in ref_magewell_path:
+ # Find the folder with pattern "N-N", e.g. "4-3" or \
+ # "[USB bus ID]-[USB port]"
+ if re.match(r'^\d-\d$', directory):
+ magewell_usb_ports.append(directory)
+
+ test_magewell_path = str(test_magewell_device).split('/')
+ for directory in test_magewell_path:
+ # Find the folder with pattern "N-N", e.g. "4-3" or \
+ # "[USB bus ID]-[USB port]"
+ if re.match(r'^\d-\d$', directory):
+ magewell_usb_ports.append(directory)
+
+ print '\nResetting USB ports where magewell devices are connected...'
+ # Use the USB bus and port ID (e.g. 4-3) to unbind and bind the USB devices
+ # (i.e. soft eject and insert).
+ try:
+ for usb_port in magewell_usb_ports:
+ echo_cmd = ['echo', usb_port]
+ unbind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/unbind']
+ bind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/bind']
kjellander_webrtc 2017/02/22 12:30:50 Will the script ask for sudo password in a clear w
janssonWebRTC 2017/03/07 14:23:45 Yes it will but for me it's quite clear that it as
kjellander_webrtc 2017/03/08 07:31:20 Acknowledged.
+
+ # TODO(jansson) Figure out a way to call on echo once for bind & unbind
+ # if possible.
+ echo_unbind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
+ unbind = subprocess.Popen(unbind_cmd, stdin=echo_unbind.stdout)
+ echo_unbind.stdout.close()
+ unbind.communicate()
+ unbind.wait()
+
+ echo_bind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
+ bind = subprocess.Popen(bind_cmd, stdin=echo_bind.stdout)
+ echo_bind.stdout.close()
+ bind.communicate()
+ bind.wait()
+ except OSError as e:
+ print 'Error while resetting magewell devices: ' + e
+ raise
+
+ print 'Reset done!\n'
+
+
+def start_recording(options, record_paths):
+ """Starts recording from the two specified video devices with
+
+ Args:
+ options(object): Contains all the provided command line options.
+ record_paths(dict): key: value pair with reference and test file
+ absolute paths.
+ """
+ ref_file_name = options.app_name + '_' + CURRENT_TIME + '_ref.' + \
kjellander_webrtc 2017/02/22 12:30:50 Use Python string formatters instead, i.e. ref_fi
janssonWebRTC 2017/03/07 14:23:44 Done.
+ options.video_container
+ ref_file_location = os.path.join(record_paths['ref_rec_location'],
+ ref_file_name)
+
+ test_file_name = options.app_name + '_' + CURRENT_TIME + '_test.' + \
+ options.video_container
+ test_file_location = os.path.join(record_paths['test_rec_location'],
+ test_file_name)
+
+ # Reference video recorder command line.
+ ref_cmd = [
+ options.ref_ffmpeg,
+ '-v', 'error',
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-framerate', options.framerate,
+ '-f', options.recording_api,
+ '-i', options.ref_video_device,
+ '-pix_fmt', options.pixel_format,
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-t', options.ref_duration,
+ '-framerate', options.framerate,
+ ref_file_location
+ ]
+
+ # Test video recorder command line.
+ test_cmd = [
+ options.test_ffmpeg,
+ '-v', 'error',
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-framerate', options.framerate,
+ '-f', options.recording_api,
+ '-i', options.test_video_device,
+ '-pix_fmt', options.pixel_format,
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-t', options.test_duration,
+ '-framerate', options.framerate,
+ test_file_location
+ ]
+ try:
+ print 'Trying to record from reference recorder...'
+ ref_recorder = subprocess.Popen(ref_cmd, stderr=None)
+ # Start the 2nd recording a little later to ensure the 1st one has started.
+ # TODO(jansson) Check that the ref_recorder output file exists rather than
+ # using sleep.
+ time.sleep(5)
kjellander_webrtc 2017/02/22 12:30:51 Maybe this should be a flag as well? Constants lik
janssonWebRTC 2017/03/07 14:23:45 Made it a flag.
kjellander_webrtc 2017/03/08 07:31:20 Acknowledged.
+ print 'Trying to record from test recorder...'
+ test_recorder = subprocess.Popen(test_cmd, stderr=None)
kjellander_webrtc 2017/02/22 12:30:50 Why stderr=None ? Don't you risk to ignore errors
janssonWebRTC 2017/03/07 14:23:45 This was just a default setting. Will fix.
kjellander_webrtc 2017/03/08 07:31:20 Acknowledged.
+ test_recorder.wait()
+ ref_recorder.wait()
+
+ print 'Ref file recorded to: ' + os.path.abspath(ref_file_location)
+ print 'Test file recorded to: ' + os.path.abspath(test_file_location)
+ print 'Recording done!\n'
+ return flip_and_crop_recordings(options, test_file_name,
+ record_paths['test_rec_location'], ref_file_name,
+ record_paths['ref_rec_location'])
+ except subprocess.CalledProcessError as e:
+ print 'Something went wrong when trying to record: ' + e
+ raise
+
+
+def flip_and_crop_recordings(options, test_file_name, test_file_location,
+ ref_file_name, ref_file_location):
kjellander_webrtc 2017/02/22 12:30:50 indent aligning with ( above
janssonWebRTC 2017/03/07 14:23:45 Done.
+ """Performs a horizontal flip of the reference video to match the test video
+ orientation and then crops the ref and test videos using
+ options.ref_crop_height, options.ref_crop_width, options.test_crop_height and
+ options.test_crop_width. Crop starting position is taken from
+ options.ref_crop_x_axis_start, options.ref_crop_y_axis_start,
+ options.test_crop_x_axis_start and options.test_crop_x_axis_start.
+
+ Args:
+ options(object): Contains all the provided command line options.
+ test_file_name(string): Name of the test video file recording.
+ test_file_location(string): Path to the test video file recording.
+ ref_file_name(string): Name of the reference video file recording.
+ ref_file_location(string): Path to the reference video file recording.
+ Return:
+ recording_files_and_time(dict): key: value pair with the path to cropped
+ test and reference video files.
kjellander_webrtc 2017/02/22 12:30:50 +4 indent
janssonWebRTC 2017/03/07 14:23:45 Done.
+ """
+ print 'Trying to crop videos...'
+ # Ref file cropping.
kjellander_webrtc 2017/02/22 12:30:50 +blank line
janssonWebRTC 2017/03/07 14:23:44 Done.
+ cropped_ref_file_name = 'cropped_' + ref_file_name
+ cropped_ref_file = os.path.abspath(\
kjellander_webrtc 2017/02/22 12:30:50 remove \
janssonWebRTC 2017/03/07 14:23:44 Done.
+ os.path.join(ref_file_location, cropped_ref_file_name))
+ ref_crop_parameters = options.ref_crop_width + ':' + \
kjellander_webrtc 2017/02/22 12:30:51 use string formatter
janssonWebRTC 2017/03/07 14:23:45 Done.
+ options.ref_crop_height + ':' + options.ref_crop_x_axis_start + ':' + \
+ options.ref_crop_y_axis_start
+
+ ref_video_crop_cmd = [
+ options.ref_ffmpeg,
+ '-v', 'error',
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-i', os.path.join(ref_file_location, ref_file_name),
+ # Flip the ref video horizontally before cropping so that we can use the
+ # same coordinates on both ref and test recordings.
+ # Also keep in mind that the X:Y starting positions are critical for the
+ # zxing library, if the barcode is moved within the recording, these
+ # probably need to be changed otherwise barcode decoding will fail.
+ # TODO(jansson) make the entire crop parameter section a command line
+ # argument.
+ '-vf', 'hflip, crop=' + ref_crop_parameters,
+ '-c:a', 'copy',
+ cropped_ref_file
+ ]
+ # Test file cropping.
+ cropped_test_file_name = 'cropped_' + test_file_name
+ cropped_test_file = os.path.abspath(\
+ os.path.join(test_file_location, cropped_test_file_name))
+ test_crop_parameters = options.test_crop_width + ':' + \
+ options.test_crop_height + ':' + options.test_crop_x_axis_start + ':' + \
+ options.test_crop_y_axis_start
+
+ test_video_crop_cmd = [
+ options.test_ffmpeg,
+ '-v', 'error',
+ '-s', options.frame_width + 'x' + options.frame_height,
+ '-i', os.path.join(test_file_location, test_file_name),
+ # TODO(jansson) make the entire crop parameter section a command line
+ # argument.
+ '-vf', 'crop=' + test_crop_parameters,
+ '-c:a', 'copy',
+ cropped_test_file
+ ]
+
+ ref_crop = subprocess.Popen(ref_video_crop_cmd)
+ ref_crop.wait()
+ print 'Ref file cropped to: ' + cropped_ref_file
+
+ try:
+ test_crop = subprocess.Popen(test_video_crop_cmd)
+ test_crop.wait()
+ print 'Test file cropped to: ' + cropped_test_file
+ print 'Cropping done!\n'
+ # Need to return these so they can be used by other parts.
+ cropped_recordings = {
+ 'cropped_test_file' : cropped_test_file,
+ 'cropped_ref_file' : cropped_ref_file
+ }
+
+ return cropped_recordings
+ except subprocess.CalledProcessError as e:
+ print 'Something went wrong during cropping: ' + e
+ raise
+
+
+def compare_videos(options, recording_result):
+ """Runs the compare_video.py script from src/webrtc/tools using the file paths
+ from recording_result and writes the output to a file named
+ <options.app_name + '_' + CURRENT_TIME + '_result.txt> in the reference video
+ recording folder taken from recording_result.
+
+ Args:
+ options(object): Contains all the provided command line options.
+ recording_files_and_time(dict): key: value pair with the path to cropped
+ test and reference video files
+ """
+ print 'Starting comparison...'
+ print 'Grab a coffee, this might take a few minutes...'
+ cropped_ref_file = recording_result['cropped_ref_file']
+ cropped_test_file = recording_result['cropped_test_file']
+ compare_videos_script = os.path.abspath(options.compare_videos_script)
+ result_file_name = \
kjellander_webrtc 2017/02/22 12:30:50 remove \ and use string formatter instead.
janssonWebRTC 2017/03/07 14:23:45 Done.
+ os.path.join(os.path.dirname(recording_result['cropped_ref_file']),
+ options.app_name + '_' + CURRENT_TIME + '_result.txt')
+
+ compare_cmd = [
+ sys.executable,
+ compare_videos_script,
+ '--ref_video', cropped_ref_file,
+ '--test_video', cropped_test_file,
+ '--frame_analyzer', os.path.abspath(options.frame_analyzer),
+ '--zxing_path', options.zxing_path,
+ '--ffmpeg_path', options.ref_ffmpeg,
+ '--stats_file_ref', os.path.join(os.path.dirname(cropped_ref_file),
+ cropped_ref_file + '_stats.txt'),
+ '--stats_file_test', os.path.join(os.path.dirname(cropped_test_file),
+ cropped_test_file + '_stats.txt'),
+ '--yuv_frame_height', options.ref_crop_height,
+ '--yuv_frame_width', options.ref_crop_width
+ ]
+
+ try:
+ with open(result_file_name, 'w') as f:
+ compare_video_recordings = subprocess.Popen(compare_cmd, stdout=f)
+ compare_video_recordings.wait()
+ print 'Result recorded to: ' + os.path.abspath(result_file_name)
+ print 'Comparison done!'
+ except subprocess.CalledProcessError as e:
+ print 'Something went wrong when trying to compare videos: ' + e
+ raise
+
+
+def main():
+ """The main function.
+
+ A simple invocation is:
+ ./run_video_analysis.py \
+ --app_name AppRTCMobile \
+ --ref_ffmpeg ./ref_ffmpeg --ref_video_device=/dev/video0 \
+ --test_ffmpeg ./test_ffmpeg --test_video_device=/dev/video1 \
+ --zxing_path ./zxing \
+ --test_crop_width 950 --test_crop_height 420 \
+ --ref_crop_width 950 --ref_crop_height 420 \
+ --test_crop_x_axis_start 130 --test_crop_y_axis_start 56 \
+ --ref_crop_x_axis_start 130 --ref_crop_y_axis_start 56 \
+ --ref_rec_dir /tmp/ref \
+ --test_rec_dir /tmp/test
+
+ This will produce the following files if successful:
+ # Original video recordings.
+ /tmp/ref/AppRTCMobile_<recording date and time>_ref.yuv
+ /tmp/test/AppRTCMobile_<recording date and time>_test.yuv
+
+ # Cropped video recordings according to the crop parameters.
+ /tmp/ref/cropped_AppRTCMobile_<recording date and time>_ref.yuv
+ /tmp/test/cropped_AppRTCMobile_<recording date and time>_ref.yuv
+
+ # Comparison metrics from cropped test and ref videos.
+ /tmp/test/AppRTCMobile_<recording date and time>_result.text
+
+ """
+ options = _parseArgs()
+ restart_magewell_devices(options)
+ record_paths = create_recording_dirs(options)
+ recording_result = start_recording(options, record_paths)
+ # Do not require compare_video.py script to run, no metrics will be generated.
+ if os.path.exists(options.frame_analyzer) or \
+ os.path.exists(options.compare_videos_script) or \
+ os.path.exists(options.zxing_path):
+ compare_videos(options, recording_result)
kjellander_webrtc 2017/02/22 12:30:50 This is a bit unexpected. I suggest you at least a
janssonWebRTC 2017/03/07 14:23:45 Done.
+
kjellander_webrtc 2017/02/22 12:30:50 +1 blank line for top-level statements.
janssonWebRTC 2017/03/07 14:23:45 Done.
+if __name__ == '__main__':
+ sys.exit(main())
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698