summaryrefslogtreecommitdiffstats
path: root/pica-client/audiodevice.h
diff options
context:
space:
mode:
authorAnton <anton@picapica.im>2026-09-01 21:09:47 +0400
committerAnton <anton@picapica.im>2026-09-01 21:09:47 +0400
commitc6649895d58bcfab864bcc33b131db222e45bfef (patch)
tree8f863865db8501dd436f1834d598ad9bdded43a9 /pica-client/audiodevice.h
parent6b01fe69c7125c01c9327a27f0d1bac0af244c17 (diff)
downloadpica-pica-c6649895d58bcfab864bcc33b131db222e45bfef.tar.gz
pica-pica-c6649895d58bcfab864bcc33b131db222e45bfef.zip
pica-client: fix echo canceller and capture gain auto regulator
Diffstat (limited to 'pica-client/audiodevice.h')
-rw-r--r--pica-client/audiodevice.h36
1 files changed, 36 insertions, 0 deletions
diff --git a/pica-client/audiodevice.h b/pica-client/audiodevice.h
index ef7e5b3..6b2db1c 100644
--- a/pica-client/audiodevice.h
+++ b/pica-client/audiodevice.h
@@ -99,6 +99,14 @@ signals:
// Counterpart of VideoDevice::accelerationInUse().
void deviceFormatInUse(QString description);
+ // The capture device is delivering samples pinned at full scale, with the
+ // share of them in the last window and the peak that was seen. Not an
+ // error in the errorOccurred() sense - the call carries on - but it wants
+ // acting on, because nothing downstream can undo it. Numbers rather than
+ // a message because the receiver drives the gain control with them; see
+ // AudioVideoCallController::capture_clipping().
+ void captureClipping(double pinnedPercent, double peakDb);
+
public:
virtual QList<MediaDeviceInfo> Enumerate(enum MediaDeviceStreamDirection dir);
@@ -155,6 +163,34 @@ private:
// joining either thread.
EchoCancellerPtr m_echoCanceller;
+ // Watches the microphone for samples pinned at the rail and warns once
+ // every kLevelReportSec of audio if too many of them are.
+ //
+ // Detection only - there is deliberately no gain applied here. Clipping
+ // happens in the converter, before we see anything, and scaling a clipped
+ // buffer down just produces a quieter clipped buffer: the flattened peaks,
+ // the harmonics they generate and the non-linearity that stops the echo
+ // canceller converging are all still in it. The only fix is less gain
+ // ahead of the ADC, which means the capture device's own mixer control.
+ void resetCaptureLevel();
+ void checkCaptureLevel(const qint16 *pcm, int nsamples);
+
+ qint64 m_clipCount;
+ qint64 m_levelCount;
+ int m_levelPeak;
+
+ // |sample| at or above this counts as pinned. 32512 is -0.13 dBFS: low
+ // enough to catch a converter that rails slightly short of 0x7fff, high
+ // enough that ordinary loud speech does not trip it.
+ static const int kClipThreshold = 32512;
+
+ // Fraction of pinned samples in a window that is worth complaining about,
+ // in percent. A stray sample at full scale is normal; a twentieth of a
+ // percent of them is not.
+ static constexpr double kClipWarnPercent = 0.05;
+
+ static const int kLevelReportSec = 2;
+
// The FFmpeg and the native halves of each direction. Which one runs is
// decided by PlatformDriverName().
void captureFFmpeg(const QString &driver);