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

Side by Side Diff: webrtc/tools/run_video_analysis.py

Issue 2704113004: Add video recording wrapper (Closed)
Patch Set: Addressed comments + cleaned it up a little Created 3 years, 9 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():
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('--time_between_recordings', type=float, default=5,
37 help='Time between starting test recording after ref.'
38 'Default: %default')
39 parser.add_option('--ref_video_device', type='string', default='/dev/video0',
40 help='Reference recording device. Default: %default')
41 parser.add_option('--test_video_device', type='string', default='/dev/video1',
42 help='Test recording device. Default: %default')
43 parser.add_option('--app_name', type='string',
44 help='Name of the app under test.')
45 parser.add_option('--recording_api', type='string', default='Video4Linux2',
46 help='Recording API to use. Default: %default')
47 parser.add_option('--pixel_format', type='string', default='yuv420p',
48 help='Recording pixel format Default: %default')
49 parser.add_option('--ffmpeg', type='string',
50 help='Path to the ffmpeg executable for the reference '
51 'device.')
52 parser.add_option('--video_container', type='string', default='yuv',
53 help='Video container for the recordings.'
54 'Default: %default')
55 parser.add_option('--compare_videos_script', type='string',
56 default='compare_videos.py',
57 help='Path to script used to compare and generate metrics.'
58 'Default: %default')
59 parser.add_option('--frame_analyzer', type='string',
60 default='../../out/Default/frame_analyzer',
61 help='Path to the frame analyzer executable.'
62 'Default: %default')
63 parser.add_option('--zxing_path', type='string',
64 help='Path to the zebra xing barcode analyzer.')
65 parser.add_option('--ref_rec_dir', type='string', default='ref',
66 help='Path to where reference recordings will be created.'
67 'Ideally keep the ref and test directories on separate'
68 'drives. Default: %default')
69 parser.add_option('--test_rec_dir', type='string', default='test',
70 help='Path to where test recordings will be created.'
71 'Ideally keep the ref and test directories on separate '
72 'drives. Default: %default')
73 parser.add_option('--test_crop_parameters', type='string',
74 help='ffmpeg processing parameters for the test video.')
75 parser.add_option('--ref_crop_parameters', type='string',
76 help='ffmpeg processing parameters for the ref video.')
77
78 options, _ = parser.parse_args()
79
80 if not options.app_name:
81 parser.error('You must provide an application name!')
82
83 if not options.test_crop_parameters or not options.ref_crop_parameters:
84 parser.error('You must provide ref and test crop parameters!')
85
86 if not options.ffmpeg:
87 parser.error('You most provide location for the ffmpeg executable.')
88 if not os.path.isfile(options.ffmpeg):
89 parser.error('Cannot find the ffmpeg executable.')
90
91 # compare_videos.py dependencies.
92 if not os.path.isfile(options.compare_videos_script):
93 parser.warning('Cannot find compare_videos.py script, no metrics will be '
94 'generated!')
95 if not os.path.isfile(options.frame_analyzer):
96 parser.warning('Cannot find frame_analyzer, no metrics will be generated!')
97 if not os.path.isfile(options.zxing_path):
98 parser.warning('Cannot find Zebra Xing, no metrics will be generated!')
99
100 return options
101
102
103 def CreateRecordingDirs(options):
104 """Create root + sub directories for reference and test recordings.
105
106 Args:
107 options(object): Contains all the provided command line options.
108 Return:
109 record_paths(dict): key: value pair with reference and test file
110 absolute paths.
111 """
112
113 # Create root directories for the video recordings.
114 if not os.path.isdir(options.ref_rec_dir):
115 os.makedirs(options.ref_rec_dir)
116 if not os.path.isdir(options.test_rec_dir):
117 os.makedirs(options.test_rec_dir)
118
119 # Create and time-stamp directories for all the output files.
120 ref_rec_dir = os.path.join(options.ref_rec_dir, options.app_name + '_' + \
121 CURRENT_TIME)
122 test_rec_dir = os.path.join(options.test_rec_dir, options.app_name + '_' + \
123 CURRENT_TIME)
124
125 os.makedirs(ref_rec_dir)
126 os.makedirs(test_rec_dir)
127
128 record_paths = {
129 'ref_rec_location' : os.path.abspath(ref_rec_dir),
130 'test_rec_location' : os.path.abspath(test_rec_dir)
131 }
132
133 return record_paths
134
135
136 def RestartMagewellDevices(options):
kjellander_webrtc 2017/03/08 07:31:21 Since you're only using two of the options here, I
janssonWebRTC 2017/03/09 15:51:00 Done.
137 """Reset the USB ports where magewell capture devices are connected to.
138
139 Tries to find the provided options.ref_video_device and
140 options.Sest_Rdeo_device devices which use video4linux and then do a soft
kjellander_webrtc 2017/03/08 07:31:21 options.Sest_Rdeo_device does not exist.
janssonWebRTC 2017/03/09 15:51:01 Done.
141 reset by using USB unbind and bind. This is due to Magewell capture devices
142 have proven to be unstable after the first recording attempt.
143
144 Args:
145 options(object): Contains all the provided command line options.
146 """
147
148 # Get the dev/videoN device name from the command line arguments.
149 ref_magewell = options.ref_video_device.split('/')[2]
150 test_magewell = options.test_video_device.split('/')[2]
151
152 # Find the device location including USB and USB Bus ID's.
153 device_string = '/sys/bus/usb/devices/usb*/**/**/video4linux/'
154 ref_magewell_device = glob.glob('%s%s' % (device_string, ref_magewell))
155 test_magewell_device = glob.glob('%s%s' % (device_string, test_magewell))
156
157 magewell_usb_ports = []
158
159 # Figure out the USB bus and port ID for each device.
160 ref_magewell_path = str(ref_magewell_device).split('/')
161 for directory in ref_magewell_path:
162
163 # Find the folder with pattern "N-N", e.g. "4-3" or \
164 # "[USB bus ID]-[USB port]"
165 if re.match(r'^\d-\d$', directory):
166 magewell_usb_ports.append(directory)
167
168 test_magewell_path = str(test_magewell_device).split('/')
169 for directory in test_magewell_path:
170
171 # Find the folder with pattern "N-N", e.g. "4-3" or \
172 # "[USB bus ID]-[USB port]"
173 if re.match(r'^\d-\d$', directory):
174 magewell_usb_ports.append(directory)
175
176 print '\nResetting USB ports where magewell devices are connected...'
177
178 # Use the USB bus and port ID (e.g. 4-3) to unbind and bind the USB devices
179 # (i.e. soft eject and insert).
180 try:
181 for usb_port in magewell_usb_ports:
182 echo_cmd = ['echo', usb_port]
183 unbind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/unbind']
184 bind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/bind']
185
186 # TODO(jansson) Figure out a way to call on echo once for bind & unbind
187 # if possible.
188 echo_unbind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
189 unbind = subprocess.Popen(unbind_cmd, stdin=echo_unbind.stdout)
190 echo_unbind.stdout.close()
191 unbind.communicate()
192 unbind.wait()
193
194 echo_bind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
195 bind = subprocess.Popen(bind_cmd, stdin=echo_bind.stdout)
196 echo_bind.stdout.close()
197 bind.communicate()
198 bind.wait()
199 except OSError as e:
200 print 'Error while resetting magewell devices: ' + e
201 raise
202
203 print 'Reset done!\n'
204
205
206 def StartRecording(options, record_paths):
207 """Starts recording from the two specified video devices.
208
209 Args:
210 options(object): Contains all the provided command line options.
211 record_paths(dict): key: value pair with reference and test file
212 absolute paths.
213 """
214 ref_file_name = '%s_%s_ref.%s' % (options.app_name, CURRENT_TIME,
215 options.video_container)
216 ref_file_location = os.path.join(record_paths['ref_rec_location'],
217 ref_file_name)
218
219 test_file_name = '%s_%s_test.%s' % (options.app_name, CURRENT_TIME,
220 options.video_container)
221 test_file_location = os.path.join(record_paths['test_rec_location'],
222 test_file_name)
223
224 # Reference video recorder command line.
225 ref_cmd = [
226 options.ffmpeg,
227 '-v', 'error',
228 '-s', options.frame_width + 'x' + options.frame_height,
229 '-framerate', options.framerate,
230 '-f', options.recording_api,
231 '-i', options.ref_video_device,
232 '-pix_fmt', options.pixel_format,
233 '-s', options.frame_width + 'x' + options.frame_height,
234 '-t', options.ref_duration,
235 '-framerate', options.framerate,
236 ref_file_location
237 ]
238
239 # Test video recorder command line.
240 test_cmd = [
241 options.ffmpeg,
242 '-v', 'error',
243 '-s', options.frame_width + 'x' + options.frame_height,
244 '-framerate', options.framerate,
245 '-f', options.recording_api,
246 '-i', options.test_video_device,
247 '-pix_fmt', options.pixel_format,
248 '-s', options.frame_width + 'x' + options.frame_height,
249 '-t', options.test_duration,
250 '-framerate', options.framerate,
251 test_file_location
252 ]
253 print 'Trying to record from reference recorder...'
254 ref_recorder = subprocess.Popen(ref_cmd, stderr=sys.stderr)
255
256 # Start the 2nd recording a little later to ensure the 1st one has started.
257 # TODO(jansson) Check that the ref_recorder output file exists rather than
258 # using sleep.
259 time.sleep(options.time_between_recordings)
260 print 'Trying to record from test recorder...'
261 test_recorder = subprocess.Popen(test_cmd, stderr=sys.stderr)
262 test_recorder.wait()
263 ref_recorder.wait()
264
265 # ffmpeg does not abort when it fails, need to check return code.
266 assert ref_recorder.returncode == 0, '%s%s' % ('Ref recording failed, '
kjellander_webrtc 2017/03/08 07:31:21 It looks strange to me to use the string formatter
janssonWebRTC 2017/03/09 15:51:00 Done.
267 'check ffmpeg output and device: ', options.ref_video_device)
268 assert test_recorder.returncode == 0, '%s%s' % ('Test recording failed, '
269 'check ffmpeg output and device: ', options.test_video_device)
270
271 print 'Ref file recorded to: ' + os.path.abspath(ref_file_location)
272 print 'Test file recorded to: ' + os.path.abspath(test_file_location)
273 print 'Recording done!\n'
274 return FlipAndCropRecordings(options, test_file_name,
275 record_paths['test_rec_location'], ref_file_name,
276 record_paths['ref_rec_location'])
277
278
279 def FlipAndCropRecordings(options, test_file_name, test_file_location,
280 ref_file_name, ref_file_location):
281 """Performs a horizontal flip of the reference video to match the test video.
282
283 This is done to the match orientation and then crops the ref and test videos
284 using the options.test_crop_parameters and options.ref_crop_parameters.
285
286 Args:
287 options(object): Contains all the provided command line options.
288 test_file_name(string): Name of the test video file recording.
289 test_file_location(string): Path to the test video file recording.
290 ref_file_name(string): Name of the reference video file recording.
291 ref_file_location(string): Path to the reference video file recording.
292 Return:
293 recording_files_and_time(dict): key: value pair with the path to cropped
294 test and reference video files.
295 """
296 print 'Trying to crop videos...'
297
298 # Ref file cropping.
299 cropped_ref_file_name = 'cropped_' + ref_file_name
300 cropped_ref_file = os.path.abspath(
301 os.path.join(ref_file_location, cropped_ref_file_name))
302
303 ref_video_crop_cmd = [
304 options.ffmpeg,
305 '-v', 'error',
306 '-s', options.frame_width + 'x' + options.frame_height,
307 '-i', os.path.join(ref_file_location, ref_file_name),
308 '-vf', options.ref_crop_parameters,
309 '-c:a', 'copy',
310 cropped_ref_file
311 ]
312
313 # Test file cropping.
314 cropped_test_file_name = 'cropped_' + test_file_name
315 cropped_test_file = os.path.abspath(
316 os.path.join(test_file_location, cropped_test_file_name))
317
318 test_video_crop_cmd = [
319 options.ffmpeg,
320 '-v', 'error',
321 '-s', options.frame_width + 'x' + options.frame_height,
322 '-i', os.path.join(test_file_location, test_file_name),
323 '-vf', options.test_crop_parameters,
324 '-c:a', 'copy',
325 cropped_test_file
326 ]
327
328 ref_crop = subprocess.Popen(ref_video_crop_cmd)
329 ref_crop.wait()
330 print 'Ref file cropped to: ' + cropped_ref_file
331
332 try:
333 test_crop = subprocess.Popen(test_video_crop_cmd)
334 test_crop.wait()
335 print 'Test file cropped to: ' + cropped_test_file
336 print 'Cropping done!\n'
337
338 # Need to return these so they can be used by other parts.
339 cropped_recordings = {
340 'cropped_test_file' : cropped_test_file,
341 'cropped_ref_file' : cropped_ref_file
342 }
343
344 return cropped_recordings
345 except subprocess.CalledProcessError as e:
346 print 'Something went wrong during cropping: ' + e
347 raise
348
349
350 def CompareVideos(options, recording_result):
351 """Runs the compare_video.py script from src/webrtc/tools using the file path.
352
353 Uses the path from recording_result and writes the output to a file named
354 <options.app_name + '_' + CURRENT_TIME + '_result.txt> in the reference video
355 recording folder taken from recording_result.
356
357 Args:
358 options(object): Contains all the provided command line options.
359 recording_files_and_time(dict): key: value pair with the path to cropped
360 test and reference video files
361 """
362 print 'Starting comparison...'
363 print 'Grab a coffee, this might take a few minutes...'
364 cropped_ref_file = recording_result['cropped_ref_file']
365 cropped_test_file = recording_result['cropped_test_file']
366 compare_videos_script = os.path.abspath(options.compare_videos_script)
367 result_file_name = os.path.join('%s', '%s_%s%s') % (
kjellander_webrtc 2017/03/08 07:31:21 Inline os.path.dirname(recording_result['cropped_r
janssonWebRTC 2017/03/09 15:51:01 Done.
368 os.path.dirname(recording_result['cropped_ref_file']), options.app_name,
369 CURRENT_TIME, '_result.txt')
370
371 # Find the crop dimensions (950 and 420) in the ref crop parameter string:
372 # 'hflip, crop=950:420:130:56'
373 for param in options.ref_crop_parameters.split('crop'):
kjellander_webrtc 2017/03/08 07:31:21 You're making hard assumptions on the format of th
janssonWebRTC 2017/03/09 15:51:01 Good catch, I've added that as check in _ParseArgs
374 if param[0] == '=':
375 crop_width = param.split(':')[0].split('=')[1]
376 crop_height = param.split(':')[1]
377
378 compare_cmd = [
379 sys.executable,
380 compare_videos_script,
381 '--ref_video', cropped_ref_file,
382 '--test_video', cropped_test_file,
383 '--frame_analyzer', os.path.abspath(options.frame_analyzer),
384 '--zxing_path', options.zxing_path,
385 '--ffmpeg_path', options.ffmpeg,
386 '--stats_file_ref', os.path.join(os.path.dirname(cropped_ref_file),
387 cropped_ref_file + '_stats.txt'),
388 '--stats_file_test', os.path.join(os.path.dirname(cropped_test_file),
389 cropped_test_file + '_stats.txt'),
390 '--yuv_frame_height', crop_height,
391 '--yuv_frame_width', crop_width
392 ]
393
394 try:
395 with open(result_file_name, 'w') as f:
396 compare_video_recordings = subprocess.Popen(compare_cmd, stdout=f)
397 compare_video_recordings.wait()
398 print 'Result recorded to: ' + os.path.abspath(result_file_name)
399 print 'Comparison done!'
400 except subprocess.CalledProcessError as e:
401 print 'Something went wrong when trying to compare videos: ' + e
402 raise
403
404
405 def main():
406 """The main function.
407
408 A simple invocation is:
409 ./run_video_analysis.py \
410 --app_name AppRTCMobile \
411 --ffmpeg ./ffmpeg --ref_video_device=/dev/video0 \
412 --test_video_device=/dev/video1 \
413 --zxing_path ./zxing \
414 --test_crop_parameters 'crop=950:420:130:56' \
415 --ref_crop_parameters 'hflip, crop=950:420:130:56' \
416 --ref_rec_dir /tmp/ref \
417 --test_rec_dir /tmp/test
418
419 This will produce the following files if successful:
420 # Original video recordings.
421 /tmp/ref/AppRTCMobile_<recording date and time>_ref.yuv
422 /tmp/test/AppRTCMobile_<recording date and time>_test.yuv
423
424 # Cropped video recordings according to the crop parameters.
425 /tmp/ref/cropped_AppRTCMobile_<recording date and time>_ref.yuv
426 /tmp/test/cropped_AppRTCMobile_<recording date and time>_ref.yuv
427
428 # Comparison metrics from cropped test and ref videos.
429 /tmp/test/AppRTCMobile_<recording date and time>_result.text
430
431 """
432 options = _ParseArgs()
433 RestartMagewellDevices(options)
434 record_paths = CreateRecordingDirs(options)
435 recording_result = StartRecording(options, record_paths)
436
437 # Do not require compare_video.py script to run, no metrics will be generated.
438 if (os.path.exists(options.frame_analyzer) or
kjellander_webrtc 2017/03/08 07:31:21 Skip os.path.exists here (you already check that i
janssonWebRTC 2017/03/09 15:51:01 Yeah I agree this is confusing, I will leave just
439 os.path.exists(options.compare_videos_script) or
kjellander_webrtc 2017/03/08 07:31:21 +2 space indent on this line and the one below.
janssonWebRTC 2017/03/09 15:51:00 Done.
440 os.path.exists(options.zxing_path)):
441 CompareVideos(options, recording_result)
442 else:
443 print ('Skipping compare videos step due to frame_analyzer, zxing or '
444 'compare_videos flags were not passed.')
kjellander_webrtc 2017/03/08 07:31:21 indent with ( above.
janssonWebRTC 2017/03/09 15:51:00 Done.
445
446
447 if __name__ == '__main__':
448 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