| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Audio latency tracing page</title> |
| </head> |
| |
| <body> |
| <div id="container"> |
| <p> This page is meant to serve as an example of how to use "audio.latency" |
| tracing to measure internal audio latency. The "audio.latency" category |
| enables code which listens for jumps in amplitude (volume) and starts or |
| stops tracing. We start tracing right after receiving loud audio from a |
| microphone, and stop right before sending that loud audio to speakers. |
| The duration of the trace event (which should show up as an |
| "AmplitudePeak" in the tracing tools) encompases the total internal latency. |
| </p> |
| <p>Instructions: |
| <ul> |
| <li>[Prerequisite] Close all other tabs but this one.</li> |
| <li>[Prerequisite] Make sure there is a microphone plugged into the test |
| machine, and that the surrounding environment is not too loud.</li> |
| <li>Open chrome://tracing and start recording a trace which includes the |
| "audio.latency" category.</li> |
| <li>Click the "Initialize" button.</li> |
| <li>Select either the WebAudio or the HTMLAudioElement button.</li> |
| <li>Repeatedly clap next to the microphone a few times. Make sure to clap |
| clearly, and to leave time between claps (0.5s-1s should be enough).</li> |
| <li>Stop the trace. "AmplitudePeak" events should show up under the audio |
| service process</li> |
| </ul> |
| </p> |
| <p> |
| Note: The "audio.latency" category only expects one input and one output. |
| Multiple IOs will result in incoherent traces. Additionally, |
| tracing <b>*must*</b> be started before starting the test, or no traces will |
| be captured. Refreshing the page after starting a trace is also not enough: |
| one must verify that there are no InputStreams or OutputStreams alive, by |
| navigating to the "audio" tab of chrome://media-internals. Closing all tabs |
| and waiting 2-10s should be enough for all outstanding streams to close. |
| </p> |
| <button id="initBtn" onClick="init()">Initialize</button> |
| <br/> |
| <br/> |
| <div id="routeMsg"></div> |
| <div id="outputTypesDiv" style="visibility:hidden"> |
| <button id="mssnBtn" onClick="trackToMSSN()">Use WebAudio</button> |
| <button id="audioElementBtn" onClick="trackToAudioElement()">Use HTMLAudioElement</button> |
| </div> |
| <div id="errorMsg"></div> |
| </div> |
| |
| <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> |
| <script> |
| const constraints = { |
| audio: true, |
| video: false |
| }; |
| |
| let track; |
| let stream; |
| |
| let audioContext; |
| let streamNode; |
| |
| let audioElement; |
| |
| function displayRoute(msg) { |
| document.querySelector("#routeMsg").innerHTML = msg; |
| } |
| |
| function disable(id) { |
| document.querySelector(id).disabled = true; |
| } |
| |
| function show(id) { |
| document.querySelector(id).style.visibility = 'visible'; |
| } |
| |
| function hide(id) { |
| document.querySelector(id).style.visibility = 'hidden'; |
| } |
| |
| async function initAudioContext() { |
| if(!audioContext) { |
| audioContext = new AudioContext(); |
| } |
| } |
| |
| function handleSuccess() { |
| const audioTracks = stream.getAudioTracks(); |
| console.log(`Using Audio device: ${audioTracks[0].label}`); |
| console.log(audioTracks); |
| track = audioTracks[0]; |
| window.track = track; // make variable available to browser console |
| } |
| |
| function handleError(error) { |
| if (error.name === 'PermissionDeniedError') { |
| errorMsg('Permissions have not been granted to use your camera and ' + |
| 'microphone, you need to allow the page access to your devices in ' + |
| 'order for the demo to work.'); |
| } |
| errorMsg(`getUserMedia error: ${error.name}`, error); |
| } |
| |
| function errorMsg(msg, error) { |
| const errorElement = document.querySelector('#errorMsg'); |
| errorElement.innerHTML += `<p>${msg}</p>`; |
| if (typeof error !== 'undefined') { |
| console.error(error); |
| } |
| } |
| |
| async function init() { |
| try { |
| stream = await navigator.mediaDevices.getUserMedia(constraints); |
| handleSuccess(); |
| } catch (e) { |
| handleError(e); |
| } |
| |
| await initAudioContext(); |
| |
| disable("#initBtn"); |
| show("#outputTypesDiv"); |
| } |
| |
| function trackToMSSN() { |
| streamNode = audioContext.createMediaStreamSource(stream); |
| streamNode.connect(audioContext.destination); |
| hide("#outputTypesDiv"); |
| displayRoute("gUM --> MediaStreamSourceNode --> audioContext.destination"); |
| } |
| |
| function trackToAudioElement() { |
| audioElement = document.createElement('audio'); |
| audioElement.srcObject = stream; |
| audioElement.play() |
| hide("#outputTypesDiv"); |
| displayRoute("gUM --> MediaStream --> <audio>.srcObject"); |
| } |
| |
| </script> |
| |
| </body> |
| </html> |