summaryrefslogtreecommitdiffstats
path: root/pica-client/audiovideocallcontroller.h
diff options
context:
space:
mode:
authorAnton <anton@picapica.im>2026-09-03 21:01:17 +0400
committerAnton <anton@picapica.im>2026-09-03 21:01:17 +0400
commite62dd7d30bec7a679013dcce8ec4bc2b2f842109 (patch)
tree0be99751303e3c0b3306b9d79ca344897996a05d /pica-client/audiovideocallcontroller.h
parentc6649895d58bcfab864bcc33b131db222e45bfef (diff)
downloadpica-pica-e62dd7d30bec7a679013dcce8ec4bc2b2f842109.tar.gz
pica-pica-e62dd7d30bec7a679013dcce8ec4bc2b2f842109.zip
pica-client: more improvements of echo cancellation
Diffstat (limited to 'pica-client/audiovideocallcontroller.h')
-rw-r--r--pica-client/audiovideocallcontroller.h65
1 files changed, 56 insertions, 9 deletions
diff --git a/pica-client/audiovideocallcontroller.h b/pica-client/audiovideocallcontroller.h
index e30f4c1..f7c9e37 100644
--- a/pica-client/audiovideocallcontroller.h
+++ b/pica-client/audiovideocallcontroller.h
@@ -85,20 +85,64 @@ private:
//
// m_savedCaptureGain is where the control was found at the start of the
// call, put back in stopAudioPipeline(): this is the user's own setting
- // and we are only borrowing it. m_captureGainSteps bounds how far we will
- // go, so a microphone that clips for some reason turning it down cannot
- // fix - a broken preamp, someone shouting into it - ends up quiet rather
- // than muted.
+ // and we are only borrowing it.
CaptureGainControl *m_captureGain;
double m_savedCaptureGain;
bool m_haveSavedCaptureGain;
- int m_captureGainSteps;
- static const int kMaxCaptureGainSteps = 12;
+ // This is a calibration that runs at the start of a call and then stops -
+ // NOT a gain control that rides the level for the call's duration. The
+ // difference matters: an adaptive echo canceller can only track a
+ // stationary echo path, and moving the microphone gain moves that path.
+ // See the AGC comment in EchoCanceller's constructor, which says the same
+ // thing about the reason speexdsp's own AGC is left switched off.
+ //
+ // So every adjustment resets the canceller, and once m_gainCalibrating is
+ // false nothing touches the control again until the call ends.
+ bool m_gainCalibrating;
+ int m_captureGainSteps;
- // Never go below this fraction of the control's travel; past it we are
- // making the user inaudible to fix a problem they can hear about instead.
- static constexpr double kMinCaptureGain = 0.05;
+ // dB reading when calibration opened, and whether the platform would give
+ // us one. Bounding the total cut in decibels rather than as a fraction of
+ // the control's travel is the whole point: a fraction means different
+ // things on a hardware mixer (linear in dB) and on a sound server or
+ // WASAPI endpoint (roughly cubic), which is how an earlier version of this
+ // managed to wind a microphone down to silence.
+ double m_gainStartDb;
+ bool m_haveGainDb;
+
+ // Consecutive adjustments that moved the control but did not reduce the
+ // clipping. Means the stage actually overloading is one we cannot reach -
+ // an analog mic boost ahead of the converter, or Windows' separate
+ // "Microphone Boost" - and that turning our control down further will
+ // silence the user without ever fixing it.
+ int m_gainIneffectiveSteps;
+ double m_lastPinnedPercent;
+
+ // Reports to ignore after an adjustment, while audio captured at the old
+ // setting drains out of the pipeline.
+ int m_gainSettleReports;
+
+ static const int kMaxCaptureGainSteps = 6;
+
+ // Bound used when the control will not report decibels at all, where we
+ // are working blind and a wrong guess is what silences a microphone.
+ static const int kMaxBlindGainSteps = 2;
+ static const int kMaxGainIneffectiveSteps = 2;
+ static const int kGainSettleReports = 1;
+
+ // How long the calibration window stays open, in milliseconds of call
+ // time. Long enough that someone who says nothing for the first few
+ // seconds still gets calibrated, short enough to be over before the
+ // conversation proper.
+ static const int kGainCalibrationMs = 20000;
+
+ // Hard ceiling on the total cut, in dB below where the user had it. Past
+ // this the problem is not one we are going to fix by turning a knob.
+ static constexpr double kMaxGainReductionDb = 24.0;
+
+ // An adjustment that achieves less than this much is not doing anything.
+ static constexpr double kMinEffectiveStepDb = 0.5;
// Sequence number / capture-clock timestamp stamped onto outgoing
// 0x76 audio packets (see PICA_PROTO_CALL_PACKET_HDRSIZE).
@@ -123,6 +167,9 @@ private:
void startCaptureGainControl(const QString &captureDevice);
void stopCaptureGainControl();
+ // Ends the calibration window, logging why. Idempotent.
+ void closeGainCalibration(const QString &why);
+
void playEarpieceTone(void (TonePlayer::*tone)());
void playRingTone();
void stopTones();