bug 815002 - allow using loopback devices in WebRTC mochitests on Linux when available. r=jsmith,jmaher

This commit is contained in:
Ted Mielczarek 2014-05-01 07:18:00 -04:00
parent 57682362bb
commit 59841f10b0
4 changed files with 158 additions and 2 deletions

View File

@ -8,6 +8,16 @@ var Cr = SpecialPowers.Cr;
// Specifies whether we are using fake streams to run this automation
var FAKE_ENABLED = true;
try {
var audioDevice = SpecialPowers.getCharPref('media.audio_loopback_dev');
var videoDevice = SpecialPowers.getCharPref('media.video_loopback_dev');
dump('TEST DEVICES: Using media devices:\n');
dump('audio: ' + audioDevice + '\nvideo: ' + videoDevice + '\n');
FAKE_ENABLED = false;
} catch (e) {
dump('TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n');
FAKE_ENABLED = true;
}
/**
@ -97,7 +107,7 @@ function createMediaElement(type, label) {
* The error callback if the stream fails to be retrieved
*/
function getUserMedia(constraints, onSuccess, onError) {
if (!("fake" in constraints)) {
if (!("fake" in constraints) && FAKE_ENABLED) {
constraints["fake"] = FAKE_ENABLED;
}

View File

@ -190,7 +190,8 @@ class MochitestRunner(MozbuildObject):
jsdebugger=False, debug_on_failure=False, start_at=None, end_at=None,
e10s=False, dmd=False, dump_output_directory=None,
dump_about_memory_after_test=False, dump_dmd_after_test=False,
install_extension=None, quiet=False, environment=[], app_override=None, **kwargs):
install_extension=None, quiet=False, environment=[], app_override=None,
useTestMediaDevices=False, **kwargs):
"""Runs a mochitest.
test_paths are path to tests. They can be a relative path from the
@ -315,6 +316,7 @@ class MochitestRunner(MozbuildObject):
options.dumpOutputDirectory = dump_output_directory
options.quiet = quiet
options.environment = environment
options.useTestMediaDevices = useTestMediaDevices
options.failureFile = failure_file_path
if install_extension != None:
@ -526,6 +528,12 @@ def MochitestCommand(func):
help="Sets the given variable in the application's environment")
func = setenv(func)
test_media = CommandArgument('--use-test-media-devices', default=False,
action='store_true',
dest='useTestMediaDevices',
help='Use test media device drivers for media testing.')
func = test_media(func)
app_override = CommandArgument('--app-override', default=None, action='store',
help="Override the default binary used to run tests with the path you provide, e.g. " \
" --app-override /usr/bin/firefox . " \

View File

@ -416,6 +416,12 @@ class MochitestOptions(optparse.OptionParser):
"help": "name of the pidfile to generate",
"default": "",
}],
[["--use-test-media-devices"],
{ "action": "store_true",
"default": False,
"dest": "useTestMediaDevices",
"help": "Use test media device drivers for media testing.",
}],
]
def __init__(self, **kwargs):
@ -574,6 +580,13 @@ class MochitestOptions(optparse.OptionParser):
self.error('--dump-output-directory not a directory: %s' %
options.dumpOutputDirectory)
if options.useTestMediaDevices:
if not mozinfo.isLinux:
self.error('--use-test-media-devices is only supported on Linux currently')
for f in ['/usr/bin/gst-launch-0.10', '/usr/bin/pactl']:
if not os.path.isfile(f):
self.error('Missing binary %s required for --use-test-media-devices')
return options

View File

@ -12,6 +12,7 @@ import sys
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
sys.path.insert(0, SCRIPT_DIR);
import ctypes
import glob
import json
import mozcrash
@ -754,11 +755,123 @@ class SSLTunnel:
if os.path.exists(self.configFile):
os.remove(self.configFile)
def checkAndConfigureV4l2loopback(device):
'''
Determine if a given device path is a v4l2loopback device, and if so
toggle a few settings on it via fcntl. Very linux-specific.
Returns (status, device name) where status is a boolean.
'''
if not mozinfo.isLinux:
return False, ''
libc = ctypes.cdll.LoadLibrary('libc.so.6')
O_RDWR = 2
# These are from linux/videodev2.h
class v4l2_capability(ctypes.Structure):
_fields_ = [
('driver', ctypes.c_char * 16),
('card', ctypes.c_char * 32),
('bus_info', ctypes.c_char * 32),
('version', ctypes.c_uint32),
('capabilities', ctypes.c_uint32),
('device_caps', ctypes.c_uint32),
('reserved', ctypes.c_uint32 * 3)
]
VIDIOC_QUERYCAP = 0x80685600
fd = libc.open(device, O_RDWR)
if fd < 0:
return False, ''
vcap = v4l2_capability()
if libc.ioctl(fd, VIDIOC_QUERYCAP, ctypes.byref(vcap)) != 0:
return False, ''
if vcap.driver != 'v4l2 loopback':
return False, ''
class v4l2_control(ctypes.Structure):
_fields_ = [
('id', ctypes.c_uint32),
('value', ctypes.c_int32)
]
# These are private v4l2 control IDs, see:
# https://github.com/umlaeute/v4l2loopback/blob/fd822cf0faaccdf5f548cddd9a5a3dcebb6d584d/v4l2loopback.c#L131
KEEP_FORMAT = 0x8000000
SUSTAIN_FRAMERATE = 0x8000001
VIDIOC_S_CTRL = 0xc008561c
control = v4l2_control()
control.id = KEEP_FORMAT
control.value = 1
libc.ioctl(fd, VIDIOC_S_CTRL, ctypes.byref(control))
control.id = SUSTAIN_FRAMERATE
control.value = 1
libc.ioctl(fd, VIDIOC_S_CTRL, ctypes.byref(control))
libc.close(fd)
return True, vcap.card
def findTestMediaDevices():
'''
Find the test media devices configured on this system, and return a dict
containing information about them. The dict will have keys for 'audio'
and 'video', each containing the name of the media device to use.
If audio and video devices could not be found, return None.
This method is only currently implemented for Linux.
'''
if not mozinfo.isLinux:
return None
info = {}
# Look for a v4l2loopback device.
name = None
device = None
for dev in sorted(glob.glob('/dev/video*')):
result, name_ = checkAndConfigureV4l2loopback(dev)
if result:
name = name_
device = dev
break
if not (name and device):
log.error('Couldn\'t find a v4l2loopback video device')
return None
# Feed it a frame of output so it has something to display
subprocess.check_call(['/usr/bin/gst-launch-0.10', 'videotestsrc',
'pattern=green', 'num-buffers=1', '!',
'v4l2sink', 'device=%s' % device])
info['video'] = name
# Use pactl to see if the PulseAudio module-sine-source module is loaded.
def sine_source_loaded():
o = subprocess.check_output(['/usr/bin/pactl', 'list', 'short', 'modules'])
return filter(lambda x: 'module-sine-source' in x, o.splitlines())
if not sine_source_loaded():
# Load module-sine-source
subprocess.check_call(['/usr/bin/pactl', 'load-module',
'module-sine-source'])
if not sine_source_loaded():
log.error('Couldn\'t load module-sine-source')
return None
# Hardcode the name since it's always the same.
info['audio'] = 'Sine source at 440 Hz'
return info
class Mochitest(MochitestUtilsMixin):
certdbNew = False
sslTunnel = None
vmwareHelper = None
DEFAULT_TIMEOUT = 60.0
mediaDevices = None
# XXX use automation.py for test name to avoid breaking legacy
# TODO: replace this with 'runtests.py' or 'mochitest' or the like
@ -877,6 +990,11 @@ class Mochitest(MochitestUtilsMixin):
'ws': options.sslPort
}
# See if we should use fake media devices.
if options.useTestMediaDevices:
prefs['media.audio_loopback_dev'] = self.mediaDevices['audio']
prefs['media.video_loopback_dev'] = self.mediaDevices['video']
# create a profile
self.profile = Profile(profile=options.profilePath,
@ -1225,6 +1343,13 @@ class Mochitest(MochitestUtilsMixin):
options.debuggerArgs,
options.debuggerInteractive)
if options.useTestMediaDevices:
devices = findTestMediaDevices()
if not devices:
log.error("Could not find test media devices to use")
return 1
self.mediaDevices = devices
self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
browserEnv = self.buildBrowserEnv(options, debuggerInfo is not None)