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.