RTCPeerConnection-insertable-streams-audio.https.html (8239B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>RTCPeerConnection Insertable Streams Audio</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src=/resources/testdriver.js></script> 8 <script src=/resources/testdriver-vendor.js></script> 9 <script src='../../mediacapture-streams/permission-helper.js'></script> 10 <script src="../../webrtc/RTCPeerConnection-helper.js"></script> 11 <script src="./RTCPeerConnection-insertable-streams.js"></script> 12 </head> 13 <body> 14 <script> 15 async function testAudioFlow(t, negotiationFunction, setConstructorParam, perFrameCallback = () => {}) { 16 const caller = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {}); 17 t.add_cleanup(() => caller.close()); 18 const callee = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {}); 19 t.add_cleanup(() => callee.close()); 20 21 await setMediaPermission("granted", ["microphone"]); 22 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 23 const audioTrack = stream.getAudioTracks()[0]; 24 t.add_cleanup(() => audioTrack.stop()); 25 26 const audioSender = caller.addTrack(audioTrack) 27 const senderStreams = audioSender.createEncodedStreams(); 28 const senderReader = senderStreams.readable.getReader(); 29 const senderWriter = senderStreams.writable.getWriter(); 30 31 const frameInfos = []; 32 const numFramesPassthrough = 5; 33 const numFramesReplaceData = 5; 34 const numFramesModifyData = 5; 35 const numFramesToSend = numFramesPassthrough + numFramesReplaceData + numFramesModifyData; 36 37 let streamsCreatedAtNegotiation; 38 39 const ontrackPromise = new Promise(resolve => { 40 callee.ontrack = t.step_func(() => { 41 const audioReceiver = callee.getReceivers().find(r => r.track.kind === 'audio'); 42 assert_not_equals(audioReceiver, undefined); 43 44 let receiverReader; 45 let receiverWriter; 46 if (streamsCreatedAtNegotiation) { 47 const audioStreams = streamsCreatedAtNegotiation.find(r => r.kind === 'audio'); 48 assert_true(!!audioStreams); 49 receiverReader = audioStreams.streams.readable.getReader(); 50 receiverWriter = audioStreams.streams.writable.getWriter(); 51 } else { 52 const receiverStreams = 53 audioReceiver.createEncodedStreams(); 54 receiverReader = receiverStreams.readable.getReader(); 55 receiverWriter = receiverStreams.writable.getWriter(); 56 } 57 58 const maxFramesToReceive = numFramesToSend; 59 let numVerifiedFrames = 0; 60 for (let i = 0; i < maxFramesToReceive; i++) { 61 receiverReader.read().then(t.step_func(result => { 62 if (frameInfos[numVerifiedFrames] && 63 areFrameInfosEqual(result.value, frameInfos[numVerifiedFrames])) { 64 numVerifiedFrames++; 65 } else { 66 // Receiving unexpected frames is an indication that 67 // frames are not passed correctly between sender and receiver. 68 assert_unreached("Incorrect frame received"); 69 } 70 assert_not_equals(result.value.getMetadata().sequenceNumber, undefined); 71 72 if (numVerifiedFrames == numFramesToSend) 73 resolve(); 74 })); 75 } 76 }); 77 }); 78 79 exchangeIceCandidates(caller, callee); 80 await negotiationFunction(caller, callee, (streams) => {streamsCreatedAtNegotiation = streams;}); 81 82 // Pass frames as they come from the encoder. 83 for (let i = 0; i < numFramesPassthrough; i++) { 84 const result = await senderReader.read(); 85 const frame = result.value; 86 perFrameCallback(frame); 87 frameInfos.push({ 88 data: frame.data, 89 timestamp: frame.timestamp, 90 type: frame.type, 91 metadata: frame.getMetadata(), 92 getMetadata() { return this.metadata; } 93 }); 94 senderWriter.write(frame); 95 } 96 97 // Replace frame data with arbitrary buffers. 98 for (let i = 0; i < numFramesReplaceData; i++) { 99 const result = await senderReader.read() 100 const frame = result.value; 101 102 const buffer = new ArrayBuffer(100); 103 const int8View = new Int8Array(buffer); 104 int8View.fill(i); 105 106 frame.data = buffer; 107 perFrameCallback(frame); 108 frameInfos.push({ 109 data: frame.data, 110 timestamp: frame.timestamp, 111 type: frame.type, 112 metadata: frame.getMetadata(), 113 getMetadata() { return this.metadata; } 114 }); 115 senderWriter.write(frame); 116 } 117 118 // Modify frame data. 119 for (let i = 0; i < numFramesReplaceData; i++) { 120 const result = await senderReader.read() 121 const frame = result.value; 122 const int8View = new Int8Array(frame.data); 123 int8View.fill(i); 124 125 perFrameCallback(frame); 126 frameInfos.push({ 127 data: frame.data, 128 timestamp: frame.timestamp, 129 type: frame.type, 130 metadata: frame.getMetadata(), 131 getMetadata() { return this.metadata; } 132 }); 133 senderWriter.write(frame); 134 } 135 136 return ontrackPromise; 137 } 138 139 for (const setConstructorParam of [false, true]) { 140 promise_test(async t => { 141 return testAudioFlow(t, exchangeOfferAnswer, setConstructorParam); 142 }, 'Frames flow correctly using insertable streams' + (setConstructorParam ? ' with param' : '')); 143 144 promise_test(async t => { 145 return testAudioFlow(t, exchangeOfferAnswerReverse, setConstructorParam); 146 }, 'Frames flow correctly using insertable streams when receiver starts negotiation' + (setConstructorParam ? ' with param' : '')); 147 } 148 149 promise_test(async t => { 150 const caller = new RTCPeerConnection({encodedInsertableStreams:true}); 151 t.add_cleanup(() => caller.close()); 152 const callee = new RTCPeerConnection(); 153 t.add_cleanup(() => callee.close()); 154 155 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 156 const track = stream.getTracks()[0]; 157 t.add_cleanup(() => track.stop()); 158 159 const sender = caller.addTrack(track) 160 const streams = sender.createEncodedStreams(); 161 const transformer = new TransformStream({ 162 transform(frame, controller) { 163 // Inserting the same frame twice will result in failure since the frame 164 // will be neutered after the first insertion is processed. 165 controller.enqueue(frame); 166 controller.enqueue(frame); 167 } 168 }); 169 170 exchangeIceCandidates(caller, callee); 171 await exchangeOfferAnswer(caller, callee); 172 173 await promise_rejects_dom( 174 t, 'OperationError', 175 streams.readable.pipeThrough(transformer).pipeTo(streams.writable)); 176 }, 'Enqueuing the same frame twice fails'); 177 178 promise_test(async t => { 179 const caller = new RTCPeerConnection({encodedInsertableStreams:true}); 180 t.add_cleanup(() => caller.close()); 181 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 182 const track = stream.getTracks()[0]; 183 t.add_cleanup(() => track.stop()); 184 const sender = caller.addTrack(track) 185 sender.createEncodedStreams(); 186 assert_throws_dom("InvalidStateError", () => sender.createEncodedStreams()); 187 }, 'Creating streams twice throws'); 188 189 promise_test(async t => { 190 let clonedFrames = []; 191 function verifyFramesSerializeAndDeserialize(frame) { 192 // Clone encoded frames using structedClone (ie serialize + deserialize) and 193 // keep a reference. 194 const clone = structuredClone(frame); 195 clonedFrames.push(clone); 196 }; 197 198 await testAudioFlow( 199 t, exchangeOfferAnswer, /*setConstructorParam=*/false, verifyFramesSerializeAndDeserialize); 200 201 // Ensure all of our cloned frames are still alive and well, despite the 202 // originals having been sent through the PeerConnection. 203 clonedFrames.forEach((clonedFrame) => { 204 assert_not_equals(clonedFrame.data.size, 0); 205 assert_not_equals(clonedFrame.timestamp, 0); 206 }); 207 }, 'Encoded frames serialize and deserialize into a deep clone'); 208 209 promise_test(async t => { 210 let clonedFrames = []; 211 function rewriteFrameTimestamps(frame) { 212 // Add 1 to the rtp timestamp of the frame. 213 const metadata = frame.getMetadata(); 214 metadata.rtpTimestamp += 1; 215 frame.setMetadata(metadata); 216 217 assert_equals(frame.getMetadata().rtpTimestamp, metadata.rtpTimestamp) 218 }; 219 220 // Run audio flows which will assert that the frames received have the 221 // rtp timestamp set by our modification. 222 await testAudioFlow( 223 t, exchangeOfferAnswer, /*setConstructorParam=*/false, rewriteFrameTimestamps); 224 }, 'Modifying rtp timestamp'); 225 226 </script> 227 </body> 228 </html>