From 6d4fe20acf25a2d0a585dc89286439aaedc7aace Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 14 Nov 2025 18:34:39 -0600 Subject: fix(recording): Fix phone call audio capture with amix filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phone calls were not capturing the remote person's voice due to severe volume loss (44 dB) when using the amerge+pan FFmpeg filter combination. Changes: - Replace amerge+pan with amix filter (provides 44 dB volume improvement) - Increase default system volume from 0.5 to 2.0 for better capture levels - Add diagnostic tool to show active audio playback (C-; r w) - Add integration test with real voice recording - Fix batch mode compatibility for test execution The amix filter properly mixes microphone and system monitor inputs without the massive volume loss that amerge+pan caused. Verified with automated integration test showing perfect transcription of test audio. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...ration-recording-monitor-capture-interactive.el | 186 +++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 tests/test-integration-recording-monitor-capture-interactive.el (limited to 'tests/test-integration-recording-monitor-capture-interactive.el') diff --git a/tests/test-integration-recording-monitor-capture-interactive.el b/tests/test-integration-recording-monitor-capture-interactive.el new file mode 100644 index 00000000..ece8b79e --- /dev/null +++ b/tests/test-integration-recording-monitor-capture-interactive.el @@ -0,0 +1,186 @@ +;;; test-integration-recording-monitor-capture-interactive.el --- Interactive recording test -*- lexical-binding: t; -*- + +;; Author: Craig Jennings +;; Created: 2025-11-14 + +;;; Commentary: +;; +;; **INTERACTIVE TEST - Run from within Emacs** +;; +;; This test must be run from an interactive Emacs session where recording +;; devices are already configured (C-; r c). +;; +;; USAGE: +;; 1. Ensure devices are configured: C-; r c +;; 2. Load this file: M-x load-file RET tests/test-integration-recording-monitor-capture-interactive.el RET +;; 3. Run test: M-x test-recording-monitor-now RET +;; +;; OR simply: +;; M-x ert RET test-integration-recording-monitor-capture RET +;; +;; The test will: +;; - Play test audio through your speakers (5 seconds) +;; - Record it +;; - Transcribe it +;; - Verify the transcription contains the expected text +;; +;; This verifies that phone call audio (speaker output) is being captured correctly. + +;;; Code: + +(require 'video-audio-recording) +(require 'transcription-config) + +(defvar test-recording--test-audio + (expand-file-name "tests/fixtures/audio/speaker-output-test.wav" user-emacs-directory) + "Test audio file for speaker output testing.") + +(defvar test-recording--expected-phrases + '("hear me" "testing" "one") + "Expected phrases in transcription (partial match OK). +Based on actual recording: 'Can you hear me? Testing, one, two, three.'") + +(defun test-recording--cleanup-files (recording-file) + "Clean up RECORDING-FILE and associated files." + (when (and recording-file (file-exists-p recording-file)) + (let* ((base (file-name-sans-extension recording-file)) + (txt-file (concat base ".txt")) + (log-file (concat base ".log"))) + (when (file-exists-p recording-file) (delete-file recording-file)) + (when (file-exists-p txt-file) (delete-file txt-file)) + (when (file-exists-p log-file) (delete-file log-file))))) + +(defun test-recording--wait-for-file (file timeout) + "Wait for FILE to exist and have content, up to TIMEOUT seconds. +Returns FILE path if successful, nil if timeout." + (let ((deadline (time-add (current-time) (seconds-to-time timeout)))) + (while (and (time-less-p (current-time) deadline) + (or (not (file-exists-p file)) + (= 0 (file-attribute-size (file-attributes file))))) + (sleep-for 1) + (message "Waiting for %s... (%d sec remaining)" + (file-name-nondirectory file) + (ceiling (float-time (time-subtract deadline (current-time)))))) + (when (and (file-exists-p file) + (> (file-attribute-size (file-attributes file)) 0)) + file))) + +;;;###autoload +(defun test-recording-monitor-now () + "Test recording monitor capture interactively. +This function can be called with M-x to test recording without ERT framework." + (interactive) + + ;; Pre-flight checks + (unless (executable-find "paplay") + (user-error "paplay not found. Install pulseaudio-utils")) + (unless (executable-find "ffmpeg") + (user-error "ffmpeg not found. Install ffmpeg")) + (unless (file-exists-p test-recording--test-audio) + (user-error "Test audio file not found: %s" test-recording--test-audio)) + (unless (and cj/recording-mic-device cj/recording-system-device) + (user-error "Recording devices not configured. Run C-; r c first")) + + (let ((test-dir (make-temp-file "recording-test-" t)) + (recording-file nil) + (playback-proc nil)) + (unwind-protect + (progn + (message "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + (message "RECORDING MONITOR CAPTURE TEST") + (message "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + (message "Configuration:") + (message " Mic: %s" cj/recording-mic-device) + (message " Monitor: %s" cj/recording-system-device) + (message " Backend: %s\n" cj/transcribe-backend) + + ;; Step 1: Start recording + (message "[1/6] Starting recording...") + (cj/ffmpeg-record-audio test-dir) + (sleep-for 1) + (unless (process-live-p cj/audio-recording-ffmpeg-process) + (error "Failed to start recording")) + (message "āœ“ Recording started\n") + + ;; Step 2: Play test audio + (message "[2/6] Playing test audio through speakers...") + (setq playback-proc (start-process "test-playback" "*test-playback*" + "paplay" test-recording--test-audio)) + (message "āœ“ Playback started\n") + + ;; Step 3: Wait for playback + (message "[3/6] Waiting for playback to complete...") + (let ((waited 0)) + (while (and (process-live-p playback-proc) (< waited 10)) + (sleep-for 0.5) + (setq waited (+ waited 0.5))) + (when (process-live-p playback-proc) + (kill-process playback-proc) + (error "Playback timed out"))) + (sleep-for 1) + (message "āœ“ Playback completed\n") + + ;; Step 4: Stop recording + (message "[4/6] Stopping recording...") + (cj/audio-recording-stop) + (sleep-for 1) + + ;; Find recording file + (let ((files (directory-files test-dir t "\\.m4a$"))) + (unless (= 1 (length files)) + (error "Expected 1 recording file, found %d" (length files))) + (setq recording-file (car files))) + + (message "āœ“ Recording stopped") + (message " File: %s" recording-file) + (message " Size: %d bytes\n" + (file-attribute-size (file-attributes recording-file))) + + ;; Step 5: Transcribe + (message "[5/6] Transcribing (this may take 30-60 seconds)...") + (cj/transcribe-audio recording-file) + + (let ((txt-file (concat (file-name-sans-extension recording-file) ".txt"))) + (unless (test-recording--wait-for-file txt-file 120) + (error "Transcription timed out or failed")) + (message "āœ“ Transcription completed\n") + + ;; Step 6: Verify + (message "[6/6] Verifying transcription...") + (let ((transcript (with-temp-buffer + (insert-file-contents txt-file) + (downcase (buffer-string)))) + (matches 0)) + (message "Transcript (%d chars): %s..." + (length transcript) + (substring transcript 0 (min 80 (length transcript)))) + + (dolist (phrase test-recording--expected-phrases) + (when (string-match-p phrase transcript) + (setq matches (1+ matches)) + (message " āœ“ Found: '%s'" phrase))) + + (message "\nMatched %d/%d expected phrases" + matches (length test-recording--expected-phrases)) + + (if (>= matches 2) + (progn + (message "\nāœ“āœ“āœ“ TEST PASSED āœ“āœ“āœ“") + (message "Monitor is correctly capturing speaker audio!")) + (error "TEST FAILED: Only matched %d/%d phrases" + matches (length test-recording--expected-phrases))))))) + + ;; Cleanup + (when (and playback-proc (process-live-p playback-proc)) + (kill-process playback-proc)) + (when (and cj/audio-recording-ffmpeg-process + (process-live-p cj/audio-recording-ffmpeg-process)) + (cj/audio-recording-stop)) + (when recording-file + (test-recording--cleanup-files recording-file)) + (when (file-exists-p test-dir) + (delete-directory test-dir t)) + (message "\nCleanup complete.")))) + +(provide 'test-integration-recording-monitor-capture-interactive) +;;; test-integration-recording-monitor-capture-interactive.el ends here -- cgit v1.2.3