Fix: supressed alsa warnings

This commit is contained in:
Grail Finder
2025-11-12 12:20:55 +03:00
parent e1b260c474
commit 2695cf437e
2 changed files with 50 additions and 47 deletions

View File

@@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"syscall"
"github.com/gordonklaus/portaudio" "github.com/gordonklaus/portaudio"
) )
@@ -145,6 +146,21 @@ func (stt *WhisperServer) IsRecording() bool {
} }
func (stt *WhisperServer) microphoneStream(sampleRate int) error { func (stt *WhisperServer) microphoneStream(sampleRate int) error {
// Temporarily redirect stderr to suppress ALSA warnings during PortAudio init
origStderr, err := syscall.Dup(syscall.Stderr)
nullFD, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0)
if err != nil {
return fmt.Errorf("failed to open /dev/null: %w", err)
}
// redirect stderr
syscall.Dup2(nullFD, syscall.Stderr)
// Initialize PortAudio (this is where ALSA warnings occur)
defer func() {
// Restore stderr
syscall.Dup2(origStderr, syscall.Stderr)
syscall.Close(origStderr)
syscall.Close(nullFD)
}()
if err := portaudio.Initialize(); err != nil { if err := portaudio.Initialize(); err != nil {
return fmt.Errorf("portaudio init failed: %w", err) return fmt.Errorf("portaudio init failed: %w", err)
} }

View File

@@ -1,5 +1,22 @@
package extra package extra
/*
#cgo LDFLAGS: -lasound
#include <alsa/asoundlib.h>
extern void go_alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...);
void set_alsa_error_handler() {
snd_lib_error_set_handler(go_alsa_error_handler);
}
void go_alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) {
return; // Complete suppression
}
*/
import "C"
import ( import (
"bytes" "bytes"
"context" "context"
@@ -10,8 +27,9 @@ import (
"log/slog" "log/slog"
"os" "os"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"time" "syscall"
"github.com/gordonklaus/portaudio" "github.com/gordonklaus/portaudio"
) )
@@ -30,6 +48,8 @@ type WhisperBinary struct {
func NewWhisperBinary(logger *slog.Logger, cfg *config.Config) *WhisperBinary { func NewWhisperBinary(logger *slog.Logger, cfg *config.Config) *WhisperBinary {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
// Set ALSA error handler first
C.set_alsa_error_handler()
return &WhisperBinary{ return &WhisperBinary{
logger: logger, logger: logger,
whisperPath: cfg.WhisperBinaryPath, whisperPath: cfg.WhisperBinaryPath,
@@ -46,33 +66,25 @@ func (w *WhisperBinary) StartRecording() error {
if w.recording { if w.recording {
return errors.New("recording is already in progress") return errors.New("recording is already in progress")
} }
// Temporarily redirect stderr to suppress ALSA warnings during PortAudio init // Temporarily redirect stderr to suppress ALSA warnings during PortAudio init
origStderr := os.Stderr origStderr, err := syscall.Dup(syscall.Stderr)
origStdout := os.Stdout nullFD, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0)
nullFile, err := os.OpenFile("/dev/null", os.O_WRONLY, 0)
if err != nil { if err != nil {
return fmt.Errorf("failed to open /dev/null: %w", err) return fmt.Errorf("failed to open /dev/null: %w", err)
} }
defer nullFile.Close() // redirect stderr
syscall.Dup2(nullFD, syscall.Stderr)
// Redirect stderr temporarily
os.Stderr = nullFile
os.Stdout = nullFile
// Initialize PortAudio (this is where ALSA warnings occur) // Initialize PortAudio (this is where ALSA warnings occur)
portaudioErr := portaudio.Initialize() portaudioErr := portaudio.Initialize()
defer func() { defer func() {
// Restore stderr // Restore stderr
os.Stderr = origStderr syscall.Dup2(origStderr, syscall.Stderr)
os.Stdout = origStdout syscall.Close(origStderr)
syscall.Close(nullFD)
}() }()
if portaudioErr != nil { if portaudioErr != nil {
return fmt.Errorf("portaudio init failed: %w", portaudioErr) return fmt.Errorf("portaudio init failed: %w", portaudioErr)
} }
// Initialize audio buffer // Initialize audio buffer
w.audioBuffer = make([]int16, 0) w.audioBuffer = make([]int16, 0)
in := make([]int16, 1024) // buffer size in := make([]int16, 1024) // buffer size
@@ -83,7 +95,6 @@ func (w *WhisperBinary) StartRecording() error {
} }
return fmt.Errorf("failed to open microphone: %w", err) return fmt.Errorf("failed to open microphone: %w", err)
} }
go w.recordAudio(stream, in) go w.recordAudio(stream, in)
w.recording = true w.recording = true
w.logger.Debug("Recording started") w.logger.Debug("Recording started")
@@ -91,26 +102,6 @@ func (w *WhisperBinary) StartRecording() error {
} }
func (w *WhisperBinary) recordAudio(stream *portaudio.Stream, in []int16) { func (w *WhisperBinary) recordAudio(stream *portaudio.Stream, in []int16) {
// Temporarily redirect stderr to suppress ALSA warnings during recording operations
origStderr := os.Stderr
origStdout := os.Stdout
nullFile, err := os.OpenFile("/dev/null", os.O_WRONLY, 0)
if err != nil {
// If we can't open /dev/null, log the error but continue anyway
w.logger.Error("Failed to open /dev/null for stderr redirection", "error", err)
// Continue without stderr redirection
} else {
// Redirect stderr temporarily for the duration of this function
os.Stderr = nullFile
os.Stdout = nullFile
defer func() {
// Restore stderr when function exits
os.Stderr = origStderr
os.Stdout = origStdout
nullFile.Close()
}()
}
defer func() { defer func() {
w.logger.Debug("recordAudio defer function called") w.logger.Debug("recordAudio defer function called")
_ = stream.Stop() // Stop the stream _ = stream.Stop() // Stop the stream
@@ -167,10 +158,8 @@ func (w *WhisperBinary) StopRecording() (string, error) {
w.recording = false w.recording = false
w.cancel() // This will stop the recording goroutine w.cancel() // This will stop the recording goroutine
w.mu.Unlock() w.mu.Unlock()
// // Small delay to allow the recording goroutine to react to context cancellation
// Small delay to allow the recording goroutine to react to context cancellation // time.Sleep(20 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
// Save the recorded audio to a temporary file // Save the recorded audio to a temporary file
tempFile, err := w.saveAudioToTempFile() tempFile, err := w.saveAudioToTempFile()
if err != nil { if err != nil {
@@ -178,14 +167,12 @@ func (w *WhisperBinary) StopRecording() (string, error) {
return "", fmt.Errorf("failed to save audio to temp file: %w", err) return "", fmt.Errorf("failed to save audio to temp file: %w", err)
} }
w.logger.Debug("Saved audio to temp file", "file", tempFile) w.logger.Debug("Saved audio to temp file", "file", tempFile)
// Run the whisper binary with a separate context to avoid cancellation during transcription // Run the whisper binary with a separate context to avoid cancellation during transcription
cmd := exec.Command(w.whisperPath, "-m", w.modelPath, "-l", w.lang, tempFile, "2>/dev/null") cmd := exec.Command(w.whisperPath, "-m", w.modelPath, "-l", w.lang, tempFile, "2>/dev/null")
var outBuf bytes.Buffer var outBuf bytes.Buffer
cmd.Stdout = &outBuf cmd.Stdout = &outBuf
// Redirect stderr to suppress ALSA warnings and other stderr output // Redirect stderr to suppress ALSA warnings and other stderr output
cmd.Stderr = io.Discard // Suppress stderr output from whisper binary cmd.Stderr = io.Discard // Suppress stderr output from whisper binary
w.logger.Debug("Running whisper binary command") w.logger.Debug("Running whisper binary command")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
// Clean up audio buffer // Clean up audio buffer
@@ -198,17 +185,17 @@ func (w *WhisperBinary) StopRecording() (string, error) {
} }
result := outBuf.String() result := outBuf.String()
w.logger.Debug("Whisper binary completed", "result", result) w.logger.Debug("Whisper binary completed", "result", result)
// Clean up audio buffer // Clean up audio buffer
w.mu.Lock() w.mu.Lock()
w.audioBuffer = nil w.audioBuffer = nil
w.mu.Unlock() w.mu.Unlock()
// Clean up the temporary file after transcription // Clean up the temporary file after transcription
w.logger.Debug("StopRecording completed") w.logger.Debug("StopRecording completed")
os.Remove(tempFile) os.Remove(tempFile)
result = strings.TrimRight(result, "\n")
return result, nil // in case there are special tokens like [_BEG_]
result = specialRE.ReplaceAllString(result, "")
return strings.TrimSpace(strings.ReplaceAll(result, "\n ", "\n")), nil
} }
// saveAudioToTempFile saves the recorded audio data to a temporary WAV file // saveAudioToTempFile saves the recorded audio data to a temporary WAV file