Commit 59c513fd authored by Guido Urdaneta's avatar Guido Urdaneta Committed by Commit Bot

[RTCInsertableStreams] Expose metadata fields for video frames

Also enable the GFD extension in tests so that all the metadata and type
fields are reported correctly.

Drive-by: test cleanups.

Bug: 1069295
Change-Id: I9ffb5c9874c9f8556785a0664ee010d3912f866a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207162Reviewed-by: default avatarMarina Ciocea <marinaciocea@chromium.org>
Reviewed-by: default avatarHarald Alvestrand <hta@chromium.org>
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Cr-Commit-Position: refs/heads/master@{#771727}
parent 16a5efcd
...@@ -41,13 +41,27 @@ RTCEncodedVideoFrameMetadata* RTCEncodedVideoFrame::getMetadata() const { ...@@ -41,13 +41,27 @@ RTCEncodedVideoFrameMetadata* RTCEncodedVideoFrame::getMetadata() const {
RTCEncodedVideoFrameMetadata* metadata = RTCEncodedVideoFrameMetadata* metadata =
RTCEncodedVideoFrameMetadata::Create(); RTCEncodedVideoFrameMetadata::Create();
metadata->setSynchronizationSource(delegate_->Ssrc()); metadata->setSynchronizationSource(delegate_->Ssrc());
const auto* webrtc_metadata = delegate_->GetMetadata();
if (!webrtc_metadata)
return metadata;
if (webrtc_metadata->GetFrameId())
metadata->setFrameId(*webrtc_metadata->GetFrameId());
Vector<int64_t> dependencies;
for (const auto& dependency : webrtc_metadata->GetFrameDependencies())
dependencies.push_back(dependency);
metadata->setDependencies(dependencies);
metadata->setWidth(webrtc_metadata->GetWidth());
metadata->setHeight(webrtc_metadata->GetHeight());
metadata->setSpatialIndex(webrtc_metadata->GetSpatialIndex());
metadata->setTemporalIndex(webrtc_metadata->GetTemporalIndex());
return metadata; return metadata;
} }
DOMArrayBuffer* RTCEncodedVideoFrame::additionalData() const { DOMArrayBuffer* RTCEncodedVideoFrame::additionalData() const {
if (!additional_data_) if (!additional_data_)
additional_data_ = delegate_->CreateAdditionalDataBuffer(); additional_data_ = delegate_->CreateAdditionalDataBuffer();
return additional_data_; return additional_data_;
} }
......
...@@ -80,6 +80,12 @@ uint32_t RTCEncodedVideoFrameDelegate::Ssrc() const { ...@@ -80,6 +80,12 @@ uint32_t RTCEncodedVideoFrameDelegate::Ssrc() const {
return webrtc_frame_ ? webrtc_frame_->GetSsrc() : 0; return webrtc_frame_ ? webrtc_frame_->GetSsrc() : 0;
} }
const webrtc::VideoFrameMetadata* RTCEncodedVideoFrameDelegate::GetMetadata()
const {
MutexLocker lock(mutex_);
return webrtc_frame_ ? &webrtc_frame_->GetMetadata() : nullptr;
}
std::unique_ptr<webrtc::TransformableVideoFrameInterface> std::unique_ptr<webrtc::TransformableVideoFrameInterface>
RTCEncodedVideoFrameDelegate::PassWebRtcFrame() { RTCEncodedVideoFrameDelegate::PassWebRtcFrame() {
MutexLocker lock(mutex_); MutexLocker lock(mutex_);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h" #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h" #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
#include "third_party/webrtc/api/frame_transformer_interface.h" #include "third_party/webrtc/api/frame_transformer_interface.h"
#include "third_party/webrtc/api/video/video_frame_metadata.h"
namespace blink { namespace blink {
...@@ -35,6 +36,7 @@ class RTCEncodedVideoFrameDelegate ...@@ -35,6 +36,7 @@ class RTCEncodedVideoFrameDelegate
void SetData(const DOMArrayBuffer* data); void SetData(const DOMArrayBuffer* data);
DOMArrayBuffer* CreateAdditionalDataBuffer() const; DOMArrayBuffer* CreateAdditionalDataBuffer() const;
uint32_t Ssrc() const; uint32_t Ssrc() const;
const webrtc::VideoFrameMetadata* GetMetadata() const;
std::unique_ptr<webrtc::TransformableVideoFrameInterface> PassWebRtcFrame(); std::unique_ptr<webrtc::TransformableVideoFrameInterface> PassWebRtcFrame();
private: private:
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
[Serializable] [Serializable]
dictionary RTCEncodedVideoFrameMetadata { dictionary RTCEncodedVideoFrameMetadata {
long long frame_id; long long frameId;
sequence<long long> dependencies; sequence<long long> dependencies;
unsigned short width; unsigned short width;
unsigned short height; unsigned short height;
long spatial_index; long spatialIndex;
long temporal_index; long temporalIndex;
unsigned long synchronizationSource; unsigned long synchronizationSource;
sequence<unsigned long> contributingSources; sequence<unsigned long> contributingSources;
}; };
...@@ -68,11 +68,14 @@ async function testVideoFlow(t, negotiationFunction) { ...@@ -68,11 +68,14 @@ async function testVideoFlow(t, negotiationFunction) {
// Pass frames as they come from the encoder. // Pass frames as they come from the encoder.
for (let i = 0; i < numFramesPassthrough; i++) { for (let i = 0; i < numFramesPassthrough; i++) {
const result = await senderReader.read() const result = await senderReader.read();
const metadata = result.value.getMetadata();
assert_true(containsVideoMetadata(metadata));
frameInfos.push({ frameInfos.push({
timestamp: result.value.timestamp, timestamp: result.value.timestamp,
type: result.value.type,
data: result.value.data, data: result.value.data,
metadata: result.value.getMetadata(), metadata: metadata,
getMetadata() { return this.metadata; } getMetadata() { return this.metadata; }
}); });
senderWriter.write(result.value); senderWriter.write(result.value);
...@@ -80,7 +83,9 @@ async function testVideoFlow(t, negotiationFunction) { ...@@ -80,7 +83,9 @@ async function testVideoFlow(t, negotiationFunction) {
// Replace frame data with arbitrary buffers. // Replace frame data with arbitrary buffers.
for (let i = 0; i < numFramesReplaceData; i++) { for (let i = 0; i < numFramesReplaceData; i++) {
const result = await senderReader.read() const result = await senderReader.read();
const metadata = result.value.getMetadata();
assert_true(containsVideoMetadata(metadata));
const buffer = new ArrayBuffer(100); const buffer = new ArrayBuffer(100);
const int8View = new Int8Array(buffer); const int8View = new Int8Array(buffer);
int8View.fill(i); int8View.fill(i);
...@@ -88,8 +93,9 @@ async function testVideoFlow(t, negotiationFunction) { ...@@ -88,8 +93,9 @@ async function testVideoFlow(t, negotiationFunction) {
result.value.data = buffer; result.value.data = buffer;
frameInfos.push({ frameInfos.push({
timestamp: result.value.timestamp, timestamp: result.value.timestamp,
type: result.value.type,
data: result.value.data, data: result.value.data,
metadata: result.value.getMetadata(), metadata: metadata,
getMetadata() { return this.metadata; } getMetadata() { return this.metadata; }
}); });
senderWriter.write(result.value); senderWriter.write(result.value);
...@@ -97,14 +103,17 @@ async function testVideoFlow(t, negotiationFunction) { ...@@ -97,14 +103,17 @@ async function testVideoFlow(t, negotiationFunction) {
// Modify frame data. // Modify frame data.
for (let i = 0; i < numFramesReplaceData; i++) { for (let i = 0; i < numFramesReplaceData; i++) {
const result = await senderReader.read() const result = await senderReader.read();
const metadata = result.value.getMetadata();
assert_true(containsVideoMetadata(metadata));
const int8View = new Int8Array(result.value.data); const int8View = new Int8Array(result.value.data);
int8View.fill(i); int8View.fill(i);
frameInfos.push({ frameInfos.push({
timestamp: result.value.timestamp, timestamp: result.value.timestamp,
type: result.value.type,
data: result.value.data, data: result.value.data,
metadata: result.value.getMetadata(), metadata: metadata,
getMetadata() { return this.metadata; } getMetadata() { return this.metadata; }
}); });
senderWriter.write(result.value); senderWriter.write(result.value);
...@@ -391,6 +400,7 @@ promise_test(async t => { ...@@ -391,6 +400,7 @@ promise_test(async t => {
const result = await senderReader.read() const result = await senderReader.read()
sentFrameInfo = { sentFrameInfo = {
timestamp: result.value.timestamp, timestamp: result.value.timestamp,
type: result.value.type,
data: result.value.data, data: result.value.data,
metadata: result.value.getMetadata(), metadata: result.value.getMetadata(),
getMetadata() { return this.metadata; } getMetadata() { return this.metadata; }
......
...@@ -13,36 +13,85 @@ function areArrayBuffersEqual(buffer1, buffer2) ...@@ -13,36 +13,85 @@ function areArrayBuffersEqual(buffer1, buffer2)
return true; return true;
} }
function areMetadataEqual(metadata1, metadata2) { function areArraysEqual(a1, a2) {
return metadata1.synchronizationSource === metadata2.synchronizationSource; if (a1 === a1)
return true;
if (a1.length != a2.length)
return false;
for (let i = 0; i < a1.length; i++) {
if (a1[i] != a2[i])
return false;
}
return true;
}
function areMetadataEqual(metadata1, metadata2, type) {
return metadata1.synchronizationSource === metadata2.synchronizationSource &&
areArraysEqual(metadata1.contributingSources, metadata2.contributingSources) &&
metadata1.frameId === metadata2.frameId &&
areArraysEqual(metadata1.dependencies, metadata2.dependencies) &&
metadata1.spatialIndex === metadata2.spatialIndex &&
metadata1.temporalIndex === metadata2.temporalIndex &&
// Width and height are reported only for key frames on the receiver side.
type == "key"
? metadata1.width === metadata2.width && metadata1.height === metadata2.height
: true;
} }
function areFrameInfosEqual(frame1, frame2) { function areFrameInfosEqual(frame1, frame2) {
return frame1.timestamp === frame2.timestamp && return frame1.timestamp === frame2.timestamp &&
areMetadataEqual(frame1.getMetadata(), frame2.getMetadata()) && frame1.type === frame2.type &&
areMetadataEqual(frame1.getMetadata(), frame2.getMetadata(), frame1.type) &&
areArrayBuffersEqual(frame1.data, frame2.data); areArrayBuffersEqual(frame1.data, frame2.data);
} }
function containsVideoMetadata(metadata) {
return metadata.synchronizationSource !== undefined &&
metadata.width !== undefined &&
metadata.height !== undefined &&
metadata.spatialIndex !== undefined &&
metadata.temporalIndex !== undefined &&
metadata.dependencies !== undefined;
}
function enableGFD(sdp) {
const FRAME_MARKER_EXTENSION =
'http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07';
const GFD_V00_EXTENSION =
'http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00';
if (sdp.indexOf(GFD_V00_EXTENSION) !== -1)
return sdp;
// Replace the frame marker extension, which is unused with the GFD extension.
return sdp.split(FRAME_MARKER_EXTENSION).join(GFD_V00_EXTENSION);
}
async function exchangeOfferAnswer(pc1, pc2) { async function exchangeOfferAnswer(pc1, pc2) {
const offer = await pc2.createOffer({offerToReceiveAudio: true, offerToReceiveVideo: true}); const offer = await pc1.createOffer();
// TODO(crbug.com/1066819): remove this hack when we do not receive duplicates from RTX // Munge the SDP to enable the GFD extension in order to get correct metadata.
// anymore. const sdpGFD = enableGFD(offer.sdp);
await pc1.setLocalDescription({type: offer.type, sdp: sdpGFD});
// Munge the SDP to disable bandwidth probing via RTX. // Munge the SDP to disable bandwidth probing via RTX.
const sdp = offer.sdp.replace(new RegExp('rtx', 'g'), 'invalid'); // TODO(crbug.com/1066819): remove this hack when we do not receive duplicates from RTX
await pc2.setLocalDescription(offer); // anymore.
await pc1.setRemoteDescription({type: 'offer', sdp}); const sdpRTX = sdpGFD.replace(new RegExp('rtx', 'g'), 'invalid');
await pc2.setRemoteDescription({type: 'offer', sdp: sdpRTX});
const answer = await pc1.createAnswer(); const answer = await pc2.createAnswer();
await pc1.setLocalDescription(answer); await pc2.setLocalDescription(answer);
await pc2.setRemoteDescription(answer); await pc1.setRemoteDescription(answer);
} }
async function exchangeOfferAnswerReverse(pc1, pc2) { async function exchangeOfferAnswerReverse(pc1, pc2) {
const offer = await pc2.createOffer({offerToReceiveAudio: true, offerToReceiveVideo: true}); const offer = await pc2.createOffer({offerToReceiveAudio: true, offerToReceiveVideo: true});
// Munge the SDP to enable the GFD extension in order to get correct metadata.
const sdpGFD = enableGFD(offer.sdp);
// Munge the SDP to disable bandwidth probing via RTX. // Munge the SDP to disable bandwidth probing via RTX.
const sdp = offer.sdp.replace(new RegExp('rtx', 'g'), 'invalid'); // TODO(crbug.com/1066819): remove this hack when we do not receive duplicates from RTX
await pc1.setRemoteDescription({type: 'offer', sdp}); // anymore.
await pc2.setLocalDescription(offer); const sdpRTX = sdpGFD.replace(new RegExp('rtx', 'g'), 'invalid');
await pc1.setRemoteDescription({type: 'offer', sdp: sdpRTX});
await pc2.setLocalDescription({type: 'offer', sdp: sdpGFD});
const answer = await pc1.createAnswer(); const answer = await pc1.createAnswer();
await pc2.setRemoteDescription(answer); await pc2.setRemoteDescription(answer);
......
...@@ -2,7 +2,6 @@ onmessage = async (event) => { ...@@ -2,7 +2,6 @@ onmessage = async (event) => {
const readableStream = event.data.readableStream; const readableStream = event.data.readableStream;
const reader = readableStream.getReader(); const reader = readableStream.getReader();
const result = await reader.read(); const result = await reader.read();
console.log('WORKER metadata = ', result.value.getMetadata().synchronizationSource);
// Post an object with individual fields so that the test side has // Post an object with individual fields so that the test side has
// values to verify the serialization of the RTCEncodedVideoFrame. // values to verify the serialization of the RTCEncodedVideoFrame.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment