javascript - support - webrtc test
¿Alguien puede explicar exhaustivamente la API de estadísticas de WebRTC? (1)
Estoy terminando un proyecto WebRTC para un curso de posgrado en comunicaciones de video, es esencialmente una sala de chat de videoconferencia. Todos los que se conectan al servidor se agregan a la conferencia.
Necesito usar la API de estadísticas en WebRTC para mostrar algunas estadísticas de rendimiento relevantes para cada RTCPeerConnection (paquetes perdidos por segundo, fluctuación, retransmisión, etc.). Esto ayuda a observar el costo de rendimiento a medida que se agregan más pares a la conversación.
Sin embargo, la API parece no estar completamente desarrollada todavía. Aparentemente ha pasado por algunas actualizaciones y no coincide con algunas de las especificaciones del W3C que he visto (aunque tal vez esté desactualizado o simplemente no entiendo los matices de leer la especificación, ni me sorprendería).
Mi invocación de la API es
similar a esta
, pero interpretar los datos no es sencillo.
Por ejemplo, al recorrer todos los elementos en
RTCStatsReport::results()
, muchos de ellos tienen nombres duplicados y valores confusos.
Parece que no puedo encontrar ninguna información sobre su significado.
Si alguien puede ayudarme a comprender algunos de los más importantes o señalarme la ciudad perdida de oro (por ejemplo, la documentación adecuada), estaría agradecido.
Es probable que la fuente de su confusión sea que la implementación de
getStats()
Google Chrome es anterior al estándar y aún no se ha actualizado (el ejemplo al que se vincula es específico de Chrome, por lo que supongo que está usando Chrome).
Si
getStats()
Firefox,
getStats()
que implementa
getStats()
en
el estándar
(sin embargo, todavía no admite todas las estadísticas en el estándar y menos estadísticas en general que la antigua API de Chrome).
Como no especificó un navegador, describiré el estándar y usaré Firefox para mostrar un ejemplo.
Probablemente ya conozca
getStats()
, pero el estándar le permite filtrar, por ejemplo, un MediaStreamTrack específico, o pasar
null
para obtener todos los datos asociados con una conexión:
var pc = new RTCPeerConnection(config)
...
pc.getStats(null, function(stats) { ...}, function(error) { ... });
También hay una nueva versión de promesa.
Los datos se devuelven en
stats
, un gran objeto de bola de nieve con identificadores únicos para cada registro.
Cada registro tiene
la siguiente
clase base:
dictionary RTCStats {
DOMHiResTimeStamp timestamp;
RTCStatsType type;
DOMString id;
};
donde
id
es una repetición del nombre de propiedad utilizado para acceder al registro.
Los tipos derivados se describen
here
.
Por lo general, enumera los registros hasta que encuentre un
RTCStatsType
de interés, por ejemplo,
"inbound-rtp"
que se ve así:
dictionary RTCRTPStreamStats : RTCStats {
DOMString ssrc;
DOMString remoteId;
boolean isRemote = false;
DOMString mediaTrackId;
DOMString transportId;
DOMString codecId;
unsigned long firCount;
unsigned long pliCount;
unsigned long nackCount;
unsigned long sliCount;
};
dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats {
unsigned long packetsReceived;
unsigned long long bytesReceived;
unsigned long packetsLost;
double jitter;
double fractionLost;
};
Hay uno correspondiente para RTCOutboundRTPStreamStats .
También puede seguir referencias cruzadas a otros registros.
Cualquier miembro que termine con
Id
es una clave externa que puede usar para buscar otro registro.
Por ejemplo,
mediaTrackId
enlaza con
RTCMediaStreamTrackStats
para la pista a la que pertenecen estos datos RTP.
Un caso particularmente curioso son los datos RTCP, que se almacenan en los mismos diccionarios que los anteriores, lo que significa que debe verificar
isRemote == false
para saber que está viendo datos RTP y no datos RTCP.
Use el
remoteId
para encontrar el otro (tenga en cuenta que este es un cambio de nombre reciente, por lo que Firefox todavía usa un
remoteId
más
remoteId
aquí).
Las estadísticas RTCP asociadas para el RTP saliente se almacenan en un diccionario entrante y viceversa (tiene sentido).
Aquí hay un ejemplo que se ejecuta en Firefox:
var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
var add = (pc, can) => can && pc.addIceCandidate(can).catch(log);
pc1.onicecandidate = e => add(pc2, e.candidate);
pc2.onicecandidate = e => add(pc1, e.candidate);
pc2.oniceconnectionstatechange = () => update(statediv, pc2.iceConnectionState);
pc2.onaddstream = e => v2.srcObject = e.stream;
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => pc1.addStream(v1.srcObject = stream))
.then(() => pc1.createOffer())
.then(offer => pc1.setLocalDescription(offer))
.then(() => pc2.setRemoteDescription(pc1.localDescription))
.then(() => pc2.createAnswer())
.then(answer => pc2.setLocalDescription(answer))
.then(() => pc1.setRemoteDescription(pc2.localDescription))
.then(() => repeat(10, () => Promise.all([pc1.getStats(), pc2.getStats()])
.then(([s1, s2]) => {
var s = "";
s1.forEach(stat => {
if (stat.type == "outbound-rtp" && !stat.isRemote) {
s += "<h4>Sender side</h4>" + dumpStats(stat);
}
});
s2.forEach(stat => {
if (stat.type == "inbound-rtp" && !stat.isRemote) {
s += "<h4>Receiver side</h4>" + dumpStats(stat);
}
});
update(statsdiv, "<small>"+ s +"</small>");
})))
.catch(failed);
function dumpStats(o) {
var s = "";
if (o.mozAvSyncDelay !== undefined || o.mozJitterBufferDelay !== undefined) {
if (o.mozAvSyncDelay !== undefined) s += "A/V sync: " + o.mozAvSyncDelay + " ms";
if (o.mozJitterBufferDelay !== undefined) {
s += " Jitter buffer delay: " + o.mozJitterBufferDelay + " ms";
}
s += "<br>";
}
s += "Timestamp: "+ new Date(o.timestamp).toTimeString() +" Type: "+ o.type +"<br>";
if (o.ssrc !== undefined) s += "SSRC: " + o.ssrc + " ";
if (o.packetsReceived !== undefined) {
s += "Recvd: " + o.packetsReceived + " packets";
if (o.bytesReceived !== undefined) {
s += " ("+ (o.bytesReceived/1024000).toFixed(2) +" MB)";
}
if (o.packetsLost !== undefined) s += " Lost: "+ o.packetsLost;
} else if (o.packetsSent !== undefined) {
s += "Sent: " + o.packetsSent + " packets";
if (o.bytesSent !== undefined) s += " ("+ (o.bytesSent/1024000).toFixed(2) +" MB)";
} else {
s += "<br><br>";
}
s += "<br>";
if (o.bitrateMean !== undefined) {
s += " Avg. bitrate: "+ (o.bitrateMean/1000000).toFixed(2) +" Mbps";
if (o.bitrateStdDev !== undefined) {
s += " ("+ (o.bitrateStdDev/1000000).toFixed(2) +" StdDev)";
}
if (o.discardedPackets !== undefined) {
s += " Discarded packts: "+ o.discardedPackets;
}
}
s += "<br>";
if (o.framerateMean !== undefined) {
s += " Avg. framerate: "+ (o.framerateMean).toFixed(2) +" fps";
if (o.framerateStdDev !== undefined) {
s += " ("+ o.framerateStdDev.toFixed(2) +" StdDev)";
}
}
if (o.droppedFrames !== undefined) s += " Dropped frames: "+ o.droppedFrames;
if (o.jitter !== undefined) s += " Jitter: "+ o.jitter;
return s;
}
var wait = ms => new Promise(r => setTimeout(r, ms));
var repeat = (ms, func) => new Promise(r => (setInterval(func, ms), wait(ms).then(r)));
var log = msg => div.innerHTML = div.innerHTML + msg +"<br>";
var update = (div, msg) => div.innerHTML = msg;
var failed = e => log(e.name +": "+ e.message +", line "+ e.lineNumber);
<table><tr><td>
<video id="v1" width="124" height="75" autoplay></video><br>
<video id="v2" width="124" height="75" autoplay></video><br>
<div id="statediv"></div></td>
<td><div id="div"></div><br><div id="statsdiv"></div></td>
</tr></table>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Para ver qué es compatible, haga
stats.forEach(stat => console.log(JSON.stringify(stat)))
para volcar todo.
Difícil de leer pero está todo ahí.
Creo que en breve se planea un polyfill para adapter.js para cerrar la brecha hasta que Chrome actualice su implementación.
Actualización: He actualizado los ejemplos para usar la nueva sintaxis similar a un mapa, y he cambiado los nombres de los tipos para incluir guiones, de acuerdo con la última especificación.