webrtc / testrtc

WebRTC Troubeshooter PROJECT IS ON HOLD
https://test.webrtc.org/
BSD 3-Clause "New" or "Revised" License
479 stars 215 forks source link

Resolution test does not use selected camera #262

Open jlongman opened 6 years ago

jlongman commented 6 years ago

In a system with multiple cameras (I have 5 listed) select different cameras and re-run the resolution tests. The camera used will not always be the camera selected. This does however work with the microphone.

Browsers and versions affected

Should be all - see Analysis below.
For completeness: MacOS 10.13.3, Chrome Version 65.0.3325.181 (Official Build) (64-bit).

Steps to reproduce

In a system with multiple cameras (I have 5 listed) select different cameras using the hamburger and re-run the resolution test. In my case CamTwist is the first camera listed but ManyCam Virtual Camera (or sometimes the Facetime HD Camera) was the one used. I had selected the fourth option, a Logitech Brio in this case.

This is visible by expanding a resolution test and comparing the output to the expectations, e.g.:

Check resolution 640x480
[ INFO ] cameraName: FaceTime HD Camera

Expected results

The resolution tests should be run with the camera selected in the configuration options.

Actual results

The camera used is chosen by the system and not the user.

Analysis

In testrtc-main.html there is a method which is intended to inject the settings into the GUM call:

      doGetUserMedia: function(constraints, onSuccess, onFail) {
        var self = this;
        var traceGumEvent = report.traceEventAsync('getusermedia');

        try {
          // Append the constraints with the getSource constraints.
          this.appendSourceId(this.$.audioSource.value, 'audio', constraints);
          this.appendSourceId(this.$.videoSource.value, 'video', constraints);

          traceGumEvent({'status': 'pending', 'constraints': constraints});
          // Call into getUserMedia via the polyfill (adapter.js).
          navigator.mediaDevices.getUserMedia(constraints)

In mictest.js we can see where this is called:

MicTest.prototype = {
  run: function() {
    if (typeof audioContext === 'undefined') {
      this.test.reportError('WebAudio is not supported, test cannot run.');
      this.test.done();
    } else {
      doGetUserMedia(this.constraints, this.gotStream.bind(this));
    }
  },

however in camresolutionstest.js we see this is not called:

  startGetUserMedia: function(resolution) {
    var constraints = {
      audio: false,
      video: {
        width: {exact: resolution[0]},
        height: {exact: resolution[1]}
      }
    };
    navigator.mediaDevices.getUserMedia(constraints)

the code bypasses the doGetUserMedia and call navigator.mediaDevices.getUserMedia directly, bypassing the source injection.

jlongman commented 6 years ago

A naïve replacement of the navigator.mediaDevices.getUserMedia(constraints) with doGetUserMedia results in:

Failed to execute 'getUserMedia' on 'MediaDevices': Malformed constraint: Cannot use both optional/mandatory and specific or advanced constraints.

streamencode commented 6 years ago

I am also having this same issue on multiple Windows boxes, only the Default camera is used regardless of video selection.

felixzapata commented 5 years ago

Maybe if you replace the current startGetUserMedia with this one:

startGetUserMedia: function(resolution) {
    var constraints = {
      audio: false,
      video: {
       deviceId: this.$.videoSource.value,
        width: { exact: resolution[0] },
        height: { exact: resolution[1] }
      }
    };
    var traceGumEvent = window.report.traceEventAsync('getusermedia');
    try {
      traceGumEvent({ status: 'pending', constraints: constraints });
      navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
        // Do not check actual video frames when more than one resolution is
        // provided.
        var cam = this.test.getDeviceName_(stream.getVideoTracks());
        traceGumEvent({ status: 'success', camera: cam });
        if(this.resolutions.length > 1) {
          this.test.reportSuccess('Supported: ' + resolution[0] + 'x' + resolution[1]);
          stream.getTracks().forEach(function(track) {
            track.stop();
          });
          this.maybeContinueGetUserMedia();
        } else {
          this.collectAndAnalyzeStats_(stream, resolution);
        }
      }.bind(this).catch(function(error) {
        traceGumEvent({ status: 'fail', error: error });
        if(this.resolutions.length > 1) {
          this.test.reportInfo(resolution[0] + 'x' + resolution[1] + ' not supported');
        } else {
          this.test.reportError('getUserMedia failed with error: ' + error.name);
        }
        this.maybeContinueGetUserMedia();
      }.bind(this));
    } catch (e) {
      console.log(e);
      traceGumEvent({ status: 'exception', error: e.message });
      return this.test.reportFatal('getUserMedia failed with exception: ' + e.message);
    }
  },