Skip to content

Commit

Permalink
[ImageCapture] Add pan/tilt constraint and wire in Linux/CrOS.
Browse files Browse the repository at this point in the history
Pan and Tilt Constraints are part of the UVC spec.
For Linux/CrOS, we can use V4L2 controls like
(V4L2_CID_PAN_ABSOLUTE and V4L2_CID_TILT_ABSOLUTE).

Spec: w3c/mediacapture-image#182

Test Page: https://riju.github.io/WebCamera/samples/panTilt/

Putting Pan/Tilt feature behind a flag:
chrome --enable-blink-features=MediaCapturePanTilt

Intent to Implement and Ship discussions:
https://groups.google.com/a/chromium.org/d/msg/blink-dev/j-Q08QgBipM/F3a5sau1BwAJ

Bug:934063
Change-Id: I552c4c8be717c3b67c4d91f826a1f16850430fa4
  • Loading branch information
riju authored and chromium-wpt-export-bot committed Jul 26, 2019
1 parent 81cf722 commit 6a38da7
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
assert_true(supported_constraints.saturation);
assert_true(supported_constraints.sharpness);
assert_true(supported_constraints.focusDistance);
assert_true(supported_constraints.pan);
assert_true(supported_constraints.tilt);
assert_true(supported_constraints.zoom);
assert_true(supported_constraints.torch);
}, 'Image Capture supported constraints');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
sharpness : 6,
focusDistance : 7,

pan : 8,
tilt : 9,
zoom : 3.141592,

torch : true
Expand Down Expand Up @@ -89,6 +91,9 @@

assert_equals(constraints.advanced[0].focusDistance, settings.focusDistance,
'focusDistance');

assert_equals(constraints.advanced[0].pan, settings.pan, 'pan');
assert_equals(constraints.advanced[0].tilt, settings.tilt, 'tilt');
assert_equals(constraints.advanced[0].zoom, settings.zoom, 'zoom');

assert_equals(constraints.advanced[0].torch, settings.torch, 'torch');
Expand Down
4 changes: 4 additions & 0 deletions mediacapture-image/MediaStreamTrack-applyConstraints.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
sharpness : 6,
focusDistance : 7,

pan : 8,
tilt : 9,
zoom : 3.141592,

torch : true
Expand Down Expand Up @@ -109,6 +111,8 @@
assert_equals(constraintsDict.focusDistance, theMock.options().focusDistance
,'focusDistance');

assert_equals(constraintsDict.pan, theMock.options().pan, 'pan');
assert_equals(constraintsDict.tilt, theMock.options().tilt, 'tilt');

assert_equals(constraintsDict.torch, theMock.options().torch, 'torch');

Expand Down
10 changes: 10 additions & 0 deletions mediacapture-image/MediaStreamTrack-getCapabilities.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@
assert_equals(capabilities.focusDistance.step,
mockCapabilities.focusDistance.step);

assert_true(capabilities.pan instanceof MediaSettingsRange);
assert_equals(capabilities.pan.max, mockCapabilities.pan.max);
assert_equals(capabilities.pan.min, mockCapabilities.pan.min);
assert_equals(capabilities.pan.step, mockCapabilities.pan.step);

assert_true(capabilities.tilt instanceof MediaSettingsRange);
assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max);
assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min);
assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step);

assert_true(capabilities.zoom instanceof MediaSettingsRange);
assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
Expand Down
3 changes: 3 additions & 0 deletions mediacapture-image/MediaStreamTrack-getConstraints-fast.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
sharpness : 6,
focusDistance : 7,

pan : 8,
tilt : 9,

zoom : 3.141592
// TODO: torch https://crbug.com/700607.
};
Expand Down
3 changes: 3 additions & 0 deletions mediacapture-image/MediaStreamTrack-getSettings.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
assert_equals(settings.sharpness, mockSettings.sharpness.current);

assert_equals(settings.focusDistance, mockSettings.focusDistance.current);

assert_equals(settings.pan, mockSettings.pan.current);
assert_equals(settings.tilt, mockSettings.tilt.current);
assert_equals(settings.zoom, mockSettings.zoom.current);

assert_equals(settings.torch, mockSettings.torch, 'torch');
Expand Down
92 changes: 66 additions & 26 deletions resources/chromium/image_capture.mojom.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
MeteringMode.MANUAL = MeteringMode.NONE + 1;
MeteringMode.SINGLE_SHOT = MeteringMode.MANUAL + 1;
MeteringMode.CONTINUOUS = MeteringMode.SINGLE_SHOT + 1;
MeteringMode.MIN_VALUE = 0,
MeteringMode.MAX_VALUE = 3,

MeteringMode.isKnownEnumValue = function(value) {
switch (value) {
Expand All @@ -47,6 +49,8 @@
RedEyeReduction.NEVER = 0;
RedEyeReduction.ALWAYS = RedEyeReduction.NEVER + 1;
RedEyeReduction.CONTROLLABLE = RedEyeReduction.ALWAYS + 1;
RedEyeReduction.MIN_VALUE = 0,
RedEyeReduction.MAX_VALUE = 2,

RedEyeReduction.isKnownEnumValue = function(value) {
switch (value) {
Expand All @@ -69,6 +73,8 @@
FillLightMode.OFF = 0;
FillLightMode.AUTO = FillLightMode.OFF + 1;
FillLightMode.FLASH = FillLightMode.AUTO + 1;
FillLightMode.MIN_VALUE = 0,
FillLightMode.MAX_VALUE = 2,

FillLightMode.isKnownEnumValue = function(value) {
switch (value) {
Expand Down Expand Up @@ -175,6 +181,8 @@
this.saturation = null;
this.sharpness = null;
this.focusDistance = null;
this.pan = null;
this.tilt = null;
this.zoom = null;
this.redEyeReduction = 0;
this.height = null;
Expand All @@ -195,7 +203,7 @@
return err;

var kVersionSizes = [
{version: 0, numBytes: 168}
{version: 0, numBytes: 184}
];
err = messageValidator.validateStructVersion(offset, kVersionSizes);
if (err !== validator.validationError.NONE)
Expand Down Expand Up @@ -298,41 +306,53 @@
return err;


// validate PhotoState.zoom
// validate PhotoState.pan
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 120, Range, false);
if (err !== validator.validationError.NONE)
return err;


// validate PhotoState.tilt
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 128, Range, false);
if (err !== validator.validationError.NONE)
return err;


// validate PhotoState.zoom
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 136, Range, false);
if (err !== validator.validationError.NONE)
return err;




// validate PhotoState.redEyeReduction
err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 128, RedEyeReduction);
err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 144, RedEyeReduction);
if (err !== validator.validationError.NONE)
return err;


// validate PhotoState.height
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 136, Range, false);
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 152, Range, false);
if (err !== validator.validationError.NONE)
return err;


// validate PhotoState.width
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 144, Range, false);
err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 160, Range, false);
if (err !== validator.validationError.NONE)
return err;


// validate PhotoState.fillLightMode
err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 152, 4, new codec.Enum(FillLightMode), false, [0], 0);
err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 168, 4, new codec.Enum(FillLightMode), false, [0], 0);
if (err !== validator.validationError.NONE)
return err;

return validator.validationError.NONE;
};

PhotoState.encodedSize = codec.kStructHeaderSize + 160;
PhotoState.encodedSize = codec.kStructHeaderSize + 176;

PhotoState.decode = function(decoder) {
var packed;
Expand Down Expand Up @@ -361,6 +381,8 @@
val.saturation = decoder.decodeStructPointer(Range);
val.sharpness = decoder.decodeStructPointer(Range);
val.focusDistance = decoder.decodeStructPointer(Range);
val.pan = decoder.decodeStructPointer(Range);
val.tilt = decoder.decodeStructPointer(Range);
val.zoom = decoder.decodeStructPointer(Range);
val.redEyeReduction = decoder.decodeStruct(codec.Int32);
decoder.skip(1);
Expand Down Expand Up @@ -400,6 +422,8 @@
encoder.encodeStructPointer(Range, val.saturation);
encoder.encodeStructPointer(Range, val.sharpness);
encoder.encodeStructPointer(Range, val.focusDistance);
encoder.encodeStructPointer(Range, val.pan);
encoder.encodeStructPointer(Range, val.tilt);
encoder.encodeStructPointer(Range, val.zoom);
encoder.encodeStruct(codec.Int32, val.redEyeReduction);
encoder.skip(1);
Expand Down Expand Up @@ -483,6 +507,8 @@
this.hasSaturation = false;
this.hasSharpness = false;
this.hasFocusDistance = false;
this.hasPan = false;
this.hasTilt = false;
this.hasZoom = false;
this.hasTorch = false;
this.torch = false;
Expand All @@ -504,6 +530,8 @@
this.saturation = 0;
this.sharpness = 0;
this.focusDistance = 0;
this.pan = 0;
this.tilt = 0;
this.zoom = 0;
this.fillLightMode = 0;
this.width = 0;
Expand All @@ -523,7 +551,7 @@
return err;

var kVersionSizes = [
{version: 0, numBytes: 136}
{version: 0, numBytes: 152}
];
err = messageValidator.validateStructVersion(offset, kVersionSizes);
if (err !== validator.validationError.NONE)
Expand Down Expand Up @@ -575,13 +603,17 @@












// validate PhotoSettings.fillLightMode
err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 104, FillLightMode);
err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 120, FillLightMode);
if (err !== validator.validationError.NONE)
return err;

Expand All @@ -594,7 +626,7 @@
return validator.validationError.NONE;
};

PhotoSettings.encodedSize = codec.kStructHeaderSize + 128;
PhotoSettings.encodedSize = codec.kStructHeaderSize + 144;

PhotoSettings.decode = function(decoder) {
var packed;
Expand All @@ -615,15 +647,17 @@
val.hasSaturation = (packed >> 1) & 1 ? true : false;
val.hasSharpness = (packed >> 2) & 1 ? true : false;
val.hasFocusDistance = (packed >> 3) & 1 ? true : false;
val.hasZoom = (packed >> 4) & 1 ? true : false;
val.hasTorch = (packed >> 5) & 1 ? true : false;
val.torch = (packed >> 6) & 1 ? true : false;
val.hasFillLightMode = (packed >> 7) & 1 ? true : false;
val.hasPan = (packed >> 4) & 1 ? true : false;
val.hasTilt = (packed >> 5) & 1 ? true : false;
val.hasZoom = (packed >> 6) & 1 ? true : false;
val.hasTorch = (packed >> 7) & 1 ? true : false;
packed = decoder.readUint8();
val.hasWidth = (packed >> 0) & 1 ? true : false;
val.hasHeight = (packed >> 1) & 1 ? true : false;
val.hasRedEyeReduction = (packed >> 2) & 1 ? true : false;
val.redEyeReduction = (packed >> 3) & 1 ? true : false;
val.torch = (packed >> 0) & 1 ? true : false;
val.hasFillLightMode = (packed >> 1) & 1 ? true : false;
val.hasWidth = (packed >> 2) & 1 ? true : false;
val.hasHeight = (packed >> 3) & 1 ? true : false;
val.hasRedEyeReduction = (packed >> 4) & 1 ? true : false;
val.redEyeReduction = (packed >> 5) & 1 ? true : false;
decoder.skip(1);
val.whiteBalanceMode = decoder.decodeStruct(codec.Int32);
val.exposureMode = decoder.decodeStruct(codec.Int32);
Expand All @@ -638,6 +672,8 @@
val.saturation = decoder.decodeStruct(codec.Double);
val.sharpness = decoder.decodeStruct(codec.Double);
val.focusDistance = decoder.decodeStruct(codec.Double);
val.pan = decoder.decodeStruct(codec.Double);
val.tilt = decoder.decodeStruct(codec.Double);
val.zoom = decoder.decodeStruct(codec.Double);
val.fillLightMode = decoder.decodeStruct(codec.Int32);
decoder.skip(1);
Expand Down Expand Up @@ -668,16 +704,18 @@
packed |= (val.hasSaturation & 1) << 1
packed |= (val.hasSharpness & 1) << 2
packed |= (val.hasFocusDistance & 1) << 3
packed |= (val.hasZoom & 1) << 4
packed |= (val.hasTorch & 1) << 5
packed |= (val.torch & 1) << 6
packed |= (val.hasFillLightMode & 1) << 7
packed |= (val.hasPan & 1) << 4
packed |= (val.hasTilt & 1) << 5
packed |= (val.hasZoom & 1) << 6
packed |= (val.hasTorch & 1) << 7
encoder.writeUint8(packed);
packed = 0;
packed |= (val.hasWidth & 1) << 0
packed |= (val.hasHeight & 1) << 1
packed |= (val.hasRedEyeReduction & 1) << 2
packed |= (val.redEyeReduction & 1) << 3
packed |= (val.torch & 1) << 0
packed |= (val.hasFillLightMode & 1) << 1
packed |= (val.hasWidth & 1) << 2
packed |= (val.hasHeight & 1) << 3
packed |= (val.hasRedEyeReduction & 1) << 4
packed |= (val.redEyeReduction & 1) << 5
encoder.writeUint8(packed);
encoder.skip(1);
encoder.encodeStruct(codec.Int32, val.whiteBalanceMode);
Expand All @@ -693,6 +731,8 @@
encoder.encodeStruct(codec.Double, val.saturation);
encoder.encodeStruct(codec.Double, val.sharpness);
encoder.encodeStruct(codec.Double, val.focusDistance);
encoder.encodeStruct(codec.Double, val.pan);
encoder.encodeStruct(codec.Double, val.tilt);
encoder.encodeStruct(codec.Double, val.zoom);
encoder.encodeStruct(codec.Int32, val.fillLightMode);
encoder.skip(1);
Expand Down
18 changes: 18 additions & 0 deletions resources/chromium/mock-imagecapture.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ var ImageCaptureTest = (() => {
step: 1.0
},

pan: {
min: 0.0,
max: 10.0,
current: 5.0,
step: 2.0
},

tilt: {
min: 0.0,
max: 10.0,
current: 5.0,
step: 2.0
},

zoom: {
min: 0.0,
max: 10.0,
Expand Down Expand Up @@ -140,6 +154,10 @@ var ImageCaptureTest = (() => {
this.state_.state.height.current = settings.height;
if (settings.hasWidth)
this.state_.state.width.current = settings.width;
if (settings.hasPan)
this.state_.state.pan.current = settings.pan;
if (settings.hasTilt)
this.state_.state.tilt.current = settings.tilt;
if (settings.hasZoom)
this.state_.state.zoom.current = settings.zoom;
if (settings.hasFocusMode)
Expand Down

0 comments on commit 6a38da7

Please sign in to comment.