RTCEncodedAudioFrame-metadata.https.html (8665B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>RTCEncodedAudioFrame can be cloned and distributed</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src=/resources/testdriver.js></script> 7 <script src=/resources/testdriver-vendor.js></script> 8 <script src='../../mediacapture-streams/permission-helper.js'></script> 9 <script src="../../webrtc/RTCPeerConnection-helper.js"></script> 10 <script src="../../service-workers/service-worker/resources/test-helpers.sub.js"></script> 11 <script src='RTCEncodedFrame-timestamps-helper.js'></script> 12 13 <script> 14 "use strict"; 15 promise_test(async t => { 16 const caller1 = new RTCPeerConnection(); 17 t.add_cleanup(() => caller1.close()); 18 const callee1 = new RTCPeerConnection({encodedInsertableStreams:true}); 19 t.add_cleanup(() => callee1.close()); 20 await setMediaPermission("granted", ["microphone"]); 21 const inputStream = await navigator.mediaDevices.getUserMedia({audio:true}); 22 const inputTrack = inputStream.getAudioTracks()[0]; 23 t.add_cleanup(() => inputTrack.stop()); 24 caller1.addTrack(inputTrack) 25 exchangeIceCandidates(caller1, callee1); 26 27 const caller2 = new RTCPeerConnection({encodedInsertableStreams:true}); 28 t.add_cleanup(() => caller2.close()); 29 const sender2 = caller2.addTransceiver("audio").sender; 30 const writer2 = sender2.createEncodedStreams().writable.getWriter(); 31 sender2.replaceTrack(new MediaStreamTrackGenerator({ kind: 'audio' })); 32 33 const framesReceivedCorrectly = new Promise((resolve, reject) => { 34 callee1.ontrack = async e => { 35 const receiverStreams = e.receiver.createEncodedStreams(); 36 const receiverReader = receiverStreams.readable.getReader(); 37 const result = await receiverReader.read(); 38 const original = result.value; 39 let newFrame = new RTCEncodedAudioFrame(original); 40 assert_true(original.getMetadata().hasOwnProperty('receiveTime')); 41 assert_true(original.getMetadata().receiveTime > 0); 42 assert_equals(original.getMetadata().rtpTimestamp, newFrame.getMetadata().rtpTimestamp); 43 assert_equals(original.getMetadata().captureTime, newFrame.getMetadata().captureTime); 44 assert_equals(original.getMetadata().receiveTime, newFrame.getMetadata().receiveTime); 45 assert_true(original.getMetadata().hasOwnProperty('audioLevel')); 46 assert_equals(original.getMetadata().audioLevel, newFrame.getMetadata().audioLevel); 47 assert_array_equals(Array.from(original.data), Array.from(newFrame.data)); 48 await writer2.write(newFrame); 49 resolve(); 50 } 51 }); 52 53 await exchangeOfferAnswer(caller1, callee1); 54 55 return framesReceivedCorrectly; 56 }, "Constructing audio frame before sending works"); 57 58 promise_test(async t => { 59 const caller1 = new RTCPeerConnection(); 60 61 t.add_cleanup(() => caller1.close()); 62 const callee1 = new RTCPeerConnection({encodedInsertableStreams:true}); 63 t.add_cleanup(() => callee1.close()); 64 await setMediaPermission("granted", ["microphone"]); 65 const inputStream = await navigator.mediaDevices.getUserMedia({audio:true}); 66 const inputTrack = inputStream.getAudioTracks()[0]; 67 t.add_cleanup(() => inputTrack.stop()); 68 caller1.addTrack(inputTrack) 69 exchangeIceCandidates(caller1, callee1); 70 71 const caller2 = new RTCPeerConnection({encodedInsertableStreams:true}); 72 t.add_cleanup(() => caller2.close()); 73 const sender2 = caller2.addTransceiver("audio").sender; 74 const writer2 = sender2.createEncodedStreams().writable.getWriter(); 75 sender2.replaceTrack(new MediaStreamTrackGenerator({ kind: 'audio' })); 76 77 const framesReceivedCorrectly = new Promise((resolve, reject) => { 78 callee1.ontrack = async e => { 79 const receiverStreams = e.receiver.createEncodedStreams(); 80 const receiverReader = receiverStreams.readable.getReader(); 81 const result = await receiverReader.read(); 82 const original = result.value; 83 let newMetadata = original.getMetadata(); 84 newMetadata.rtpTimestamp = newMetadata.rtpTimestamp + 1; 85 let newFrame = new RTCEncodedAudioFrame(original, {metadata: newMetadata}); 86 assert_not_equals(original.getMetadata().rtpTimestamp, newFrame.getMetadata().rtpTimestamp); 87 assert_equals(newMetadata.rtpTimestamp, newFrame.getMetadata().rtpTimestamp); 88 assert_equals(original.getMetadata().receiveTime, newFrame.getMetadata().receiveTime); 89 assert_equals(original.getMetadata().captureTime, newFrame.getMetadata().captureTime); 90 assert_equals(original.getMetadata().audioLevel, newFrame.getMetadata().audioLevel); 91 assert_array_equals(Array.from(original.data), Array.from(newFrame.data)); 92 await writer2.write(newFrame); 93 resolve(); 94 } 95 }); 96 97 await exchangeOfferAnswer(caller1, callee1); 98 99 return framesReceivedCorrectly; 100 }, "Constructing audio frame with metadata argument before sending works"); 101 102 promise_test(async t => { 103 const caller1 = new RTCPeerConnection(); 104 t.add_cleanup(() => caller1.close()); 105 const callee1 = new RTCPeerConnection({encodedInsertableStreams:true}); 106 t.add_cleanup(() => callee1.close()); 107 await setMediaPermission("granted", ["microphone"]); 108 const inputStream = await navigator.mediaDevices.getUserMedia({audio:true}); 109 const inputTrack = inputStream.getAudioTracks()[0]; 110 t.add_cleanup(() => inputTrack.stop()); 111 caller1.addTrack(inputTrack) 112 exchangeIceCandidates(caller1, callee1); 113 114 const caller2 = new RTCPeerConnection({encodedInsertableStreams:true}); 115 t.add_cleanup(() => caller2.close()); 116 const sender2 = caller2.addTransceiver("audio").sender; 117 const writer2 = sender2.createEncodedStreams().writable.getWriter(); 118 sender2.replaceTrack(new MediaStreamTrackGenerator({ kind: 'audio' })); 119 120 const framesReceivedCorrectly = new Promise((resolve, reject) => { 121 callee1.ontrack = async e => { 122 const receiverStreams = e.receiver.createEncodedStreams(); 123 const receiverReader = receiverStreams.readable.getReader(); 124 const result = await receiverReader.read(); 125 const original = result.value; 126 let newMetadata = original.getMetadata(); 127 newMetadata.synchronizationSource = newMetadata.synchronizationSource + 1; 128 assert_throws_dom("InvalidModificationError", () => new RTCEncodedAudioFrame(original, {metadata: newMetadata})); 129 resolve(); 130 } 131 }); 132 133 await exchangeOfferAnswer(caller1, callee1); 134 135 return framesReceivedCorrectly; 136 }, "Constructing audio frame with bad metadata argument before sending does not work"); 137 138 promise_test(async t => { 139 const kAudioLevel = 0.5; 140 const kCaptureTime = 12345; 141 const pc1 = new RTCPeerConnection({encodedInsertableStreams:true}); 142 t.add_cleanup(() => pc1.close()); 143 const pc2 = new RTCPeerConnection({encodedInsertableStreams:true}); 144 t.add_cleanup(() => pc2.close()); 145 146 exchangeIceCandidates(pc1, pc2); 147 148 let numFrames = 0; 149 let audioLevelRead = new Promise((resolve, reject) => { 150 pc2.ontrack = t.step_func(e => { 151 const receiverTransformer = new TransformStream({ 152 async transform(encodedFrame, controller) { 153 const metadata = encodedFrame.getMetadata(); 154 if (metadata.audioLevel === undefined) { 155 reject("No audioLevel"); 156 } else if (metadata.audioLevel < kAudioLevel - 0.1 || metadata.audioLevel > kAudioLevel + 0.1) { 157 reject("Unexpected audioLevel"); 158 } 159 if (metadata.captureTime < kCaptureTime - 1 || metadata.captureTime > kCaptureTime + 1) { 160 reject("Unexpected captureTime"); 161 } 162 controller.enqueue(encodedFrame); 163 if (++numFrames == 10) 164 resolve(); 165 } 166 }); 167 const receiverStreams = e.receiver.createEncodedStreams(); 168 receiverStreams.readable 169 .pipeThrough(receiverTransformer) 170 .pipeTo(receiverStreams.writable); 171 }); 172 }); 173 174 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 175 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 176 const sender = pc1.addTrack(stream.getAudioTracks()[0]); 177 const senderStreams = sender.createEncodedStreams(); 178 const senderTransformer = new TransformStream({ 179 async transform(encodedFrame, controller) { 180 let metadata = encodedFrame.getMetadata(); 181 metadata.audioLevel = kAudioLevel; 182 metadata.captureTime = kCaptureTime; 183 controller.enqueue(new RTCEncodedAudioFrame(encodedFrame, {metadata})); 184 } 185 }); 186 senderStreams.readable 187 .pipeThrough(senderTransformer) 188 .pipeTo(senderStreams.writable); 189 190 await addAbsCaptureTimeAndExchangeOffer(pc1, pc2); 191 await checkAbsCaptureTimeAndExchangeAnswer(pc1, pc2, true); 192 193 await audioLevelRead; 194 }, 'Basic simulcast setup with three spatial layers'); 195 196 </script>