<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: snowlyg</title>
    <description>The latest articles on DEV Community by snowlyg (@snowlyg).</description>
    <link>https://dev.to/snowlyg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F643857%2F89322273-34f4-4ed7-a959-6c05c9b9a3ce.jpg</url>
      <title>DEV Community: snowlyg</title>
      <link>https://dev.to/snowlyg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/snowlyg"/>
    <language>en</language>
    <item>
      <title>Debugging Android WebRTC Audio 3A with AEC_DUMP and Audacity</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Mon, 15 Jun 2026 12:59:53 +0000</pubDate>
      <link>https://dev.to/snowlyg/debugging-android-webrtc-audio-3a-with-aecdump-and-audacity-2406</link>
      <guid>https://dev.to/snowlyg/debugging-android-webrtc-audio-3a-with-aecdump-and-audacity-2406</guid>
      <description>&lt;p&gt;Android WebRTC audio problems often start as a vague report: call quality is bad. The root cause may be echo, background noise, howling feedback, unstable loudness, near-end speech being over-suppressed, or far-end playback leaking back into the microphone.&lt;/p&gt;

&lt;p&gt;If the only test method is asking two people to make calls repeatedly and remember whether this build sounds better than the previous one, the result drifts quickly. Human listening gets tired, room noise changes, and firmware or parameter changes are hard to compare without durable evidence.&lt;/p&gt;

&lt;p&gt;The practical goal is to turn subjective listening into inspectable audio material. WebRTC AEC_DUMP can record Audio Processing Module inputs, reverse playback reference, processed output, and runtime settings. After unpacking it with &lt;code&gt;unpack_aecdump&lt;/code&gt;, the generated WAV files can be inspected in Audacity or a similar audio tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: Listening Memory Is Not Enough
&lt;/h2&gt;

&lt;p&gt;This case came from an Android 14 WebRTC call scenario. The device hardware platform included RK3562, and the application used WebRTC's Audio Processing Module for audio processing. In this article, 3A mainly refers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AEC: Acoustic Echo Cancellation.&lt;/li&gt;
&lt;li&gt;AGC: Automatic Gain Control.&lt;/li&gt;
&lt;li&gt;NS / ANS: Noise Suppression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a real product, audio quality is not determined by the WebRTC algorithm alone. Enclosure design, microphone openings, speaker loudness, Audio HAL gain, sample rate, channel layout, playback delay, and scheduling can all affect the final call.&lt;/p&gt;

&lt;p&gt;Listening-only testing has several weaknesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is difficult to reproduce the same speech, loudness, and distance;&lt;/li&gt;
&lt;li&gt;people cannot reliably remember small differences between builds;&lt;/li&gt;
&lt;li&gt;it is hard to distinguish bad raw microphone input from bad 3A output;&lt;/li&gt;
&lt;li&gt;it is hard to know whether AEC received a valid far-end reference signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Audio debugging needs evidence that can be saved and compared. That is where AEC_DUMP helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AEC_DUMP Gives You
&lt;/h2&gt;

&lt;p&gt;AEC_DUMP records key inputs and settings around the WebRTC audio processing path. It is not a complete replacement for real call testing, but it helps answer concrete questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the raw microphone input already broken?&lt;/li&gt;
&lt;li&gt;Did the far-end playback reference reach AEC?&lt;/li&gt;
&lt;li&gt;What changed before and after WebRTC audio processing?&lt;/li&gt;
&lt;li&gt;Do sample rate, channel count, and APM switches match the expected device path?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This changes the debugging conversation from "it sounds different" to "we can compare the same captured material."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuir5q8h7zqh3f28it4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuir5q8h7zqh3f28it4g.png" alt="AEC dump file flow" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Environment and Boundary
&lt;/h2&gt;

&lt;p&gt;A minimal test setup can use two Android devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Android device A
  -&amp;gt; WebRTC call
  -&amp;gt; Android device B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test application can be based on the public sample project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/snowlyg/AndroidWebRTCGradle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat the sample build as a debugging tool, not as a production client. Public notes do not need real device identifiers, deployment details, test accounts, or complete field configuration.&lt;/p&gt;

&lt;p&gt;One practical warning: if both devices use speaker mode, are too close to each other, and the volume is high, they can form a strong acoustic loop and produce harsh feedback. Keep volume and distance controlled, and use headphones or a fixed playback source when needed. Otherwise, an extreme acoustic feedback case may be mistaken for a pure software issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capturing AEC_DUMP
&lt;/h2&gt;

&lt;p&gt;A typical capture flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prepare two Android devices with USB debugging enabled.&lt;/li&gt;
&lt;li&gt;Install a WebRTC test application that can save AEC_DUMP.&lt;/li&gt;
&lt;li&gt;Enable AEC_DUMP file saving on both devices.&lt;/li&gt;
&lt;li&gt;Start a WebRTC call.&lt;/li&gt;
&lt;li&gt;Run a fixed test sequence: far-end speech, near-end silence, near-end speech, and double talk.&lt;/li&gt;
&lt;li&gt;Hang up the call so the application closes the dump file.&lt;/li&gt;
&lt;li&gt;Pull &lt;code&gt;audio.aecdump&lt;/code&gt; from the device.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb pull /sdcard/Download/audio.aecdump ./audio.aecdump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The path is only an example. A real application can write the file to its own debug directory, but public material should avoid production paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unpacking audio.aecdump
&lt;/h2&gt;

&lt;p&gt;WebRTC source can build the &lt;code&gt;unpack_aecdump&lt;/code&gt; tool. Build directories and target names can vary by WebRTC version, but a common command in an existing WebRTC build environment is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ninja &lt;span class="nt"&gt;-C&lt;/span&gt; out/Default unpack_aecdump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the tool is available, unpack the captured file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./unpack_aecdump audio.aecdump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The common output files are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input.wav
reverse.wav
ref_out.wav
settings.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file represents a different kind of evidence. They should not be interpreted as the same signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  reverse.wav: The Far-End Reference for AEC
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;reverse.wav&lt;/code&gt; is the far-end playback reference that is fed into AEC. In practical terms, it represents what the local speaker side is expected to play.&lt;/p&gt;

&lt;p&gt;Use it to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether AEC received a far-end reference signal;&lt;/li&gt;
&lt;li&gt;whether the reference audio is empty, intermittent, or abnormally low/high;&lt;/li&gt;
&lt;li&gt;whether the reference could plausibly align with the real playback path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;reverse.wav&lt;/code&gt; has no useful content, AEC does not have enough information to cancel speaker audio that leaks back into the microphone. In that case, tuning AEC delay or NS parameters is usually not the first priority.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;reverse.wav&lt;/code&gt; looks healthy but echo remains strong, continue with acoustic path, speaker volume, capture gain, delay alignment, and microphone input.&lt;/p&gt;

&lt;h2&gt;
  
  
  input.wav: Near-End Audio Before 3A
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;input.wav&lt;/code&gt; is the near-end microphone audio before WebRTC APM processing. It is the key file for judging the hardware capture and system input path.&lt;/p&gt;

&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;raw capture level being too low;&lt;/li&gt;
&lt;li&gt;clipping;&lt;/li&gt;
&lt;li&gt;obvious noise floor during silence;&lt;/li&gt;
&lt;li&gt;far-end playback already overloading the microphone input;&lt;/li&gt;
&lt;li&gt;hardware, Audio HAL, capture gain, or microphone path problems before WebRTC processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;input.wav&lt;/code&gt; is already heavily clipped, AEC, NS, and AGC cannot reliably repair it. Clipping means information has already been lost. Start with hardware gain, system capture path, microphone placement, and speaker volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  ref_out.wav: Output After 3A Processing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ref_out.wav&lt;/code&gt; is the processed near-end reference output reconstructed from the dump inputs and settings. It is useful when compared with &lt;code&gt;input.wav&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input.wav
  -&amp;gt; WebRTC APM
  -&amp;gt; ref_out.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;background noise is reduced;&lt;/li&gt;
&lt;li&gt;echo components are weaker;&lt;/li&gt;
&lt;li&gt;near-end speech is over-suppressed;&lt;/li&gt;
&lt;li&gt;AGC makes loudness unstable;&lt;/li&gt;
&lt;li&gt;NS introduces obvious distortion or cuts speech tails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;input.wav&lt;/code&gt; has clear speech but &lt;code&gt;ref_out.wav&lt;/code&gt; severely suppresses the near-end speaker, check 3A settings, double-talk behavior, NS strength, AGC behavior, and AEC delay alignment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjck34q3izpnjzmqraogn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjck34q3izpnjzmqraogn.png" alt="Audacity analysis workflow" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  settings.txt: Verify Configuration Before Tuning
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;settings.txt&lt;/code&gt; records WebRTC APM initialization and reconfiguration information. At minimum, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input / output / reverse sample rate;&lt;/li&gt;
&lt;li&gt;input / output / reverse channels;&lt;/li&gt;
&lt;li&gt;whether AEC is enabled;&lt;/li&gt;
&lt;li&gt;whether AECM is enabled;&lt;/li&gt;
&lt;li&gt;whether NS is enabled;&lt;/li&gt;
&lt;li&gt;whether AGC is enabled;&lt;/li&gt;
&lt;li&gt;whether HPF is enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If sample rate, channel count, or 3A switches do not match expectations, fix configuration first. Otherwise, waveform analysis may be based on the wrong assumptions.&lt;/p&gt;

&lt;p&gt;For example, if capture and reverse playback have mismatched sample-rate assumptions, AEC delay reasoning becomes unreliable. If channel count is different from the expected device path, APM output may not represent the real product behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Audacity for Engineering Judgment
&lt;/h2&gt;

&lt;p&gt;Audacity should not be treated as an absolute scoring system. Its value is in practical inspection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;waveform height for loudness and dynamic range;&lt;/li&gt;
&lt;li&gt;flat tops for clipping;&lt;/li&gt;
&lt;li&gt;silence thickness for noise floor;&lt;/li&gt;
&lt;li&gt;spectrogram brightness for noise distribution;&lt;/li&gt;
&lt;li&gt;same-time comparison between &lt;code&gt;input.wav&lt;/code&gt; and &lt;code&gt;ref_out.wav&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;far-end playback sections to see whether &lt;code&gt;reverse.wav&lt;/code&gt; leaks into &lt;code&gt;input.wav&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fixed test sequence helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0-5s: near-end silence, far-end speech
5-10s: near-end speech, far-end silence
10-15s: double talk
15-20s: near-end silence, room noise only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After each firmware or parameter change, compare the same type of segment instead of relying on a full-call impression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Decision Paths
&lt;/h2&gt;

&lt;p&gt;An AEC_DUMP review can be organized as several decision paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reverse.wav is empty or broken
  -&amp;gt; check render path and AEC reverse stream first

input.wav is clipped
  -&amp;gt; check microphone gain, Audio HAL, speaker volume, acoustic path

input.wav is clean but ref_out.wav is damaged
  -&amp;gt; check APM settings, AGC/NS strength, double-talk behavior, delay

settings.txt is unexpected
  -&amp;gt; fix sample rate, channels, and 3A switches before tuning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnqlod8wnh2an52n1haw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnqlod8wnh2an52n1haw.png" alt="AEC dump decision path" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The point is not to solve everything in one pass. The point is to locate the layer first: capture is bad, reverse reference is missing, APM configuration is wrong, or the processing strategy needs tuning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relationship to Device Acoustic Tuning
&lt;/h2&gt;

&lt;p&gt;AEC_DUMP is not isolated from device acoustic work. It belongs to the same loop as enclosure tuning, sample-rate adaptation, and AEC delay adjustment.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;input.wav&lt;/code&gt; shows a large amount of speaker energy entering the microphone, changing AEC parameters may only reduce the symptom. The device structure may need attention: speaker outlet, microphone sealing, enclosure reflection, pickup distance, and playback loudness.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;settings.txt&lt;/code&gt; shows sample-rate or channel assumptions that do not match the real device path, align software configuration before evaluating AEC delay. Otherwise, parameter tuning becomes guesswork.&lt;/p&gt;

&lt;p&gt;A safer order is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;capture AEC_DUMP
  -&amp;gt; inspect input / reverse / ref_out / settings
  -&amp;gt; fix obvious capture or config problems
  -&amp;gt; adjust acoustic structure or playback level
  -&amp;gt; tune AEC delay and 3A parameters
  -&amp;gt; capture again and compare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For embedded WebRTC audio work, the reusable rule is simple: visualize first, tune second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AEC_DUMP does not replace real call testing, and it does not prove that a device sounds good in every environment. But it makes Android WebRTC audio problems recordable, comparable, and reviewable.&lt;/p&gt;

&lt;p&gt;For Android WebRTC devices, &lt;code&gt;input.wav&lt;/code&gt;, &lt;code&gt;reverse.wav&lt;/code&gt;, &lt;code&gt;ref_out.wav&lt;/code&gt;, and &lt;code&gt;settings.txt&lt;/code&gt; represent different layers of evidence. First confirm that the far-end reference exists, then check whether raw microphone input is healthy, compare audio before and after 3A, and finally verify sample rate, channels, and processing switches.&lt;/p&gt;

&lt;p&gt;The value is not adding more ceremony to debugging. The value is avoiding the vague conclusion that "AEC is bad" whenever audio quality fails. The actual issue may be microphone gain, enclosure acoustics, sample-rate assumptions, render reference, delay alignment, or APM configuration. Splitting the evidence makes the fix path much more stable.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>android</category>
      <category>audio</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Debugging WebRTC Audio Playback Latency on OpenHarmony 5.0: ArkWeb, AudioRenderer Underrun, and Native SDK Comparison</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:28:36 +0000</pubDate>
      <link>https://dev.to/snowlyg/debugging-webrtc-audio-playback-latency-on-openharmony-50-arkweb-audiorenderer-underrun-and-1bnn</link>
      <guid>https://dev.to/snowlyg/debugging-webrtc-audio-playback-latency-on-openharmony-50-arkweb-audiorenderer-underrun-and-1bnn</guid>
      <description>&lt;p&gt;This case happened in a WebRTC call running inside nweb / ArkWeb on an OpenHarmony 5.0 device. The peer side was Chrome, and the call could be established normally. The OpenHarmony side was also receiving audio data, but playback was clearly wrong: speech became choppy, delayed audio kept accumulating, and the user experience turned into "the peer finishes speaking, then the device plays it several seconds later."&lt;/p&gt;

&lt;p&gt;At first glance, this kind of symptom can look like a network or WebRTC ICE problem. In this case, the useful evidence pointed in a different direction. WebRTC stats showed a connected media path, low round-trip time, low jitter, no packet loss, and continuously increasing send/receive bytes. The more important failure was in the local playback path: ArkWeb supplied decoded audio to the system playback path too slowly, &lt;code&gt;AudioRenderer&lt;/code&gt; underrun appeared continuously, and old PCM data kept queuing instead of being dropped.&lt;/p&gt;

&lt;p&gt;The native &lt;code&gt;ohos_webrtc&lt;/code&gt; project is still relevant in this investigation, but not because it directly patches ArkWeb's internal audio sink. Its value is as a comparison path: if the same call works through native WebRTC, the problem is more likely inside ArkWeb/nweb playback. If native WebRTC also fails, the investigation moves lower into AudioServer, audio host, HAL, driver, or scheduling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: WebRTC in nweb / ArkWeb
&lt;/h2&gt;

&lt;p&gt;The affected scenario used a web-side WebRTC call on OpenHarmony 5.0. The application path looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome peer
    -&amp;gt; WebRTC network and media path
    -&amp;gt; nweb / ArkWeb on OpenHarmony
    -&amp;gt; AudioRendererSinkInner
    -&amp;gt; AudioRenderer / AudioServer
    -&amp;gt; audio host / HAL / device speaker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The peer side could establish a call normally. The OpenHarmony side could receive audio, but playback had several visible symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speech was choppy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AudioRenderer&lt;/code&gt; underrun appeared repeatedly.&lt;/li&gt;
&lt;li&gt;older audio continued to queue.&lt;/li&gt;
&lt;li&gt;the perceived playback became several seconds late.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last symptom matters. If audio packets were simply missing, the result would be gaps or silence. In this case, old audio continued playing late, which suggested that the local playback path was falling behind real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllv8sz0t35wx9y6ugepi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllv8sz0t35wx9y6ugepi.png" alt="ArkWeb WebRTC audio playback path" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Network Was Not the First Suspect
&lt;/h2&gt;

&lt;p&gt;WebRTC stats did not show a broken media path. The observed values were in a healthy range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connectionState: connected
iceConnectionState: connected
packetsLost: 0
roundTripTime: 0.002 ~ 0.021 seconds
jitter: 0.022 ~ 0.031 seconds
bytesReceived / bytesSent: increasing continuously
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These numbers do not prove that the network is perfect, but they make it unlikely that network loss, ICE failure, or remote-side media starvation is the primary cause of a multi-second playback delay.&lt;/p&gt;

&lt;p&gt;The key distinction is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network problem:
  packets are lost, delayed heavily, or ICE state degrades

local playback problem:
  media arrives, but decoded audio is not delivered to the playback device in real time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the second explanation matched the logs better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Logs: RenderFrame Was Too Slow
&lt;/h2&gt;

&lt;p&gt;The most important device-side evidence came from the audio rendering logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AudioRendererSinkInner: [RenderFrame] RenderFrame len[7056] cost[100~106]ms
RendererInServer: Underrun
PaRendererStreamImpl underrun: 5757, 5758, 5759...
Buffer is not empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important details are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a single &lt;code&gt;RenderFrame&lt;/code&gt; call cost around 100 ms.&lt;/li&gt;
&lt;li&gt;the playback side reported underrun.&lt;/li&gt;
&lt;li&gt;the upper buffer was still not empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is very different from "no audio data arrived." It means audio data existed above the playback layer, but the data was not reaching the low-level playback device at a real-time pace.&lt;/p&gt;

&lt;p&gt;For a real-time call, a 100 ms render operation is too expensive. If this repeats, AudioServer runs out of data at the playback deadline, underrun is reported, and the remaining old PCM continues to queue. The user then hears delayed speech instead of live speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Chain: Data Exists, Playback Falls Behind
&lt;/h2&gt;

&lt;p&gt;The practical failure chain can be summarized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nweb / ArkWeb audio supply is slow
  -&amp;gt; AudioServer playback buffer runs dry
  -&amp;gt; AudioRenderer underrun appears
  -&amp;gt; old PCM is still queued above the sink
  -&amp;gt; playback delay grows to several seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This explains why the call can look connected and still sound unusable. WebRTC may continue to receive packets, decode audio, and update stats, while the local playback sink fails to keep up with real time.&lt;/p&gt;

&lt;p&gt;It also explains why simply waiting does not recover the call. If old PCM is not dropped when the renderer falls behind, the playback queue can preserve outdated audio. The device then keeps playing history instead of catching up to the live stream.&lt;/p&gt;

&lt;p&gt;For communication products, that behavior is usually worse than a short dropout. A short dropout is noticeable, but delayed conversation breaks turn-taking and makes the call unusable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lmyutlk4pu8tqb3yhud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lmyutlk4pu8tqb3yhud.png" alt="AudioRenderer underrun and delayed PCM queue" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Native ohos_webrtc Project Provides
&lt;/h2&gt;

&lt;p&gt;The relevant native project is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gitcode.com/openharmony-sig/ohos_webrtc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This project is based on WebRTC m120 for OpenHarmony adaptation. From the project history and exposed SDK surface, its work is mainly around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ArkTS SDK wrapping.&lt;/li&gt;
&lt;li&gt;native WebRTC integration.&lt;/li&gt;
&lt;li&gt;audio and video capture/rendering.&lt;/li&gt;
&lt;li&gt;AudioDeviceModule adaptation and optimization.&lt;/li&gt;
&lt;li&gt;video hardware encode/decode support.&lt;/li&gt;
&lt;li&gt;screen capture.&lt;/li&gt;
&lt;li&gt;system audio capture.&lt;/li&gt;
&lt;li&gt;memory and stability fixes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the project already provides a native WebRTC SDK path for OpenHarmony. It is useful for building a comparison test against the ArkWeb path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web path:
  Chrome -&amp;gt; nweb / ArkWeb -&amp;gt; AudioRendererSinkInner

native path:
  ArkTS UI -&amp;gt; native ohos_webrtc -&amp;gt; AudioDeviceModule -&amp;gt; AudioRenderer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native path does not directly repair ArkWeb's internal &lt;code&gt;AudioRendererSinkInner&lt;/code&gt;, but it can help answer a critical question: is the playback problem specific to ArkWeb's WebRTC sink, or does it still happen when the media path is moved to native WebRTC?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzh7xv4s7nei19d6vars.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzh7xv4s7nei19d6vars.png" alt="Native SDK comparison path" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware Audio Codec Is Not an Existing Project Capability
&lt;/h2&gt;

&lt;p&gt;The current project evidence does not show an implemented hardware audio encoder/decoder path.&lt;/p&gt;

&lt;p&gt;The audio codec path still appears to use WebRTC's built-in software factories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;CreateBuiltinAudioEncoderFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;CreateBuiltinAudioDecoderFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The typical supported audio codecs in that path are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opus
G722
iLBC
G711
L16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OpenHarmony AVCodec adaptation in the project is mainly visible on the video side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HardwareVideoEncoderFactory
HardwareVideoDecoderFactory
H264 / H265
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current material does not show a matching hardware audio factory layer such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HardwareAudioEncoderFactory
HardwareAudioDecoderFactory
OH_AudioEncoder
OH_AudioDecoder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be stated carefully. It does not mean the platform can never support hardware audio codecs. It only means that this project, as observed, should not be treated as already having a hardware audio encode/decode path comparable to its video hardware codec path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hardware Audio Codec Is Not the First Fix
&lt;/h2&gt;

&lt;p&gt;More importantly, the observed failure does not first point to compressed-audio codec performance.&lt;/p&gt;

&lt;p&gt;Hardware audio encoding/decoding mainly affects this boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;compressed audio &amp;lt;-&amp;gt; PCM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs point to a later boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;decoded PCM -&amp;gt; AudioRendererSinkInner -&amp;gt; AudioRenderer -&amp;gt; AudioServer -&amp;gt; HAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual symptoms were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RenderFrame&lt;/code&gt; taking around 100 ms.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AudioRenderer&lt;/code&gt; underrun.&lt;/li&gt;
&lt;li&gt;upper buffer not empty.&lt;/li&gt;
&lt;li&gt;old PCM continuing to queue.&lt;/li&gt;
&lt;li&gt;playback delay growing to seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if hardware audio codec support were added later, it would not necessarily fix a renderer that cannot deliver PCM to the playback device in real time. The first priority is the playback scheduling and buffer behavior after audio has already reached PCM form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native SDK as a Comparison Experiment
&lt;/h2&gt;

&lt;p&gt;The most useful short-term experiment is to run the same call scenario through native &lt;code&gt;ohos_webrtc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the native path is smooth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;native ohos_webrtc works normally
  -&amp;gt; the problem is more likely in nweb / ArkWeb audio sink behavior
  -&amp;gt; business logic can consider bypassing web-side WebRTC for calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the native path also has underrun and delayed playback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;native ohos_webrtc also stutters
  -&amp;gt; the problem is more likely below ArkWeb
  -&amp;gt; inspect AudioServer, audio_host, HAL, driver, thread priority, and CPU scheduling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This comparison is valuable because it avoids arguing from one log stream. It turns the question into a controlled boundary test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same device
same network
same call scenario
different WebRTC playback path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a better isolation strategy than immediately tuning codec parameters or changing ICE configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  AEC Dump Capability and Its Boundary
&lt;/h2&gt;

&lt;p&gt;The native project exposes AEC dump controls through &lt;code&gt;PeerConnectionFactory&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;startAecDump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_size_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;stopAecDump&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical native SDK usage pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filesDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/call_debug.aecdump`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;READ_WRITE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRUNC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;pcf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAecDump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for debugging the native WebRTC audio-processing path. It can help inspect audio processing behavior in the native SDK call path.&lt;/p&gt;

&lt;p&gt;The boundary is equally important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it applies to the native &lt;code&gt;ohos_webrtc&lt;/code&gt; SDK path.&lt;/li&gt;
&lt;li&gt;it does not directly capture nweb / ArkWeb internal WebRTC AEC dump.&lt;/li&gt;
&lt;li&gt;after the file descriptor is handed to native/WebRTC, ownership must be handled carefully; do not close or reuse it from the application side in a way that breaks the native dump writer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So AEC dump is useful for the comparison path, but it is not direct visibility into ArkWeb's internal playback sink.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Direction
&lt;/h2&gt;

&lt;p&gt;The short-term direction is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce the same call through native &lt;code&gt;ohos_webrtc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compare playback behavior with the nweb / ArkWeb path.&lt;/li&gt;
&lt;li&gt;If native playback is smooth, evaluate moving the call path to the native SDK.&lt;/li&gt;
&lt;li&gt;If native playback also fails, move the investigation to the system audio stack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;System-side investigation should focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AudioRendererSinkInner&lt;/code&gt; &lt;code&gt;RenderFrame&lt;/code&gt; calls taking around 100 ms.&lt;/li&gt;
&lt;li&gt;low-latency &lt;code&gt;AudioRenderer&lt;/code&gt; path.&lt;/li&gt;
&lt;li&gt;AudioServer and audio host thread priority.&lt;/li&gt;
&lt;li&gt;HAL period and buffer configuration.&lt;/li&gt;
&lt;li&gt;CPU governor and big/little core scheduling.&lt;/li&gt;
&lt;li&gt;dropping old PCM when playback is already behind real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is important for real-time communication. When the renderer is late, preserving all old PCM may be technically faithful, but it makes conversation worse. A real-time call usually needs a catch-up strategy that favors current audio over stale audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This OpenHarmony WebRTC issue was not primarily a classic network problem. The call was connected, ICE stayed connected, packets were not being lost, RTT and jitter were low, and bytes continued to move.&lt;/p&gt;

&lt;p&gt;The strongest evidence pointed to local audio playback real-time failure. &lt;code&gt;AudioRendererSinkInner::RenderFrame&lt;/code&gt; cost around 100 ms, AudioServer reported underrun, and the upper buffer was still not empty. That means the system was not simply missing audio data. It had audio data, but it was not being delivered to the playback device fast enough, and old PCM kept accumulating.&lt;/p&gt;

&lt;p&gt;The native &lt;code&gt;ohos_webrtc&lt;/code&gt; project provides an important comparison path. It already covers ArkTS SDK wrapping, native WebRTC integration, AudioDeviceModule work, video hardware codec support, screen capture, system audio capture, and AEC dump controls. But it should not be described as having hardware audio encode/decode support based on the current evidence.&lt;/p&gt;

&lt;p&gt;For this specific issue, hardware audio codec work is not the first fix. The first priority is to verify whether native WebRTC can bypass the ArkWeb audio sink and then determine whether the remaining bottleneck belongs to ArkWeb, AudioRenderer, AudioServer, audio host, HAL, or scheduling.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>openharmony</category>
      <category>audio</category>
      <category>debugging</category>
    </item>
    <item>
      <title>WebRTC Echo and Noise Optimization on Android 14 Bedside Devices</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Sat, 06 Jun 2026 07:28:45 +0000</pubDate>
      <link>https://dev.to/snowlyg/webrtc-echo-and-noise-optimization-on-android-14-bedside-devices-3l7e</link>
      <guid>https://dev.to/snowlyg/webrtc-echo-and-noise-optimization-on-android-14-bedside-devices-3l7e</guid>
      <description>&lt;p&gt;This article looks at WebRTC audio tuning on Android 14 bedside devices.&lt;/p&gt;

&lt;p&gt;The target scenario is not a consumer video-call app. It is closer to shared institutional devices that need to stay online for long periods, support hands-free calls, capture speech from a practical distance, and still keep the conversation intelligible in noisy rooms.&lt;/p&gt;

&lt;p&gt;The important lesson from this case is that echo and noise tuning was not only a WebRTC AEC parameter problem. The physical acoustic path, microphone sealing, sample-rate behavior, and AEC delay setting all had to be handled together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed on the hardware side
&lt;/h2&gt;

&lt;p&gt;The first change was around the speaker output path. Part of the enclosure near the speaker was opened so playback was less blocked by the shell. That improved perceived loudness and reduced the amount of sound energy trapped inside the small cavity.&lt;/p&gt;

&lt;p&gt;The second change was around the microphone path. A small seal pad was added near the microphone opening. The goal was to reduce leakage and structural echo pickup, so the microphone input became closer to the intended acoustic path.&lt;/p&gt;

&lt;p&gt;This made the later software tuning more meaningful. If the input signal is already dominated by leakage and enclosure reflection, the echo canceller has to solve a harder problem than necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed on the WebRTC side
&lt;/h2&gt;

&lt;p&gt;The software work focused on two areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adapting the WebRTC audio path to the actual sample-rate behavior of the device;&lt;/li&gt;
&lt;li&gt;tuning the AEC delay value against the real capture/playback path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an embedded terminal, the real delay is not only an algorithm parameter. It includes capture buffering, playback buffering, resampling, scheduling, and the physical distance between speaker and microphone.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I validated it
&lt;/h2&gt;

&lt;p&gt;The validation setup kept variables stable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same device placement;&lt;/li&gt;
&lt;li&gt;same speaker volume;&lt;/li&gt;
&lt;li&gt;same speaking distance;&lt;/li&gt;
&lt;li&gt;same background-noise condition;&lt;/li&gt;
&lt;li&gt;same test phrase or playback source;&lt;/li&gt;
&lt;li&gt;same recording and comparison method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was not to produce a perfect lab metric. The goal was to confirm that playback became louder, residual echo became lower, speech stayed understandable, and AEC was no longer fighting a mismatched sample-rate or delay assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;For embedded WebRTC devices, tune the physical acoustic path first. Then tune AEC parameters against that real path.&lt;/p&gt;

&lt;p&gt;If the enclosure reflects too much speaker energy back toward the microphone, software tuning alone starts from a bad signal. A cleaner acoustic path makes WebRTC audio processing more effective and easier to validate.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>android</category>
      <category>audio</category>
      <category>embedded</category>
    </item>
    <item>
      <title>WebSocket Half-Open Connections and gRPC Gateway Discovery in Complex LAN WebRTC Systems</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Thu, 04 Jun 2026 14:26:37 +0000</pubDate>
      <link>https://dev.to/snowlyg/websocket-half-open-connections-and-grpc-gateway-discovery-in-complex-lan-webrtc-systems-50bo</link>
      <guid>https://dev.to/snowlyg/websocket-half-open-connections-and-grpc-gateway-discovery-in-complex-lan-webrtc-systems-50bo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F044lhgxhhxwchmqtmnz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F044lhgxhhxwchmqtmnz4.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The target scenario is not consumer calling. It is closer to B2B institutional device fleets: many shared devices, long-running uptime, centralized operations, noisy rooms, and a need to preserve both pickup distance and speech quality.&lt;/p&gt;

&lt;p&gt;In constrained LAN environments, the hardest WebRTC failure is not always a clean disconnect. The more dangerous state is a control connection that still appears alive while application messages no longer move.&lt;/p&gt;

&lt;p&gt;The media path can still be WebRTC. The question is how to design the control plane around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Failure Mode
&lt;/h2&gt;

&lt;p&gt;A WebSocket-centered signaling path can work well in a simple network. Each client connects to a central service, receives call events, and forwards offer, answer, and ICE candidate messages.&lt;/p&gt;

&lt;p&gt;In constrained networks, long-lived connections may become stale. A client can stay alive while its upstream connection is no longer useful. The server may still hold old state. The UI may still show the peer as online.&lt;/p&gt;

&lt;p&gt;For a WebRTC call system, that can mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call events do not reach the callee&lt;/li&gt;
&lt;li&gt;timeout does not close all affected UI&lt;/li&gt;
&lt;li&gt;stale sessions block new calls&lt;/li&gt;
&lt;li&gt;group-call media state is not cleaned up correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Gateway Boundary
&lt;/h2&gt;

&lt;p&gt;The design I prefer is to keep media on WebRTC and move control recovery into a gateway layer.&lt;/p&gt;

&lt;p&gt;A Go/gRPC gateway can own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;device registration&lt;/li&gt;
&lt;li&gt;gateway discovery&lt;/li&gt;
&lt;li&gt;heartbeat and reachability state&lt;/li&gt;
&lt;li&gt;call session routing&lt;/li&gt;
&lt;li&gt;offer, answer, and ICE candidate forwarding&lt;/li&gt;
&lt;li&gt;timeout and hangup convergence&lt;/li&gt;
&lt;li&gt;cleanup after abnormal state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Audio observability also belongs in the reliability model. In shared rooms, the system has to handle background noise while preserving enough pickup distance. That means call state, recovery state, and audio state should be debugged together instead of treated as unrelated problems.&lt;/p&gt;

&lt;p&gt;The key is bidirectional reachability. Recovery should not depend only on a stale client-initiated connection noticing that it is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session State Matters
&lt;/h2&gt;

&lt;p&gt;A primary node or discovery service needs a state table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gateway id&lt;/li&gt;
&lt;li&gt;current reachability&lt;/li&gt;
&lt;li&gt;latest heartbeat time&lt;/li&gt;
&lt;li&gt;current call state&lt;/li&gt;
&lt;li&gt;active session ids&lt;/li&gt;
&lt;li&gt;gateway version&lt;/li&gt;
&lt;li&gt;latest error reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This table answers operational questions: which gateway should receive a call event, whether a peer is actually reachable now, and whether an old session must be closed before a new call starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call Lifecycle Convergence
&lt;/h2&gt;

&lt;p&gt;Timeout and hangup should be converged by server-side state.&lt;/p&gt;

&lt;p&gt;For one-to-one calls, either side can request hangup. The primary node should notify both gateways, remove media mappings, and mark the session as ended.&lt;/p&gt;

&lt;p&gt;For group calls, one participant leaving should remove only that participant's media. A full hangup should close the entire session. Without explicit participant and media mapping, group cleanup becomes either too weak or too destructive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Validate
&lt;/h2&gt;

&lt;p&gt;The useful tests are forced failures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop one gateway while the call is ringing.&lt;/li&gt;
&lt;li&gt;Drop the network path between two gateways.&lt;/li&gt;
&lt;li&gt;Keep the UI open while the control stream becomes unusable.&lt;/li&gt;
&lt;li&gt;Make one participant leave a group call.&lt;/li&gt;
&lt;li&gt;Trigger timeout and manual hangup at nearly the same time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not that nothing ever disconnects. The goal is that every abnormal path reaches a clear state and leaves no stale UI, stale session, or stale media mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;This is not simply replacing WebSocket with gRPC. It is designing a stronger recovery model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gateways can discover each other&lt;/li&gt;
&lt;li&gt;the primary node owns session state&lt;/li&gt;
&lt;li&gt;both sides can be reached by control events&lt;/li&gt;
&lt;li&gt;timeout and hangup are explicit&lt;/li&gt;
&lt;li&gt;group-call media mappings are tracked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For WebRTC systems in constrained LANs, recovery has to be part of the architecture, not an afterthought.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>go</category>
      <category>grpc</category>
      <category>android</category>
    </item>
    <item>
      <title>Connecting Android WebRTC to a Go gRPC Signaling Gateway</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Tue, 02 Jun 2026 13:23:05 +0000</pubDate>
      <link>https://dev.to/snowlyg/connecting-android-webrtc-to-a-go-grpc-signaling-gateway-1dcm</link>
      <guid>https://dev.to/snowlyg/connecting-android-webrtc-to-a-go-grpc-signaling-gateway-1dcm</guid>
      <description>&lt;p&gt;This is a sanitized engineering recap about connecting Android WebRTC clients to a Go gRPC signaling gateway.&lt;/p&gt;

&lt;p&gt;Source article:&lt;br&gt;
&lt;a href="https://www.lodan.me/posts/golang-grpc-webrtc-android/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/golang-grpc-webrtc-android/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The main boundary
&lt;/h2&gt;

&lt;p&gt;The important part is not only creating a &lt;code&gt;PeerConnection&lt;/code&gt;. The harder part is keeping signaling, Android lifecycle, and WebRTC media state separated enough to debug.&lt;/p&gt;

&lt;p&gt;The boundary I use is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go/gRPC owns registration, call control, callbacks, and signaling events.&lt;/li&gt;
&lt;li&gt;Android owns permissions, UI lifecycle, local audio/video resources, and cleanup.&lt;/li&gt;
&lt;li&gt;WebRTC owns offer/answer, ICE candidates, tracks, and connection state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why logs matter
&lt;/h2&gt;

&lt;p&gt;When a call fails, the log needs to connect all layers. Useful fields include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call id&lt;/li&gt;
&lt;li&gt;peer id&lt;/li&gt;
&lt;li&gt;action&lt;/li&gt;
&lt;li&gt;signaling state&lt;/li&gt;
&lt;li&gt;ICE state&lt;/li&gt;
&lt;li&gt;selected candidate pair&lt;/li&gt;
&lt;li&gt;local and remote lifecycle state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these fields, the same failure can look like a network issue, a device issue, or a WebRTC SDK issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full article
&lt;/h2&gt;

&lt;p&gt;The full write-up covers gateway startup, device registration, callback boundaries, &lt;code&gt;SdpObserver&lt;/code&gt;, &lt;code&gt;PeerConnection.Observer&lt;/code&gt;, single-call and multi-call lifecycle, hold, hangup, broadcast, and cleanup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.lodan.me/posts/golang-grpc-webrtc-android/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/golang-grpc-webrtc-android/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>go</category>
      <category>android</category>
      <category>grpc</category>
    </item>
    <item>
      <title>Server-Side WebRTC Noise Reduction with Pion, FFmpeg, and RNN Models</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Sat, 30 May 2026 15:22:56 +0000</pubDate>
      <link>https://dev.to/snowlyg/server-side-webrtc-noise-reduction-with-pion-ffmpeg-and-rnn-models-3ck5</link>
      <guid>https://dev.to/snowlyg/server-side-webrtc-noise-reduction-with-pion-ffmpeg-and-rnn-models-3ck5</guid>
      <description>&lt;p&gt;This is a sanitized engineering note about server-side audio noise reduction for WebRTC calls.&lt;/p&gt;

&lt;p&gt;Source article:&lt;br&gt;
&lt;a href="https://www.lodan.me/posts/server-side-webrtc-noise-reduction-pion-ffmpeg-rnn/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/server-side-webrtc-noise-reduction-pion-ffmpeg-rnn/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the prototype tests
&lt;/h2&gt;

&lt;p&gt;The goal is not to replace WebRTC's built-in audio processing. The narrower test is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;receive a WebRTC Opus track with Pion&lt;/li&gt;
&lt;li&gt;read RTP packets in &lt;code&gt;OnTrack&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;decode Opus payloads to PCM&lt;/li&gt;
&lt;li&gt;pipe raw PCM into FFmpeg&lt;/li&gt;
&lt;li&gt;apply the &lt;code&gt;arnndn&lt;/code&gt; RNN noise reduction filter&lt;/li&gt;
&lt;li&gt;validate the output as a file before considering real-time forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this boundary matters
&lt;/h2&gt;

&lt;p&gt;RTP, Opus, PCM, and FFmpeg raw audio input are different boundaries. If the PCM format is wrong, FFmpeg may still produce a file, but the result should not be trusted.&lt;/p&gt;

&lt;p&gt;For example, if the Go side writes &lt;code&gt;int16&lt;/code&gt; PCM, the FFmpeg input format should be reviewed as &lt;code&gt;s16le&lt;/code&gt;, not casually treated as &lt;code&gt;s32le&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production concerns
&lt;/h2&gt;

&lt;p&gt;The prototype is useful because it isolates the audio path, but production use needs more work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;buffering and latency&lt;/li&gt;
&lt;li&gt;CPU and memory isolation&lt;/li&gt;
&lt;li&gt;FFmpeg process lifecycle&lt;/li&gt;
&lt;li&gt;model choice&lt;/li&gt;
&lt;li&gt;packet loss and jitter&lt;/li&gt;
&lt;li&gt;RTP timestamps&lt;/li&gt;
&lt;li&gt;audio/video sync&lt;/li&gt;
&lt;li&gt;whether the processed audio is returned to WebRTC or only recorded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full article has diagrams and the longer explanation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.lodan.me/posts/server-side-webrtc-noise-reduction-pion-ffmpeg-rnn/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/server-side-webrtc-noise-reduction-pion-ffmpeg-rnn/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>go</category>
      <category>ffmpeg</category>
      <category>audio</category>
    </item>
    <item>
      <title>WebRTC + Go gRPC Call Architecture for Constrained LAN Environments</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Wed, 27 May 2026 14:29:46 +0000</pubDate>
      <link>https://dev.to/snowlyg/webrtc-go-grpc-call-architecture-for-constrained-lan-environments-l91</link>
      <guid>https://dev.to/snowlyg/webrtc-go-grpc-call-architecture-for-constrained-lan-environments-l91</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31x9c358evwyhe8opvpk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31x9c358evwyhe8opvpk.png" alt="WebRTC + Go gRPC call architecture" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I published a new architecture recap on building WebRTC calls around a Go gRPC signaling layer and Android clients in constrained LAN environments.&lt;/p&gt;

&lt;p&gt;The design is intentionally split into clear ownership boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gRPC signaling handles device state, session control, and event delivery.&lt;/li&gt;
&lt;li&gt;WebRTC owns SDP/ICE negotiation, media tracks, selected candidate pairs, and transport state.&lt;/li&gt;
&lt;li&gt;Android clients own runtime permissions, audio/video resource management, UI state, and lifecycle cleanup.&lt;/li&gt;
&lt;li&gt;An RTC Gateway gives the system a place for discovery, recovery, session mapping, and observability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main lesson is that call setup is only the beginning. The real engineering work is in recovery and observability: LAN paths can change, devices can restart, sessions can become stale, audio quality can degrade, and gateway state can drift away from client state.&lt;/p&gt;

&lt;p&gt;Full write-up:&lt;br&gt;
&lt;a href="https://www.lodan.me/posts/webrtc-grpc-lan-call-architecture/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/webrtc-grpc-lan-call-architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is part of a longer series on WebRTC reliability, AEC dump debugging, server-side noise suppression, and RTC Gateway concurrency issues.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>go</category>
      <category>android</category>
    </item>
    <item>
      <title>Debugging Android 14 WebRTC Disconnects on a coturn Relay Path</title>
      <dc:creator>snowlyg</dc:creator>
      <pubDate>Sat, 23 May 2026 09:14:29 +0000</pubDate>
      <link>https://dev.to/snowlyg/debugging-android-14-webrtc-disconnects-on-a-coturn-relay-path-1lbm</link>
      <guid>https://dev.to/snowlyg/debugging-android-14-webrtc-disconnects-on-a-coturn-relay-path-1lbm</guid>
      <description>&lt;p&gt;I recently wrote up a debugging case around Android WebRTC, coturn, TURN relay, and ICE failures. This is the anonymized version because the useful part is the debugging method, not the original environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Android WebRTC audio/video call.&lt;/li&gt;
&lt;li&gt;The service originally worked mostly on an internal network.&lt;/li&gt;
&lt;li&gt;A TURN relay path through &lt;code&gt;coturn&lt;/code&gt; was introduced for more complex network boundaries.&lt;/li&gt;
&lt;li&gt;Older Android devices were mostly stable.&lt;/li&gt;
&lt;li&gt;Android 14 devices often disconnected after 30-60 seconds.&lt;/li&gt;
&lt;li&gt;Forcing the media path through TURN relay made the failure much easier to reproduce.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Misleading Clue
&lt;/h2&gt;

&lt;p&gt;The first clue looked obvious: a repeated timeout around a local relay-related address. It appeared near the disconnect window, so it was tempting to treat it as the cause.&lt;/p&gt;

&lt;p&gt;That was not enough.&lt;/p&gt;

&lt;p&gt;In WebRTC debugging, a noisy log line can be real without being the root cause. The better starting point was the ICE state transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IceConnectionChange -&amp;gt; DISCONNECTED / FAILED
StandardizedIceConnectionChange -&amp;gt; DISCONNECTED / FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That moved the investigation away from "why does this timeout appear?" and toward "why does the selected media path fail?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What Helped Narrow It Down
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Force the path through &lt;code&gt;coturn&lt;/code&gt; so direct LAN candidates did not hide the issue.&lt;/li&gt;
&lt;li&gt;Test a newer WebRTC build to rule out a simple SDK version mismatch.&lt;/li&gt;
&lt;li&gt;Expand the device matrix until the issue narrowed around Android 14 behavior.&lt;/li&gt;
&lt;li&gt;Re-check TURN-side certificate/security settings and required port exposure.&lt;/li&gt;
&lt;li&gt;Re-test the same matrix after adjusting the TURN-side configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After the TURN-side changes, the 30-60s disconnect stopped reproducing in the same test matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;For WebRTC production issues, I would rather start from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ICE state transitions.&lt;/li&gt;
&lt;li&gt;Selected candidate pair.&lt;/li&gt;
&lt;li&gt;Whether the call is using host, srflx, or relay candidates.&lt;/li&gt;
&lt;li&gt;TURN server logs and port exposure.&lt;/li&gt;
&lt;li&gt;Client-side network and app lifecycle events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loudest log line is useful, but it should not be allowed to define the investigation by itself.&lt;/p&gt;

&lt;p&gt;WebRTC reliability is usually a system problem: client version, network path, TURN configuration, device behavior, and application state handling all interact. Treating it as a single log-line problem can waste a lot of time.&lt;/p&gt;

&lt;p&gt;Full write-up:&lt;br&gt;
&lt;a href="https://www.lodan.me/posts/android14-coturn-webrtc-disconnect/" rel="noopener noreferrer"&gt;https://www.lodan.me/posts/android14-coturn-webrtc-disconnect/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webrtc</category>
    </item>
  </channel>
</rss>
