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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3 #
4 # Use of this source code is governed by a BSD-style license
5 # that can be found in the LICENSE file in the root of the source
6 # tree. An additional intellectual property rights grant can be found
7 # in the file PATENTS. All contributing project authors may
8 # be found in the AUTHORS file in the root of the source tree.
9
10 import optparse
11 import os
12 import subprocess
13 import sys
14 import time
15 import glob
16 import re
17
18 # Used to time-stamp output files and directories
19 CURRENT_TIME = time.strftime("%d_%m_%Y-%H:%M:%S")
20
21 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/
22 """Registers the command-line options."""
23 usage = 'usage: %prog [options]'
24 parser = optparse.OptionParser(usage=usage)
25
26 parser.add_option('--frame_width', type='string', default='1280',
27 help='Width of the recording. Default: %default')
28 parser.add_option('--frame_height', type='string', default='720',
29 help='Height of the recording. Default: %default')
30 parser.add_option('--framerate', type='string', default='60',
31 help='Recording framerate. Default: %default')
32 parser.add_option('--ref_duration', type='string', default='20',
33 help='Reference recording duration. Default: %default')
34 parser.add_option('--test_duration', type='string', default='10',
35 help='Test recording duration. Default: %default')
36 parser.add_option('--ref_video_device', type='string', default='/dev/video0',
37 help='Reference recording device. Default: %default')
38 parser.add_option('--test_video_device', type='string', default='/dev/video1',
39 help='Test recording device. Default: %default')
40 parser.add_option('--app_name', type='string',
41 help='Name of the app under test.')
42 parser.add_option('--recording_api', type='string', default='Video4Linux2',
43 help='Recording API to use. Default: %default')
44 parser.add_option('--pixel_format', type='string', default='yuv420p',
45 help='Recording pixel format Default: %default')
46 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.
47 help='Path to the ffmpeg executable for the reference \
48 device.')
49 parser.add_option('--test_ffmpeg', type='string',
50 help='Path to the ffmpeg executable for the test device.')
51 parser.add_option('--video_container', type='string', default='yuv',
52 help='Video container for the recordings. \
53 Default: %default')
54 parser.add_option('--compare_videos_script', type='string',
55 default='compare_videos.py',
56 help='Path to script used to compare and generate metrics. \
57 Default: %default')
58 parser.add_option('--frame_analyzer', type='string',
59 default='../../out/Default/frame_analyzer',
60 help='Path to the frame analyzer executable. \
61 Default: %default')
62 parser.add_option('--zxing_path', type='string',
63 help='Path to the zebra xing barcode analyzer.')
64 parser.add_option('--ref_crop_width', type='string', default='950',
65 help='Cropping width. \
66 Default: %default')
67 parser.add_option('--ref_crop_height', type='string', default='420',
68 help='Cropping height. \
69 Default: %default')
70 parser.add_option('--ref_crop_x_axis_start', type='string', default='130',
71 help='y coordinate where cropping starts from. \
72 Default: %default')
73 parser.add_option('--ref_crop_y_axis_start', type='string', default='56',
74 help='x coordinate where cropping starts from. \
75 Default: %default')
76 parser.add_option('--test_crop_width', type='string', default='950',
77 help='Cropping width. \
78 Default: %default')
79 parser.add_option('--test_crop_height', type='string', default='420',
80 help='Cropping height. \
81 Default: %default')
82 parser.add_option('--test_crop_x_axis_start', type='string', default='130',
83 help='y coordinate where cropping starts from. \
84 Default: %default')
85 parser.add_option('--test_crop_y_axis_start', type='string', default='56',
86 help='x coordinate where cropping starts from. \
87 Default: %default')
88 parser.add_option('--ref_rec_dir', type='string', default='ref',
89 help='Path to where reference recordings will be created. \
90 Ideally keep the ref and test directories on separate \
91 drives. Default: %default')
92 parser.add_option('--test_rec_dir', type='string', default='test',
93 help='Path to where test recordings will be created. \
94 Ideally keep the ref and test directories on separate \
95 drives. Default: %default')
96
97 options, _ = parser.parse_args()
98
99 if not options.app_name:
100 parser.error('You must provide an application name!')
101
102 if not options.ref_ffmpeg:
103 parser.error('You most provide location for the reference device ffmpeg \
104 executable.')
105 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.
106 parser.error('Cannot find the reference device ffmpeg executable.')
107
108 if not options.test_ffmpeg:
109 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.
110 executable.')
111 if not os.path.exists(options.test_ffmpeg):
112 parser.error('Cannot find the test device ffmpeg executable.')
113
114 # compare_videos.py dependencies.
115 if not os.path.exists(options.compare_videos_script):
116 parser.warning('Cannot find compare_videos.py script, no metrics will be \
117 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.
118 if not os.path.exists(options.frame_analyzer):
119 parser.warning('Cannot find frame_analyzer, no metrics will be generated!')
120 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.
121 parser.warning('Cannot find Zebra Xing, no metrics will be generated!')
122
123 return options
124
125
126 def create_recording_dirs(options):
127 """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.
128 sub-directories using <options.app_name + '_' + CURRENT_TIME>.
129
130 Args:
131 options(object): Contains all the provided command line options.
132 Return:
133 record_paths(dict): key: value pair with reference and test file
134 absolute paths.
kjellander_webrtc 2017/02/22 12:30:50 +4 spaces indent before "absolute" here.
janssonWebRTC 2017/03/07 14:23:45 Done.
135 """
136 # Create root directories for the video recordings.
137 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.
138 os.makedirs(options.ref_rec_dir)
139 if not os.path.exists(options.test_rec_dir):
140 os.makedirs(options.test_rec_dir)
141 # 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.
142 ref_rec_dir = os.path.join(options.ref_rec_dir, options.app_name + '_' + \
143 CURRENT_TIME)
144 test_rec_dir = os.path.join(options.test_rec_dir, options.app_name + '_' + \
145 CURRENT_TIME)
146
147 os.makedirs(ref_rec_dir)
148 os.makedirs(test_rec_dir)
149
150 record_paths = {
151 'ref_rec_location' : os.path.abspath(ref_rec_dir),
152 'test_rec_location' : os.path.abspath(test_rec_dir)
153 }
154
155 return record_paths
156
157
158 def restart_magewell_devices(options):
159 """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.
160 options.test_video_device devices which use video4linux and then do a soft
161 reset by using USB unbind and bind. This is due to Magewell capture devices
162 have proven to be unstable after the first recording attempt.
163
164 Args:
165 options(object): Contains all the provided command line options.
166 """
167 # Get the dev/videoN device name from the command line arguments.
168 ref_magewell = options.ref_video_device.split('/')[2]
169 test_magewell = options.test_video_device.split('/')[2]
170
171 # Find the device location including USB and USB Bus ID's.
172 ref_magewell_device = \
173 glob.glob('/sys/bus/usb/devices/usb*/**/**/video4linux/' + ref_magewell)
174 test_magewell_device = \
175 glob.glob('/sys/bus/usb/devices/usb*/**/**/video4linux/' + test_magewell)
176
177 magewell_usb_ports = []
178 # Figure out the USB bus and port ID for each device.
179 ref_magewell_path = str(ref_magewell_device).split('/')
180 for directory in ref_magewell_path:
181 # Find the folder with pattern "N-N", e.g. "4-3" or \
182 # "[USB bus ID]-[USB port]"
183 if re.match(r'^\d-\d$', directory):
184 magewell_usb_ports.append(directory)
185
186 test_magewell_path = str(test_magewell_device).split('/')
187 for directory in test_magewell_path:
188 # Find the folder with pattern "N-N", e.g. "4-3" or \
189 # "[USB bus ID]-[USB port]"
190 if re.match(r'^\d-\d$', directory):
191 magewell_usb_ports.append(directory)
192
193 print '\nResetting USB ports where magewell devices are connected...'
194 # Use the USB bus and port ID (e.g. 4-3) to unbind and bind the USB devices
195 # (i.e. soft eject and insert).
196 try:
197 for usb_port in magewell_usb_ports:
198 echo_cmd = ['echo', usb_port]
199 unbind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/unbind']
200 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.
201
202 # TODO(jansson) Figure out a way to call on echo once for bind & unbind
203 # if possible.
204 echo_unbind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
205 unbind = subprocess.Popen(unbind_cmd, stdin=echo_unbind.stdout)
206 echo_unbind.stdout.close()
207 unbind.communicate()
208 unbind.wait()
209
210 echo_bind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
211 bind = subprocess.Popen(bind_cmd, stdin=echo_bind.stdout)
212 echo_bind.stdout.close()
213 bind.communicate()
214 bind.wait()
215 except OSError as e:
216 print 'Error while resetting magewell devices: ' + e
217 raise
218
219 print 'Reset done!\n'
220
221
222 def start_recording(options, record_paths):
223 """Starts recording from the two specified video devices with
224
225 Args:
226 options(object): Contains all the provided command line options.
227 record_paths(dict): key: value pair with reference and test file
228 absolute paths.
229 """
230 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.
231 options.video_container
232 ref_file_location = os.path.join(record_paths['ref_rec_location'],
233 ref_file_name)
234
235 test_file_name = options.app_name + '_' + CURRENT_TIME + '_test.' + \
236 options.video_container
237 test_file_location = os.path.join(record_paths['test_rec_location'],
238 test_file_name)
239
240 # Reference video recorder command line.
241 ref_cmd = [
242 options.ref_ffmpeg,
243 '-v', 'error',
244 '-s', options.frame_width + 'x' + options.frame_height,
245 '-framerate', options.framerate,
246 '-f', options.recording_api,
247 '-i', options.ref_video_device,
248 '-pix_fmt', options.pixel_format,
249 '-s', options.frame_width + 'x' + options.frame_height,
250 '-t', options.ref_duration,
251 '-framerate', options.framerate,
252 ref_file_location
253 ]
254
255 # Test video recorder command line.
256 test_cmd = [
257 options.test_ffmpeg,
258 '-v', 'error',
259 '-s', options.frame_width + 'x' + options.frame_height,
260 '-framerate', options.framerate,
261 '-f', options.recording_api,
262 '-i', options.test_video_device,
263 '-pix_fmt', options.pixel_format,
264 '-s', options.frame_width + 'x' + options.frame_height,
265 '-t', options.test_duration,
266 '-framerate', options.framerate,
267 test_file_location
268 ]
269 try:
270 print 'Trying to record from reference recorder...'
271 ref_recorder = subprocess.Popen(ref_cmd, stderr=None)
272 # Start the 2nd recording a little later to ensure the 1st one has started.
273 # TODO(jansson) Check that the ref_recorder output file exists rather than
274 # using sleep.
275 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.
276 print 'Trying to record from test recorder...'
277 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.
278 test_recorder.wait()
279 ref_recorder.wait()
280
281 print 'Ref file recorded to: ' + os.path.abspath(ref_file_location)
282 print 'Test file recorded to: ' + os.path.abspath(test_file_location)
283 print 'Recording done!\n'
284 return flip_and_crop_recordings(options, test_file_name,
285 record_paths['test_rec_location'], ref_file_name,
286 record_paths['ref_rec_location'])
287 except subprocess.CalledProcessError as e:
288 print 'Something went wrong when trying to record: ' + e
289 raise
290
291
292 def flip_and_crop_recordings(options, test_file_name, test_file_location,
293 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.
294 """Performs a horizontal flip of the reference video to match the test video
295 orientation and then crops the ref and test videos using
296 options.ref_crop_height, options.ref_crop_width, options.test_crop_height and
297 options.test_crop_width. Crop starting position is taken from
298 options.ref_crop_x_axis_start, options.ref_crop_y_axis_start,
299 options.test_crop_x_axis_start and options.test_crop_x_axis_start.
300
301 Args:
302 options(object): Contains all the provided command line options.
303 test_file_name(string): Name of the test video file recording.
304 test_file_location(string): Path to the test video file recording.
305 ref_file_name(string): Name of the reference video file recording.
306 ref_file_location(string): Path to the reference video file recording.
307 Return:
308 recording_files_and_time(dict): key: value pair with the path to cropped
309 test and reference video files.
kjellander_webrtc 2017/02/22 12:30:50 +4 indent
janssonWebRTC 2017/03/07 14:23:45 Done.
310 """
311 print 'Trying to crop videos...'
312 # Ref file cropping.
kjellander_webrtc 2017/02/22 12:30:50 +blank line
janssonWebRTC 2017/03/07 14:23:44 Done.
313 cropped_ref_file_name = 'cropped_' + ref_file_name
314 cropped_ref_file = os.path.abspath(\
kjellander_webrtc 2017/02/22 12:30:50 remove \
janssonWebRTC 2017/03/07 14:23:44 Done.
315 os.path.join(ref_file_location, cropped_ref_file_name))
316 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.
317 options.ref_crop_height + ':' + options.ref_crop_x_axis_start + ':' + \
318 options.ref_crop_y_axis_start
319
320 ref_video_crop_cmd = [
321 options.ref_ffmpeg,
322 '-v', 'error',
323 '-s', options.frame_width + 'x' + options.frame_height,
324 '-i', os.path.join(ref_file_location, ref_file_name),
325 # Flip the ref video horizontally before cropping so that we can use the
326 # same coordinates on both ref and test recordings.
327 # Also keep in mind that the X:Y starting positions are critical for the
328 # zxing library, if the barcode is moved within the recording, these
329 # probably need to be changed otherwise barcode decoding will fail.
330 # TODO(jansson) make the entire crop parameter section a command line
331 # argument.
332 '-vf', 'hflip, crop=' + ref_crop_parameters,
333 '-c:a', 'copy',
334 cropped_ref_file
335 ]
336 # Test file cropping.
337 cropped_test_file_name = 'cropped_' + test_file_name
338 cropped_test_file = os.path.abspath(\
339 os.path.join(test_file_location, cropped_test_file_name))
340 test_crop_parameters = options.test_crop_width + ':' + \
341 options.test_crop_height + ':' + options.test_crop_x_axis_start + ':' + \
342 options.test_crop_y_axis_start
343
344 test_video_crop_cmd = [
345 options.test_ffmpeg,
346 '-v', 'error',
347 '-s', options.frame_width + 'x' + options.frame_height,
348 '-i', os.path.join(test_file_location, test_file_name),
349 # TODO(jansson) make the entire crop parameter section a command line
350 # argument.
351 '-vf', 'crop=' + test_crop_parameters,
352 '-c:a', 'copy',
353 cropped_test_file
354 ]
355
356 ref_crop = subprocess.Popen(ref_video_crop_cmd)
357 ref_crop.wait()
358 print 'Ref file cropped to: ' + cropped_ref_file
359
360 try:
361 test_crop = subprocess.Popen(test_video_crop_cmd)
362 test_crop.wait()
363 print 'Test file cropped to: ' + cropped_test_file
364 print 'Cropping done!\n'
365 # Need to return these so they can be used by other parts.
366 cropped_recordings = {
367 'cropped_test_file' : cropped_test_file,
368 'cropped_ref_file' : cropped_ref_file
369 }
370
371 return cropped_recordings
372 except subprocess.CalledProcessError as e:
373 print 'Something went wrong during cropping: ' + e
374 raise
375
376
377 def compare_videos(options, recording_result):
378 """Runs the compare_video.py script from src/webrtc/tools using the file paths
379 from recording_result and writes the output to a file named
380 <options.app_name + '_' + CURRENT_TIME + '_result.txt> in the reference video
381 recording folder taken from recording_result.
382
383 Args:
384 options(object): Contains all the provided command line options.
385 recording_files_and_time(dict): key: value pair with the path to cropped
386 test and reference video files
387 """
388 print 'Starting comparison...'
389 print 'Grab a coffee, this might take a few minutes...'
390 cropped_ref_file = recording_result['cropped_ref_file']
391 cropped_test_file = recording_result['cropped_test_file']
392 compare_videos_script = os.path.abspath(options.compare_videos_script)
393 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.
394 os.path.join(os.path.dirname(recording_result['cropped_ref_file']),
395 options.app_name + '_' + CURRENT_TIME + '_result.txt')
396
397 compare_cmd = [
398 sys.executable,
399 compare_videos_script,
400 '--ref_video', cropped_ref_file,
401 '--test_video', cropped_test_file,
402 '--frame_analyzer', os.path.abspath(options.frame_analyzer),
403 '--zxing_path', options.zxing_path,
404 '--ffmpeg_path', options.ref_ffmpeg,
405 '--stats_file_ref', os.path.join(os.path.dirname(cropped_ref_file),
406 cropped_ref_file + '_stats.txt'),
407 '--stats_file_test', os.path.join(os.path.dirname(cropped_test_file),
408 cropped_test_file + '_stats.txt'),
409 '--yuv_frame_height', options.ref_crop_height,
410 '--yuv_frame_width', options.ref_crop_width
411 ]
412
413 try:
414 with open(result_file_name, 'w') as f:
415 compare_video_recordings = subprocess.Popen(compare_cmd, stdout=f)
416 compare_video_recordings.wait()
417 print 'Result recorded to: ' + os.path.abspath(result_file_name)
418 print 'Comparison done!'
419 except subprocess.CalledProcessError as e:
420 print 'Something went wrong when trying to compare videos: ' + e
421 raise
422
423
424 def main():
425 """The main function.
426
427 A simple invocation is:
428 ./run_video_analysis.py \
429 --app_name AppRTCMobile \
430 --ref_ffmpeg ./ref_ffmpeg --ref_video_device=/dev/video0 \
431 --test_ffmpeg ./test_ffmpeg --test_video_device=/dev/video1 \
432 --zxing_path ./zxing \
433 --test_crop_width 950 --test_crop_height 420 \
434 --ref_crop_width 950 --ref_crop_height 420 \
435 --test_crop_x_axis_start 130 --test_crop_y_axis_start 56 \
436 --ref_crop_x_axis_start 130 --ref_crop_y_axis_start 56 \
437 --ref_rec_dir /tmp/ref \
438 --test_rec_dir /tmp/test
439
440 This will produce the following files if successful:
441 # Original video recordings.
442 /tmp/ref/AppRTCMobile_<recording date and time>_ref.yuv
443 /tmp/test/AppRTCMobile_<recording date and time>_test.yuv
444
445 # Cropped video recordings according to the crop parameters.
446 /tmp/ref/cropped_AppRTCMobile_<recording date and time>_ref.yuv
447 /tmp/test/cropped_AppRTCMobile_<recording date and time>_ref.yuv
448
449 # Comparison metrics from cropped test and ref videos.
450 /tmp/test/AppRTCMobile_<recording date and time>_result.text
451
452 """
453 options = _parseArgs()
454 restart_magewell_devices(options)
455 record_paths = create_recording_dirs(options)
456 recording_result = start_recording(options, record_paths)
457 # Do not require compare_video.py script to run, no metrics will be generated.
458 if os.path.exists(options.frame_analyzer) or \
459 os.path.exists(options.compare_videos_script) or \
460 os.path.exists(options.zxing_path):
461 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.
462
kjellander_webrtc 2017/02/22 12:30:50 +1 blank line for top-level statements.
janssonWebRTC 2017/03/07 14:23:45 Done.
463 if __name__ == '__main__':
464 sys.exit(main())
OLDNEW
« 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