A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer answers technician calls, records both call legs as separate channels, and a Ruby backend transcribes them through Whisper and files an AI summary against the caller. Squashed to a single commit for sharing. No credentials are included; secrets live outside the repo in /etc/helpdesk/env on the server or a gitignored .claude/env.local locally. See .claude/env.local.example for the shape. Start at README.md, then docs/architecture.md.
47 lines
2.8 KiB
Diff
47 lines
2.8 KiB
Diff
diff --git a/java/com/android/dialer/callrecord/impl/BaseCallRecorder.java b/java/com/android/dialer/callrecord/impl/BaseCallRecorder.java
|
|
index 555838f58..a64d0fc9e 100644
|
|
--- a/java/com/android/dialer/callrecord/impl/BaseCallRecorder.java
|
|
+++ b/java/com/android/dialer/callrecord/impl/BaseCallRecorder.java
|
|
@@ -201,6 +201,9 @@ public abstract class BaseCallRecorder implements Closeable {
|
|
// This might be left here if the job is cancelled, but that's fine since the pool this
|
|
// buffer belongs to gets closed if this is cancelled anyway.
|
|
private ByteBufferPool.Buffer mBuffer;
|
|
+ // Helpdesk patch: consecutive negative AudioRecord reads (e.g. while the call is on hold), used
|
|
+ // only to rate-limit logging so a long hold doesn't spam logcat.
|
|
+ private int mReadErrorStreak = 0;
|
|
|
|
AudioRecordPeriodicProducerJob(long startTimeMs, long periodMs) {
|
|
mStartTimeMs = startTimeMs;
|
|
@@ -279,16 +282,28 @@ public abstract class BaseCallRecorder implements Closeable {
|
|
// AudioRecord does not change the ByteBuffer position
|
|
int read = mAudioRecord.read(mBuffer.data, mBuffer.data.capacity(),
|
|
AudioRecord.READ_NON_BLOCKING);
|
|
- if (read < 0) {
|
|
- Log.e(TAG, "error on AudioRecord read: " + read);
|
|
- return false;
|
|
- } else if (read > 0) {
|
|
+ if (read > 0) {
|
|
+ mReadErrorStreak = 0;
|
|
// This will set buffer's limit and reset position
|
|
if (!mAudioBufferPool.produce(mBuffer, read)) {
|
|
// pool is closed
|
|
return false;
|
|
}
|
|
mBuffer = null;
|
|
+ } else if (read < 0) {
|
|
+ // Helpdesk patch: a negative read is NOT treated as fatal. When the operator places the call on
|
|
+ // hold, the VOICE_CALL audio path is torn down and AudioRecord.read can return an error for the
|
|
+ // whole duration of the hold. Upstream returned false here, which tore the recorder down (and, in
|
|
+ // our build, finalized the file at that fraught moment — producing a moov-less, un-transcribable
|
|
+ // MP4). Instead we skip this cycle, keep mBuffer, and leave the encoder+muxer alive so capture
|
|
+ // resumes cleanly when the call comes off hold. The recorder is still stopped deterministically on
|
|
+ // disconnect: stopRecordingBlocking() closes the buffer pool, so the next acquire() returns null
|
|
+ // and this loop exits via the "pool is closed" paths above.
|
|
+ if ((mReadErrorStreak++ % 100) == 0) { // rate-limit: ~once/sec at the 10ms loop period
|
|
+ Log.w(TAG, "AudioRecord read returned " + read + " (call on hold?), skipping; streak="
|
|
+ + mReadErrorStreak);
|
|
+ }
|
|
+ // keep mBuffer for reuse on the next iteration
|
|
} else {
|
|
// If read == 0, keep mBuffer for the next iteration so that we don't need to acquire from
|
|
// pool again.
|