Debugging

Debugging #

Debugging WebRTC bisa menjadi tugas yang menakutkan. Ada banyak bagian yang bergerak, dan semuanya bisa rusak secara independen. Jika Anda tidak hati-hati, Anda dapat kehilangan berminggu-minggu waktu melihat hal yang salah. Ketika Anda akhirnya menemukan bagian yang rusak, Anda perlu belajar sedikit untuk memahami mengapa.

Bab ini akan membawa Anda ke pola pikir untuk men-debug WebRTC. Ini akan menunjukkan kepada Anda cara memecah masalah. Setelah kami mengetahui masalahnya, kami akan memberikan tur singkat dari alat debugging yang populer.

Isolasi Masalah #

Saat men-debug, Anda perlu mengisolasi dari mana masalah berasal. Mulai dari awal…

Kegagalan Signaling #

Kegagalan Jaringan #

Uji server STUN Anda menggunakan netcat:

  1. Siapkan paket permintaan pengikatan 20-byte:

    echo -ne "\x00\x01\x00\x00\x21\x12\xA4\x42TESTTESTTEST" | hexdump -C
    00000000  00 01 00 00 21 12 a4 42  54 45 53 54 54 45 53 54  |....!..BTESTTEST|
    00000010  54 45 53 54                                       |TEST|
    00000014
    

    Interpretasi:

    • 00 01 adalah tipe pesan.
    • 00 00 adalah panjang bagian data.
    • 21 12 a4 42 adalah magic cookie.
    • dan 54 45 53 54 54 45 53 54 54 45 53 54 (Dekode ke ASCII: TESTTESTTEST) adalah transaction ID 12-byte.
  2. Kirim permintaan dan tunggu respons 32 byte:

    stunserver=stun1.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x00\x21\x12\xA4\x42TESTTESTTEST" | nc -u -p $listenport $stunserver $stunport -w 1 | hexdump -C
    00000000  01 01 00 0c 21 12 a4 42  54 45 53 54 54 45 53 54  |....!..BTESTTEST|
    00000010  54 45 53 54 00 20 00 08  00 01 6f 32 7f 36 de 89  |TEST. ....o2.6..|
    00000020
    

    Interpretasi:

    • 01 01 adalah tipe pesan
    • 00 0c adalah panjang bagian data yang didekode menjadi 12 dalam desimal
    • 21 12 a4 42 adalah magic cookie
    • dan 54 45 53 54 54 45 53 54 54 45 53 54 (Dekode ke ASCII: TESTTESTTEST) adalah transaction ID 12-byte.
    • 00 20 00 08 00 01 6f 32 7f 36 de 89 adalah data 12-byte, interpretasi:
      • 00 20 adalah tipenya: XOR-MAPPED-ADDRESS
      • 00 08 adalah panjang bagian nilai yang didekode menjadi 8 dalam desimal
      • 00 01 6f 32 7f 36 de 89 adalah nilai data, interpretasi:
        • 00 01 adalah tipe alamat (IPv4)
        • 6f 32 adalah port yang di-XOR-mapped
        • 7f 36 de 89 adalah alamat IP yang di-XOR-mapped

Mendekode bagian XOR-mapped merepotkan, tetapi kita dapat mengelabui server stun untuk melakukan dummy XOR-mapping, dengan memberikan dummy magic cookie (tidak valid) yang disetel ke 00 00 00 00:

stunserver=stun1.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x00\x00\x00\x00\x00TESTTESTTEST" | nc -u -p $listenport $stunserver $stunport -w 1 | hexdump -C
00000000  01 01 00 0c 00 00 00 00  54 45 53 54 54 45 53 54  |........TESTTEST|
00000010  54 45 53 54 00 01 00 08  00 01 4e 20 5e 24 7a cb  |TEST......N ^$z.|
00000020

XOR-ing terhadap dummy magic cookie adalah idempoten, jadi port dan alamat akan jelas dalam respons. Ini tidak akan bekerja dalam semua situasi, karena beberapa router memanipulasi paket yang lewat, curang pada alamat IP. Jika kita melihat nilai data yang dikembalikan (delapan byte terakhir):

  • 00 01 4e 20 5e 24 7a cb adalah nilai data, interpretasi:
    • 00 01 adalah tipe alamat (IPv4)
    • 4e 20 adalah port yang di-mapped, yang didekode menjadi 20000 dalam desimal
    • 5e 24 7a cb adalah alamat IP, yang didekode menjadi 94.36.122.203 dalam notasi dotted-decimal.

Kegagalan Keamanan #

Kegagalan Media #

Kegagalan Data #

Alat perdagangan #

netcat (nc) #

netcat adalah utilitas jaringan command-line untuk membaca dari dan menulis ke koneksi jaringan menggunakan TCP atau UDP. Ini biasanya tersedia sebagai perintah nc.

tcpdump #

tcpdump adalah penganalisis paket jaringan data command-line.

Perintah umum:

  • Tangkap paket UDP ke dan dari port 19302, cetak hexdump dari konten paket:

    sudo tcpdump 'udp port 19302' -xx

  • Sama, tetapi simpan paket dalam file PCAP (packet capture) untuk inspeksi nanti:

    sudo tcpdump 'udp port 19302' -w stun.pcap

    File PCAP dapat dibuka dengan aplikasi Wireshark: wireshark stun.pcap

Wireshark #

Wireshark adalah penganalisis protokol jaringan yang banyak digunakan.

Alat peramban WebRTC #

Peramban dilengkapi dengan alat bawaan yang dapat Anda gunakan untuk memeriksa koneksi yang Anda buat. Chrome memiliki chrome://webrtc-internals dan chrome://webrtc-logs. Firefox memiliki about:webrtc.

Latensi #

Bagaimana Anda tahu Anda memiliki latensi tinggi? Anda mungkin telah memperhatikan bahwa video Anda tertinggal, tetapi apakah Anda tahu persis berapa banyak tertinggal? Untuk dapat mengurangi latensi ini, Anda harus mulai dengan mengukurnya terlebih dahulu.

Latensi sejati seharusnya diukur end-to-end. Itu berarti tidak hanya latensi jalur jaringan antara pengirim dan penerima, tetapi latensi gabungan dari pengambilan kamera, pengkodean frame, transmisi, penerimaan, dekoding dan tampilan, serta kemungkinan antrian antara salah satu langkah ini.

Latensi end-to-end bukan jumlah sederhana dari latensi setiap komponen.

Meskipun Anda secara teoritis dapat mengukur latensi komponen dari pipeline transmisi video langsung secara terpisah dan kemudian menambahkannya bersama-sama, dalam praktiknya, setidaknya beberapa komponen akan tidak dapat diakses untuk instrumentasi, atau menghasilkan hasil yang sangat berbeda ketika diukur di luar pipeline. Kedalaman antrian variabel antara tahap pipeline, topologi jaringan dan perubahan eksposur kamera hanyalah beberapa contoh komponen yang mempengaruhi latensi end-to-end.

Latensi intrinsik dari setiap komponen dalam sistem live-streaming Anda dapat berubah dan mempengaruhi komponen downstream. Bahkan konten video yang ditangkap mempengaruhi latensi. Misalnya, lebih banyak bit diperlukan untuk fitur frekuensi tinggi seperti cabang pohon, dibandingkan dengan langit biru yang jelas dengan frekuensi rendah. Kamera dengan eksposur otomatis yang diaktifkan mungkin memakan waktu jauh lebih lama daripada 33 milidetik yang diharapkan untuk menangkap frame, bahkan jika ketika tingkat pengambilan disetel ke 30 frame per detik. Transmisi melalui jaringan, terutama seluler, juga sangat dinamis karena permintaan yang berubah. Lebih banyak pengguna memperkenalkan lebih banyak obrolan di udara. Lokasi fisik Anda (zona sinyal rendah yang terkenal) dan beberapa faktor lain meningkatkan packet loss dan latensi. Apa yang terjadi ketika Anda mengirim paket ke antarmuka jaringan, katakanlah adaptor WiFi atau modem LTE untuk pengiriman? Jika tidak dapat segera dikirim, ia akan diantrekan pada antarmuka, semakin besar antrian semakin banyak latensi yang diperkenalkan antarmuka jaringan tersebut.

Pengukuran latensi end-to-end manual #

Ketika kita berbicara tentang latensi end-to-end, yang kami maksud adalah waktu antara event terjadi dan diamati, artinya frame video muncul di layar.

EndToEndLatency = T(observe) - T(happen)

Pendekatan naif adalah merekam waktu ketika event terjadi dan menguranginya dari waktu pengamatan. Namun, karena presisi turun ke milidetik sinkronisasi waktu menjadi masalah. Mencoba menyinkronkan jam di seluruh sistem terdistribusi sebagian besar sia-sia, bahkan kesalahan kecil dalam sinkronisasi waktu menghasilkan pengukuran latensi yang tidak dapat diandalkan.

Solusi sederhana untuk masalah sinkronisasi jam adalah menggunakan jam yang sama. Tempatkan pengirim dan penerima dalam kerangka referensi yang sama.

Bayangkan Anda memiliki jam milidetik yang berdetak atau sumber event lainnya sebenarnya. Anda ingin mengukur latensi dalam sistem yang melakukan live stream jam ke layar remote dengan mengarahkan kamera padanya. Cara yang jelas untuk mengukur waktu antara timer milidetik berdetak (Thappen) dan frame video jam muncul di layar (Tobserve) adalah sebagai berikut:

  • Arahkan kamera Anda ke jam milidetik.
  • Kirim frame video ke penerima yang berada di lokasi fisik yang sama.
  • Ambil gambar (gunakan ponsel Anda) dari timer milidetik dan video yang diterima di layar.
  • Kurangi dua waktu.

Itu adalah pengukuran latensi end-to-end yang paling benar untuk diri Anda sendiri. Ini memperhitungkan semua latensi komponen (kamera, encoder, jaringan, decoder) dan tidak bergantung pada sinkronisasi jam apa pun.

DIY Latency. DIY Latency Example Dalam foto di atas latensi end-to-end yang diukur adalah 101 milidetik. Event yang terjadi sekarang adalah 10:16:02.862, tetapi pengamat sistem live-streaming melihat 10:16:02.761.

Pengukuran latensi end-to-end otomatis #

Pada saat penulisan (Mei 2021) standar WebRTC untuk penundaan end-to-end sedang aktif dibahas. Firefox mengimplementasikan satu set API untuk membiarkan pengguna membuat pengukuran latensi otomatis di atas API WebRTC standar. Namun dalam paragraf ini, kami membahas cara yang paling kompatibel untuk mengukur latensi secara otomatis.

NTP Style Latency Measurement

Roundtrip time dalam singkatnya: Saya mengirim Anda waktu saya tR1, ketika saya menerima kembali tR1 saya pada waktu tR2, saya tahu round trip time adalah tR2 - tR1.

Diberikan saluran komunikasi antara pengirim dan penerima (misalnya DataChannel), penerima dapat memodelkan jam monotonic pengirim dengan mengikuti langkah-langkah di bawah ini:

  1. Pada waktu tR1, penerima mengirim pesan dengan timestamp jam monotonic lokalnya.
  2. Ketika diterima di pengirim dengan waktu lokal tS1, pengirim merespons dengan salinan tR1 serta tS1 pengirim dan waktu track video pengirim tSV1.
  3. Pada waktu tR2 di sisi penerima, round trip time dihitung dengan mengurangi waktu pengiriman dan penerimaan pesan: RTT = tR2 - tR1.
  4. Round trip time RTT bersama dengan timestamp lokal pengirim tS1 cukup untuk membuat estimasi jam monotonic pengirim. Waktu saat ini pada pengirim pada waktu tR2 akan sama dengan tS1 ditambah setengah dari round trip time.
  5. Timestamp jam lokal pengirim tS1 dipasangkan dengan timestamp track video tSV1 bersama dengan round trip time RTT karena itu cukup untuk menyinkronkan waktu track video penerima ke track video pengirim.

Sekarang kita tahu berapa banyak waktu telah berlalu sejak waktu frame video pengirim yang dikenal terakhir tSV1, kita dapat memperkirakan latensi dengan mengurangi waktu frame video yang ditampilkan saat ini (actual_video_time) dari waktu yang diharapkan:

expected_video_time = tSV1 + time_since(tSV1)
latency = expected_video_time - actual_video_time

Kelemahan metode ini adalah tidak termasuk latensi intrinsik kamera. Sebagian besar sistem video menganggap timestamp pengambilan frame adalah waktu ketika frame dari kamera dikirim ke memori utama, yang akan beberapa saat setelah event yang direkam benar-benar terjadi.

Contoh estimasi latensi #

Implementasi sampel membuka data channel latency pada penerima dan secara berkala mengirim timestamp timer monotonic penerima ke pengirim. Pengirim merespons kembali dengan pesan JSON dan penerima menghitung latensi berdasarkan pesan.

{
    "received_time": 64714,       // Timestamp dikirim oleh penerima, pengirim memantulkan timestamp. 
    "delay_since_received": 46,   // Waktu yang berlalu sejak `received_time` terakhir diterima di pengirim.
    "local_clock": 1597366470336, // Waktu jam monotonic pengirim saat ini.
    "track_times_msec": {
        "myvideo_track1": [
            13100,        // Timestamp RTP frame video (dalam milidetik).
            1597366470289 // Timestamp jam monotonic frame video.
        ]
    }
}

Buka data channel pada penerima:

dataChannel = peerConnection.createDataChannel('latency');

Kirim waktu penerima tR1 secara berkala. Contoh ini menggunakan 2 detik tanpa alasan tertentu:

setInterval(() => {
    let tR1 = Math.trunc(performance.now());
    dataChannel.send("" + tR1);
}, 2000);

Tangani pesan masuk dari penerima di pengirim:

// Assuming event.data is a string like "1234567".
tR1 = event.data
now = Math.trunc(performance.now());
tSV1 = 42000; // Current frame RTP timestamp converted to millisecond timescale.
tS1 = 1597366470289; // Current frame monotonic clock timestamp.
msg = {
  "received_time": tR1,
  "delay_since_received": 0,
  "local_clock": now,
  "track_times_msec": {
    "myvideo_track1": [tSV1, tS1]
  }
}
dataChannel.send(JSON.stringify(msg));

Tangani pesan masuk dari pengirim dan cetak latensi yang diestimasi ke console:

let tR2 = performance.now();
let fromSender = JSON.parse(event.data);
let tR1 = fromSender['received_time'];
let delay = fromSender['delay_since_received']; // How much time that has passed between the sender receiving and sending the response.
let senderTimeFromResponse = fromSender['local_clock'];
let rtt = tR2 - delay - tR1;
let networkLatency = rtt / 2;
let senderTime = (senderTimeFromResponse + delay + networkLatency);
VIDEO.requestVideoFrameCallback((now, framemeta) => {
    // Estimate current time of the sender.
    let delaySinceVideoCallbackRequested = now - tR2;
    senderTime += delaySinceVideoCallbackRequested;
    let [tSV1, tS1] = Object.entries(fromSender['track_times_msec'])[0][1]
    let timeSinceLastKnownFrame = senderTime - tS1;
    let expectedVideoTimeMsec = tSV1 + timeSinceLastKnownFrame;
    let actualVideoTimeMsec = Math.trunc(framemeta.rtpTimestamp / 90); // Convert RTP timebase (90000) to millisecond timebase.
    let latency = expectedVideoTimeMsec - actualVideoTimeMsec;
    console.log('latency', latency, 'msec');
});

Waktu video aktual di peramban #

<video>.requestVideoFrameCallback() memungkinkan penulis web untuk diberi tahu ketika frame telah disajikan untuk komposisi.

Sampai sangat baru-baru ini (Mei 2020), hampir tidak mungkin untuk mendapatkan timestamp dari frame video yang ditampilkan saat ini di peramban dengan andal. Metode solusi berdasarkan video.currentTime ada, tetapi tidak terlalu tepat. Baik pengembang peramban Chrome dan Mozilla mendukung pengenalan standar W3C baru, HTMLVideoElement.requestVideoFrameCallback(), yang menambahkan callback API untuk mengakses waktu frame video saat ini. Meskipun penambahan terdengar sepele, ini telah memungkinkan beberapa aplikasi media lanjutan di web yang memerlukan sinkronisasi audio dan video. Khusus untuk WebRTC, callback akan menyertakan bidang rtpTimestamp, timestamp RTP yang terkait dengan frame video saat ini. Ini harus ada untuk aplikasi WebRTC, tetapi tidak ada selain itu.

Tips Debugging Latensi #

Karena debugging kemungkinan akan mempengaruhi latensi yang diukur, aturan umum adalah menyederhanakan pengaturan Anda ke yang terkecil yang mungkin yang masih dapat mereproduksi masalah. Semakin banyak komponen yang dapat Anda hapus, semakin mudah untuk mencari tahu komponen mana yang menyebabkan masalah latensi.

Latensi kamera #

Tergantung pada pengaturan kamera latensi kamera mungkin bervariasi. Periksa pengaturan eksposur otomatis, fokus otomatis dan keseimbangan putih otomatis. Semua fitur “auto” dari kamera web memerlukan waktu ekstra untuk menganalisis gambar yang ditangkap sebelum membuatnya tersedia untuk stack WebRTC.

Jika Anda di Linux, Anda dapat menggunakan alat command line v4l2-ctl untuk mengontrol pengaturan kamera:

# Disable autofocus:
v4l2-ctl -d /dev/video0 -c focus_auto=0
# Set focus to infinity:
v4l2-ctl -d /dev/video0 -c focus_absolute=0

Anda juga dapat menggunakan alat UI grafis guvcview untuk dengan cepat memeriksa dan men-tweak pengaturan kamera.

Latensi encoder #

Sebagian besar encoder modern akan buffer beberapa frame sebelum mengeluarkan yang dikode. Prioritas pertama mereka adalah keseimbangan antara kualitas gambar yang dihasilkan dan bitrate. Pengkodean multipass adalah contoh ekstrem dari pengabaian latensi output encoder. Selama pass pertama encoder mencerna seluruh video dan hanya setelah itu mulai mengeluarkan frame.

Namun, dengan penyetelan yang tepat orang telah mencapai latensi sub-frame. Pastikan encoder Anda tidak menggunakan reference frame yang berlebihan atau bergantung pada B-frame. Pengaturan penyetelan latensi setiap codec berbeda, tetapi untuk x264 kami merekomendasikan menggunakan tune=zerolatency dan profile=baseline untuk latensi output frame terendah.

Latensi jaringan #

Latensi jaringan adalah salah satu yang dapat Anda lakukan paling sedikit, selain meningkatkan ke koneksi jaringan yang lebih baik. Latensi jaringan sangat mirip dengan cuaca - Anda tidak dapat menghentikan hujan, tetapi Anda dapat memeriksa prakiraan dan membawa payung. WebRTC mengukur kondisi jaringan dengan presisi milidetik. Metrik penting adalah:

  • Round-trip time.
  • Packet loss dan retransmisi paket.

Round-Trip Time

Stack WebRTC memiliki mekanisme pengukuran round trip time (RTT) jaringan bawaan mechanism. Perkiraan latensi yang cukup baik adalah setengah dari RTT. Ini mengasumsikan bahwa dibutuhkan waktu yang sama untuk mengirim dan menerima paket, yang tidak selalu terjadi. RTT menetapkan batas bawah pada latensi end-to-end. Frame video Anda tidak dapat mencapai penerima lebih cepat dari RTT/2, tidak peduli seberapa dioptimalkan pipeline kamera ke encoder Anda.

Mekanisme RTT bawaan didasarkan pada paket RTCP khusus yang disebut sender/receiver reports. Pengirim mengirim pembacaan waktunya ke penerima, penerima pada gilirannya memantulkan timestamp yang sama ke pengirim. Dengan demikian pengirim tahu berapa banyak waktu yang dibutuhkan paket untuk melakukan perjalanan ke penerima dan kembali. Lihat bab Sender/Receiver Reports untuk lebih detail tentang pengukuran RTT.

Packet loss dan retransmisi paket

Baik RTP dan RTCP adalah protokol berdasarkan UDP, yang tidak memiliki jaminan pengurutan, pengiriman yang berhasil, atau non-duplikasi. Semua hal di atas dapat dan memang terjadi dalam aplikasi WebRTC dunia nyata. Implementasi decoder yang tidak canggih mengharapkan semua paket dari frame dikirim agar decoder berhasil merakit gambar. Dalam kehadiran packet loss artefak dekoding mungkin muncul jika paket dari P-frame hilang. Jika paket I-frame hilang maka semua frame dependen akan mendapatkan artefak berat atau tidak akan didekode sama sekali. Kemungkinan besar ini akan membuat video “membeku” untuk sesaat.

Untuk menghindari (yah, setidaknya untuk mencoba menghindari) pembekuan video atau artefak dekoding, WebRTC menggunakan pesan pengakuan negatif (NACK). Ketika penerima tidak mendapatkan paket RTP yang diharapkan, ia mengembalikan pesan NACK untuk memberi tahu pengirim untuk mengirim paket yang hilang lagi. Penerima menunggu untuk retransmisi paket. Retransmisi seperti itu menyebabkan peningkatan latensi. Jumlah paket NACK yang dikirim dan diterima dicatat dalam bidang statistik bawaan WebRTC outbound stream nackCount dan inbound stream nackCount.

Anda dapat melihat grafik bagus dari nackCount inbound dan outbound di halaman webrtc internals. Jika Anda melihat nackCount meningkat, itu berarti jaringan mengalami packet loss tinggi, dan stack WebRTC melakukan yang terbaik untuk membuat pengalaman video/audio yang mulus meskipun itu.

Ketika packet loss sangat tinggi sehingga decoder tidak dapat menghasilkan gambar, atau gambar dependen berikutnya seperti dalam kasus I-frame yang hilang sepenuhnya, semua P-frame masa depan tidak akan didekode. Penerima akan mencoba mengurangi itu dengan mengirim pesan Picture Loss Indication khusus (PLI). Setelah pengirim menerima PLI, ia akan menghasilkan I-frame baru untuk membantu decoder penerima. I-frame biasanya lebih besar dalam ukuran daripada P-frame. Ini meningkatkan jumlah paket yang perlu ditransmisikan. Seperti dengan pesan NACK, penerima perlu menunggu I-frame baru, memperkenalkan latensi tambahan.

Perhatikan pliCount di halaman webrtc internals. Jika meningkat, tweak encoder Anda untuk menghasilkan lebih sedikit paket atau aktifkan mode yang lebih tahan kesalahan.

Latensi sisi penerima #

Latensi akan dipengaruhi oleh paket yang tiba tidak berurutan. Jika paket setengah bawah gambar datang sebelum atas Anda harus menunggu atas sebelum dekoding. Ini dijelaskan dalam bab Solving Jitter dengan sangat detail.

Anda juga dapat merujuk ke metrik bawaan jitterBufferDelay untuk melihat berapa lama frame ditahan di buffer penerimaan, menunggu semua paketnya sampai dilepaskan ke decoder.