Fix: supressed alsa warnings
This commit is contained in:
16
extra/stt.go
16
extra/stt.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user