Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a test for ReplaceTrack that verifies video track content. #22779

Merged
merged 1 commit into from
Apr 30, 2020

Conversation

chromium-wpt-export-bot
Copy link
Collaborator

@chromium-wpt-export-bot chromium-wpt-export-bot commented Apr 8, 2020

This verifies that replaceTrack() actually replaces the track.
Adds a new helper function to add a "signal" square into a noise track.

Bug: none
Change-Id: Ia90535c984d65adcdf2c63a5700b08d7c1e384c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2141913
Commit-Queue: Harald Alvestrand <[email protected]>
Reviewed-by: Guido Urdaneta <[email protected]>
Cr-Commit-Position: refs/heads/master@{#758062}

Copy link
Collaborator

@wpt-pr-bot wpt-pr-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The review process for this patch is being conducted in the Chromium project.

doSignalingHandshake(pc1, pc2);
await metadataToBeLoaded;
await detectSignal(t, v, 20);
await sender.replaceTrack(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of await sender.replaceTrack(null) in the code?

@stephenmcgruer
Copy link
Contributor

Failed both chrome and firefox stability checks. Firefox was a timeout, but Chrome was:

62:04.05 INFO ## Unstable results ##
62:04.05 INFO |                      Test                      |                           Subtest                            |          Results           |                                         Messages                                        |
62:04.05 INFO |------------------------------------------------|--------------------------------------------------------------|----------------------------|-----------------------------------------------------------------------------------------|
62:04.05 INFO | `/webrtc/RTCPeerConnection-ondatachannel.html` | `Should be able to send data in a datachannel event handler` | **FAIL: 1/10, PASS: 9/10** | `Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'` |
62:04.05 INFO 

There's no previously recorded flake for that test on wpt.fyi, but this PR also doesn't obvious touch it. I'll need to look closer at the changes to the helper js files.

if (signal !== null) {
ctx.fillStyle = `rgb(${signal}, ${signal}, ${signal})`;
ctx.fillRect(10, 10, 20, 20);
let pixel = ctx.getImageData(15, 15, 1, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of let pixel = ctx.getImageData(15, 15, 1, 1)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does look like there's no good reason for it. I'll send a PR to delete this line once this PR is landed.

pc2.ontrack = (e) => {
v.srcObject = new MediaStream([e.track]);
};
const metadataToBeLoaded = new Promise((resolve) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue is metadataToBeLoaded Promise never fulfills.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://wpt.fyi/results/webrtc/RTCRtpSender-replaceTrack.https.html?label=pr_head&max-count=1&pr=22779 are the results from running this test in Chrome, Firefox and Safari.

The failure due to OffscreenCanvas is inside detectSignal, so this promise did resolve.

If you see some other behavior locally it's probably because differences in rules around autoplaying video. In CI we pass some additional flags to browsers to allow that, since so many tests depend on this.

@guest271314
Copy link
Contributor

As indicated at #22779 (comment) metadataToBeLoaded never resolves. autoplay has no effect at HTMLVideoElement in that case as to loadedmetadata event at either Chromium or Firefox. For an event to be fired in that case play() needs to be executed

One example of observable differences between Firefox and Chromium implementation of either canvas.captureStream() and the resulting MediaStreamTrack, besides, or because of requestFrame() being defined at MediaStream or MediaStreamTrack, and/or RTCPeerConnection proper at each browser is the following exact opposite implementation

    // where MediaStreamTrack added to pc1 is a CanvasCaptureMediaStreamTrack
    pc2.ontrack = e => {
      console.log(e);
      currentTrack = e.track;
      currentTrack.onmute = currentTrack.onunmute = e => console.log(e); // L59
      v.srcObject = new MediaStream([e.track]);
      v.play(); // use this to fulfill metadataToBeLoaded
      requestFrame();
    };

and later we have

    track.onmute = track.onunmute = e => {
      console.log(e); // L79
    }
    track.enabled = false;
    console.log(track, currentTrack);

the result is that, more than not, Nightly 77 will not fire unmute event, though may or may not fire the event every 5 to 10 runs of the code. Chromium dispatches mute and unmute events

Chromium 83

unmute (index):50 
play (index):82 CanvasCaptureMediaStreamTrack {canvas: canvas, kind: "video", id: "0e02d54b-3bf7-48d6-993c-372816a87d1b", label: "SnM8rr1B9oYJ+5HvRvaArzofNuysBDNKlPmMlq4BnGu5k1l99CPqvbVYN6u27IOFR0EVY6wxVkRH04q8G54x3w==", enabled: false, …} MediaStreamTrack {kind: "video", id: "0e02d54b-3bf7-48d6-993c-372816a87d1b", label: "0e02d54b-3bf7-48d6-993c-372816a87d1b", enabled: true, muted: false, …}
(index):59 mute
(index):79 Event {isTrusted: true, type: "mute", target: CanvasCaptureMediaStreamTrack, currentTarget: CanvasCaptureMediaStreamTrack, eventPhase: 2, …}

Nightly 77


track { target: RTCPeerConnection, isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Restricted, transceiver: RTCRtpTransceiver, srcElement: RTCPeerConnection, currentTarget: RTCPeerConnection, eventPhase: 2, bubbles: false, … }
ck8zbssq900061u6qle79hmt7:57:19
play ck8zbssq900061u6qle79hmt7:50:21
MediaStreamTrack { kind: "video", id: "{7ab0c793-b993-4e41-8437-3d05ae00d07a}", label: "", enabled: false, muted: false, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
 
MediaStreamTrack { kind: "video", id: "{2c320e5d-b675-49f1-9082-3588e7d19f62}", label: "remote video", enabled: true, muted: true, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
ck8zbssq900061u6qle79hmt7:82:17

track { target: RTCPeerConnection, isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Restricted, transceiver: RTCRtpTransceiver, srcElement: RTCPeerConnection, currentTarget: RTCPeerConnection, eventPhase: 2, bubbles: false, … }
ck8zbssq900061u6qle79hmt7:57:19
play ck8zbssq900061u6qle79hmt7:50:21
MediaStreamTrack { kind: "video", id: "{0a6d2053-1dba-4af4-a296-474e7fc245ba}", label: "", enabled: false, muted: false, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
 
MediaStreamTrack { kind: "video", id: "{ec3a5eac-35ce-4b16-a749-49cb1a200bc3}", label: "remote video", enabled: true, muted: true, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
ck8zbssq900061u6qle79hmt7:82:17

track { target: RTCPeerConnection, isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Restricted, transceiver: RTCRtpTransceiver, srcElement: RTCPeerConnection, currentTarget: RTCPeerConnection, eventPhase: 2, bubbles: false, … }
ck8zbssq900061u6qle79hmt7:57:19
play ck8zbssq900061u6qle79hmt7:50:21
MediaStreamTrack { kind: "video", id: "{4d189e0a-d73b-4e78-b45f-c61ddb197fea}", label: "", enabled: false, muted: false, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
 
MediaStreamTrack { kind: "video", id: "{7d954617-7843-45cb-91eb-c2f81e88c1a0}", label: "remote video", enabled: true, muted: true, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
ck8zbssq900061u6qle79hmt7:82:17

track { target: RTCPeerConnection, isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Restricted, transceiver: RTCRtpTransceiver, srcElement: RTCPeerConnection, currentTarget: RTCPeerConnection, eventPhase: 2, bubbles: false, … }
ck8zbssq900061u6qle79hmt7:57:19
play ck8zbssq900061u6qle79hmt7:50:21

unmute ck8zbssq900061u6qle79hmt7:59:70  // single unmute event dispatched during more than one run of same code

track { target: RTCPeerConnection, isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Restricted, transceiver: RTCRtpTransceiver, srcElement: RTCPeerConnection, currentTarget: RTCPeerConnection, eventPhase: 2, bubbles: false, … }
ck8zbssq900061u6qle79hmt7:57:19
play ck8zbssq900061u6qle79hmt7:50:21
MediaStreamTrack { kind: "video", id: "{dfd81fe9-bb87-4b14-b6b4-b853f7ad14c7}", label: "", enabled: false, muted: false, onmute: onunmute(e), onunmute: onunmute(e)
, readyState: "live", onended: null }
 
MediaStreamTrack { kind: "video", id: "{727bd3ea-78ad-4e03-bd4b-74f075d5caac}", label: "remote video", enabled: true, muted: true, onmute: onunmute(e), onunmute: onunmute(e), readyState: "live", onended: null }

@guest271314
Copy link
Contributor

Another issue is both Firefox and Chromium appear to return an ImageData containing all 0s when using drawImage() with <video> in the code with remote stream set as srcObject.

At Chromium ImageBitmap from new ImageCapture(v.srcObject.getVideoTracks()[0]) does set data other than 0s and 255s at returned ImageData instance. Firefox does not implement ImageCapture, and does set all 0s and 255s at ImageData running the same code.

Nightly 77

First ImageData is a newly created canvas that replaced the track at <video>.srcObject

burlywood 
ImageData
data: Uint8ClampedArray(400) [ 222, 184, 135, … ]

the ImageData using createImageBitmap() and getImageData()

ImageData
​
data: Uint8ClampedArray(400)
​​
[0…99]
​​
[100…199]
​​​
100: 0
​​​
101: 0
​​​
102: 0
​​​
103: 255
​​​
104: 0
​​​
105: 0
​​​
106: 0
​​​
107: 255
​​​
108: 0
​​​
109: 0
​​​
110: 0
​​​
111: 255

Chromium 83, same code

burlywood 
ImageData {data: Uint8ClampedArray(400), width: 10, height: 10, dataUnion: Uint8ClampedArray(400)}
data: Uint8ClampedArray(400) [222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, …]
dataUnion: Uint8ClampedArray(400) [222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, …]
height: 10
width: 10
__proto__: ImageData
 
ImageData {data: Uint8ClampedArray(400), width: 10, height: 10, dataUnion: Uint8ClampedArray(400)}
data: Uint8ClampedArray(400) [0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, …]
dataUnion: Uint8ClampedArray(400) [0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, …]
height: 10
width: 10
__proto__: ImageData

Using ImageCapture at Chromium 83

ImageData {data: Uint8ClampedArray(400), width: 10, height: 10, dataUnion: Uint8ClampedArray(400)}
data: Uint8ClampedArray(400) [222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, …]
dataUnion: Uint8ClampedArray(400) [222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, 222, 184, 135, 255, …]
height: 10
width: 10
__proto__: ImageData
 
ImageData {data: Uint8ClampedArray(400), width: 10, height: 10, dataUnion: Uint8ClampedArray(400)}
data: Uint8ClampedArray(400) [222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, …]
dataUnion: Uint8ClampedArray(400) [222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, 222, 184, 134, 255, …]
height: 10
width: 10
__proto__: ImageData

@guest271314
Copy link
Contributor

Comparing the Uint8ClampedArrays from createImageBitmap(), getImageData() and ImageCapture.grabFrame(), respectively, at index 2 at the former 135 is set at the 134. Am not sure if

if (signal !== null && signal < value + 1 && signal > value - 1) { 
  resolve();
}

is intended to handle that case?

Or, if the test should fail if 135 is original value and 134 is set at the value returned by using multiple methods to get said value at all?

@guest271314
Copy link
Contributor

A test for Chromium that uses ImageCapture.grabFrame() (plnkr https://plnkr.co/edit/2MA2uLj4c3cCrVY8?preview)

<!DOCTYPE html>

<html>
  <head>
    <title>
      Test and verify RTCRtpSender.replaceTrack() replaces video track content
    </title>
    <script src="RTCPeerConnection-helper.js"></script>
    <style>
      video,
      canvas {
        border: 2px solid purple;
      }
    </style>
  </head>

  <body>
    <a href="https://github.com/web-platform-tests/wpt/pull/22779"
      >Add a test for ReplaceTrack that verifies video track content. #22779.</a
    ><br>
    <script>
      (async _ => {
        const v = document.createElement('video');
        v.width = v.height = 10;
        v.controls = true;
        const pc1 = new RTCPeerConnection();
        const pc2 = new RTCPeerConnection();
        document.body.appendChild(v);
        const verifyReplacedTrackColors = color => {
          return new ImageCapture(v.srcObject.getVideoTracks()[0])
            .grabFrame()
            .then(imageBitmap => {
              const canvas = document.createElement('canvas');
              canvas.width = canvas.height = 10;
              const ctx = canvas.getContext('2d');
              ctx.drawImage(imageBitmap, 0, 0, canvas.width, canvas.height);
              document.body.appendChild(canvas);
              return ctx.getImageData(0, 0, 10, 10);
            });
        };
        const canvasStream = ({ color }) => {
          const canvas = document.createElement('canvas');
          canvas.width = canvas.height = v.width;
          const ctx = canvas.getContext('2d');
          ctx.fillStyle = color;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
          const stream = canvas.captureStream(0);
          const [track] = stream.getVideoTracks();
          track.onmute = track.onunmute = e => console.log(e.type);
          const requestFrame = _ =>
            'requestFrame' in track
              ? track.requestFrame()
              : stream.requestFrame();
          const paint = _ => {
            if (track.enabled && track.readyState === 'live') {
              ctx.fillStyle = color;
              ctx.fillRect(0, 0, canvas.width, canvas.height);
              requestFrame();
              requestAnimationFrame(paint);
            } else {
              return false;
            }
          };
          requestAnimationFrame(paint);
          return {
            imageData: ctx.getImageData(0, 0, canvas.width, canvas.height),
            track,
          };
        };
        const metadataToBeLoaded = new Promise(resolve => {
          v.onplay = e => {
            v.onplay = null;
            resolve();
          };
        });
        // initial, placeholder MediaStreamTrack to be replaced
        let { imageData, track } = canvasStream({ color: 'transparent' });
        const sender = pc1.addTrack(track);
        pc2.ontrack = async e => {
          v.srcObject = new MediaStream([e.track]);
          await v.play();
        };

        exchangeIceCandidates(pc1, pc2);
        doSignalingHandshake(pc1, pc2);

        await metadataToBeLoaded;
        const cssColor = 'https://drafts.csswg.org/css-color/';

        const html = await (await fetch(cssColor)).text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const colorNames = Array.from(
          doc.querySelectorAll('.named-color-table dfn'),
          ({ textContent }) => textContent
        );
        let result = [];
        for (const color of colorNames) {
          if (track) {
            track.enabled = false;
            track.stop();
          }
          ({ imageData, track } = canvasStream({ color }));

          await sender.replaceTrack(track);
          let replacedImageData = await verifyReplacedTrackColors();

          let diffs = [];
          let data = {
            color,
            everyCanvasImageDataIndexMatchesReplacedTrackVideoImageData: imageData.data.every(
              (value, index) => {
                const _color = replacedImageData.data[index];
                const diff = Math.max(_color, value) - Math.min(_color, value);
                if (diff > 0) {
                  diffs.push({ index, diffs });
                }
                return diff === 0;
              }
            ),
          };
          Object.assign(data, ...diffs);
          result.push(data);
          track.enabled = false;
          track.stop();
        }
        pc1.close();
        pc2.close();
        console.log(
          result,
          result.length,
          result.filter(
            ({
              everyCanvasImageDataIndexMatchesReplacedTrackVideoImageData: bool,
            }) => bool
          ).length
        );
      })();
    </script>
  </body>
</html>

Results after 5 runs where the left column is the number of colors tested (148) and the right column is the number of strict equal results for every index of the respective Uint8ClampedArrays from getImageData() are set with the exact same values

148 12
148 18
148 14
148 16
148 14

as is clear from this version of the test every value at every index match exactly an averge of 10% for the 5 runs.

What is the exact criteria of the test? How is pass and fail determined?

What is the expected result?

@guest271314
Copy link
Contributor

For Nightly, substituting createImageBitmap() for ImageCapture.grabFrame() some colors are apparently drawn

148 1
148 1
148 1
148 1
148 1


@guest271314
Copy link
Contributor

Re using canvas for tests (without addressing) see Get image data url in JavaScript? https://stackoverflow.com/a/42916772

When drawn on a canvas, the passed image is uncompressed + all pre-multiplied.
When exported, its uncompressed or recompressed with a different algorithm, and un-multiplied.

All browsers and devices will have different rounding errors happening in this process
(see Canvas fingerprinting).

Reconstruct original 16-bit Raw pixel data from the HTML5 canvas https://stackoverflow.com/a/43413784

No.

When drawn to a canvas the image is uncompressed, and all the pixels data are pre-multiplied and converted to 24-bit data + an 8-bit alpha channel (RGBA).
In this process the image looses everything from the original, even for same color depth original image because of various rounding errors (See Canvas fingerprinting.)

So even lossless formats are loosy on a canvas.

If you need the raw data, you'll need to write a parser yourself and treat directly your images files as arrayBuffer.

Is canvas getImageData method machine/browser dependent? https://stackoverflow.com/a/26615864

Yes. This fact is exploited by canvas fingerprinting:

The same HTML5 Canvas element can produce exceptional pixels on a different web browsers, depending on the system on which it was executed.

This happens for several reasons: at the image format level — web browsers uses different image processing engines, export options, compression level, final images may got different hashes even if they are pixel-perfect; at the pixmap level — operating systems use different algorithms and settings for anti-aliasing and sub-pixel rendering. We don't know all the reasons, but we have already collected more than a thousand unique signatures.

Errata

Chromium behaviour of a captured canvas is observably different from Firefox behaviour. The CanvasCaptureMediaStreamTrack toggles between mute and unmute (the events are actually fired) at Chromium, where Firefox does not fire the events for the same code. With a WebRTC PeerConnection awaiting the unmute event of a remote track is critical, and can take an arbitrary amount of time to fire once connection is made, you can run https://plnkr.co/edit/Axkb8s?preview to observe the behaviour for yourself at Chromium. Note also, that Chromium takes substantially more time to process image data than Firefox; it there was a timeout occurring would not be surprised, e.g., consider attempting to capture every frame of a 33 second video,

Nightly 76

Object { originalVideoDuration: 33.423, elapsedTimeToSeekAllVideoFrames: 46.987 }
ended
Object { elapsedTimeToStreamAllVideoFrames: 33.536, originalVideoDuration: 33.423 }

Chromium 82

{originalVideoDuration: 33.394, elapsedTimeToSeekAllVideoFrames: 267.686}
{elapsedTimeToStreamAllVideoFrames: 33.406, originalVideoDuration: 33.394}

notice that Chromium 82 took over 4 minutes to render and capture each image of a 33 second video.

Transmittal of MediaStreamTrack data at Firefox has issues too, is not an immediate occurrence. Firefox and Nightly have some form of a delay before transmission of the MediaStream occurs

https://gist.github.com/guest271314/c38042935db4e0131c1e0b68ca59f4ac#gistcomment-3241753

Hey, I saw that on firefox MediaSource has about a 2 second delay (lag) playing my stream. I only found this: https://bugzilla.mozilla.org/show_bug.cgi?id=1340302 related to it, but no solution... do you know a way around that lag? Chrome I have <0.2sec
thanks

Writing MediaStream, MediaStreamTrack code that includes edge cases and problems to overcome that might not be immediately clear whatsoever when just reading the specifications.

For the tests demonstrating how fast Firefox is at processing images and video, or conversely, how slow Chromium is at processing images, if the tab does not freeze or crash, see https://bugs.chromium.org/p/chromium/issues/detail?id=1065675 and Issue 1063681: Using video.requestAnimationFrame() with OffscreenCanvas and createImageBitmap() freezes video presentation https://bugs.chromium.org/p/chromium/issues/detail?id=1063681 and Issue 1065720: Chromium never sets ended attribute or fires ended event on HTMLMediaElement when video is produced by Chromium MediaRecorder - Nightly does get duration, set ended attribute, fires ended event with same file https://bugs.chromium.org/p/chromium/issues/detail?id=1065720, where a user will never get the last frame of a video at

Firefox might throw an error as well when attempting to connect to a remote track, e.g., https://bugzilla.mozilla.org/show_bug.cgi?id=1212237.

Chromium will output a Blob with size 0 when a MediaStream is not set as srcObject of a <video> when using MediaRecorder.

In short, as the author of the test is probably well aware of, it is non-trivial, yet illuminating, to compose WebRTC, or MediaStream, MediaStreamTrack code that is intended to be run and get same output at Chromium and Firefox - particularly using the same code, unmodified for the edge case described above, and the various other incompatibilities that encountered writing code at

@guest271314
Copy link
Contributor

Note also, OffscreenCanvas is behing a Prefernce at Nightly https://bugzilla.mozilla.org/show_bug.cgi?id=1609238#c3

This verifies that replaceTrack() actually replaces the track.
Adds a new helper function to add a "signal" square into a noise track.

Bug: none
Change-Id: Ia90535c984d65adcdf2c63a5700b08d7c1e384c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2141913
Commit-Queue: Harald Alvestrand <[email protected]>
Reviewed-by: Guido Urdaneta <[email protected]>
Cr-Commit-Position: refs/heads/master@{#758062}
@foolip foolip force-pushed the chromium-export-cl-2141913 branch from 5f88979 to 499a34a Compare April 27, 2020 13:16
@foolip
Copy link
Member

foolip commented Apr 28, 2020

wpt-firefox-nightly-stability timed out and failed after 2 hours. This is because of #7660. I'm going to admin merge this.

@foolip
Copy link
Member

foolip commented Apr 28, 2020

Actually, there's a regression of a test in Safari that we should look at first:
https://wpt.fyi/results/webrtc/RTCPeerConnection-setRemoteDescription.html?diff&filter=ADC&run_id=499370001&run_id=497460002

@foolip
Copy link
Member

foolip commented Apr 28, 2020

@guest271314 PRs with the label chromium-export are created and approved by a bot, and will be merged as soon as the corresponding change in Chromium has been reviewed and landed. Most of the time no human will look at these PRs, so any comments will be missed. The only changes we make in these PRs are ones necessary to get them landed, any changes beyond that we'd make separately. Otherwise it can easily get confusing about where a change came from.

if (v.videoWidth < 21 || v.videoHeight < 21) {
return null;
}
const canvas = new OffscreenCanvas(v.videoWidth, v.videoHeight);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alvestrand is it necessary to use OffscreenCanvas here? In https://wpt.fyi/results/webrtc/RTCRtpSender-replaceTrack.https.html?label=pr_head&max-count=1&pr=22779 it looks like Firefox and Safari fail this test because of this. Could a regular canvas be used just as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if canvas is used the expectation of the test is not clear where the codec used is lossy: why is a specific pixel expected to be in an exact coordinate when using a lossy video codec.

The timeout is probably related to loadedmetadata never being fired in the code in this PR see w3c/webrtc-pc#2506 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detectSignal allows for an error margin of 1, but good point that this may not always hold.

I don't see a timeout in the test results so there isn't anything to investigate there I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test paints a box in a specific color, and then checks that the middle of that box is the expected color (or close to it). The margin should be big enough that even edge-blurring codecs don't mess things up; if the video width and height are the same (no resize), the box should be in the same place.

The amount of color disparity one can expect should be tested; good point. Later CL.

@alvestrand
Copy link
Contributor

Note: The RTCDataChannel flake is a real flake, but it's completely unrelated to this thread.
(was alerted to this thread)

@foolip
Copy link
Member

foolip commented Apr 30, 2020

Thanks for taking a look, @alvestrand! I'll go ahead and merge this now, so that the follow-up fixes can be made more easily. I'll send a PR for the one thing I think I can fix myself.

I checked the seeming regression in Safari mentioned in #22779 (comment), but using the "Show History" button on https://wpt.fyi/results/webrtc/RTCPeerConnection-setRemoteDescription.html I can see this test was already flaky on master:
image

@foolip foolip merged commit 0de19dc into master Apr 30, 2020
@foolip foolip deleted the chromium-export-cl-2141913 branch April 30, 2020 08:04
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request May 15, 2020
…eData()` call, a=testonly

Automatic update from web-platform-tests
[webrtc] remove unnecessary `ctx.getImageData()` call (#23337)

See web-platform-tests/wpt#22779 (comment)
--

wpt-commits: a9f1e8c01979f0415e4f18396fa7b46d257a675a
wpt-pr: 23337
xeonchen pushed a commit to xeonchen/gecko that referenced this pull request May 15, 2020
…eData()` call, a=testonly

Automatic update from web-platform-tests
[webrtc] remove unnecessary `ctx.getImageData()` call (#23337)

See web-platform-tests/wpt#22779 (comment)
--

wpt-commits: a9f1e8c01979f0415e4f18396fa7b46d257a675a
wpt-pr: 23337
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request May 20, 2020
…eData()` call, a=testonly

Automatic update from web-platform-tests
[webrtc] remove unnecessary `ctx.getImageData()` call (#23337)

See web-platform-tests/wpt#22779 (comment)
--

wpt-commits: a9f1e8c01979f0415e4f18396fa7b46d257a675a
wpt-pr: 23337
xeonchen pushed a commit to xeonchen/gecko that referenced this pull request May 20, 2020
…eData()` call, a=testonly

Automatic update from web-platform-tests
[webrtc] remove unnecessary `ctx.getImageData()` call (#23337)

See web-platform-tests/wpt#22779 (comment)
--

wpt-commits: a9f1e8c01979f0415e4f18396fa7b46d257a675a
wpt-pr: 23337
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants