recording sends back chunks to pipeline through frame ch

This commit is contained in:
LeonardoTrapani
2025-08-14 13:12:09 +02:00
parent a43adfcf90
commit 4852ad7ac1
3 changed files with 95 additions and 46 deletions
+41 -10
View File
@@ -172,36 +172,67 @@ func (d *Daemon) handle(c net.Conn) {
} }
func (d *Daemon) toggle() { func (d *Daemon) toggle() {
switch d.status { // Capture current state and prepare action under lock
d.mu.Lock()
status := d.status
var pipelineToStop pipeline.Pipeline
var actionChan chan<- pipeline.Action
switch status {
case Idle: case Idle:
// Defensive: if a prior pipeline exists, stop and wait before starting a new one // Clean up any existing pipeline first
if d.pipeline != nil { if d.pipeline != nil {
d.pipeline.Stop() pipelineToStop = d.pipeline
d.pipeline = nil d.pipeline = nil
} }
// Start new pipeline
p := pipeline.New() p := pipeline.New()
statusCh, actionCh := p.Run(d.ctx) statusCh, actionCh := p.Run(d.ctx)
d.pipeline = p d.pipeline = p
d.actionChannel = actionCh d.actionChannel = actionCh
go d.notifier.RecordingStarted() d.mu.Unlock()
// Stop old pipeline if needed (outside lock)
if pipelineToStop != nil {
pipelineToStop.Stop()
}
go d.notifier.RecordingStarted()
go d.startStatusReader(d.ctx, statusCh) go d.startStatusReader(d.ctx, statusCh)
case Recording: case Recording:
pipelineToStop = d.pipeline
d.mu.Unlock()
go d.notifier.RecordingEnded() go d.notifier.RecordingEnded()
d.pipeline.Stop() if pipelineToStop != nil {
pipelineToStop.Stop()
}
case Transcribing: case Transcribing:
select { actionChan = d.actionChannel
case d.actionChannel <- pipeline.Inject: d.mu.Unlock()
default:
// Try to inject (non-blocking)
if actionChan != nil {
select {
case actionChan <- pipeline.Inject:
default:
}
} }
case Injecting: case Injecting:
pipelineToStop = d.pipeline
d.mu.Unlock()
go d.notifier.RecordingEnded() go d.notifier.RecordingEnded()
d.pipeline.Stop() // aborted during injection if pipelineToStop != nil {
pipelineToStop.Stop() // aborted during injection
}
default:
d.mu.Unlock()
} }
} }
+51 -32
View File
@@ -5,6 +5,8 @@ import (
"log" "log"
"sync" "sync"
"time" "time"
"github.com/leonardotrapani/hyprvoice/internal/recording"
) )
type Status string type Status string
@@ -61,45 +63,62 @@ func (p *pipeline) run(ctx context.Context, statusCh chan<- Status) {
log.Printf("Pipeline: Starting recording") log.Printf("Pipeline: Starting recording")
statusCh <- Recording statusCh <- Recording
// Recording phase recorder := recording.NewDefaultRecorder()
select { frameCh, errCh, err := recorder.Start(ctx)
case <-time.After(2 * time.Second): if err != nil {
log.Printf("Pipeline: TODO start recording, and on first chunk set transcribing and start streaming with Whisper") log.Printf("Pipeline: Recording error: %v", err)
statusCh <- Transcribing statusCh <- Idle
case <-ctx.Done():
log.Printf("Pipeline: Stopped during recording")
return return
} }
defer recorder.Stop()
// TODO: when integrating the recorder, ensure its context is cancelled here on exit paths // Track statistics for logging
// Wait for an action or timeout frameCount := 0
select { totalBytes := 0
case action := <-p.actionCh: startTime := time.Now()
switch action {
case Inject: // Main event loop
log.Printf("Pipeline: Injection started") for {
statusCh <- Injecting select {
case frame, ok := <-frameCh:
if !ok {
// Frame channel closed, recording ended
log.Printf("Pipeline: Recording ended. Total frames: %d, Total bytes: %d, Duration: %v",
frameCount, totalBytes, time.Since(startTime))
statusCh <- Transcribing
// Injection work
select {
case <-time.After(1 * time.Second):
log.Printf("Pipeline: Injection complete")
statusCh <- Idle
case <-ctx.Done():
log.Printf("Pipeline: Stopped during injection")
return return
} }
default: // Log frame details
// Unknown action: ignore for now frameCount++
totalBytes += len(frame.Data)
log.Printf("Pipeline: Received frame #%d - Size: %d bytes, Timestamp: %v, Total bytes so far: %d",
frameCount, len(frame.Data), frame.Timestamp.Format("15:04:05.000"), totalBytes)
// TODO: pass this channel to the transcriber
case err := <-errCh:
if err != nil {
log.Printf("Pipeline: Recording error: %v", err)
statusCh <- Idle
return
}
case action := <-p.actionCh:
log.Printf("Pipeline: Received action: %v", action)
if action == Inject {
log.Printf("Pipeline: Inject action received, stopping recording")
if err := recorder.Stop(); err != nil {
log.Printf("Pipeline: Error stopping recorder: %v", err)
}
statusCh <- Injecting
return
}
case <-ctx.Done():
log.Printf("Pipeline: Context cancelled, stopping")
return
} }
case <-time.After(10 * time.Second): // use context timeout
log.Printf("Pipeline: Auto-timeout, completing")
statusCh <- Idle // Instead of Completed
case <-ctx.Done():
log.Printf("Pipeline: Stopped during transcription wait")
return
} }
} }
+3 -4
View File
@@ -32,7 +32,7 @@ func DefaultConfig() Config {
return Config{ return Config{
SampleRate: 16000, SampleRate: 16000,
Channels: 1, Channels: 1,
Format: "s16le", Format: "s16",
BufferSize: 4096, BufferSize: 4096,
Device: "", Device: "",
ChannelBufferSize: 20, ChannelBufferSize: 20,
@@ -175,7 +175,6 @@ func (r *Recorder) captureLoop(ctx context.Context, frameCh chan<- AudioFrame, e
case frameCh <- frame: case frameCh <- frame:
sentCount++ sentCount++
case <-ctx.Done(): case <-ctx.Done():
// Context cancelled, stop cleanly after writing any pending frames.
return return
default: default:
droppedCount++ droppedCount++
@@ -266,8 +265,8 @@ func (r *Recorder) validateConfig() error {
if r.config.Format == "" { if r.config.Format == "" {
return fmt.Errorf("invalid Format: empty") return fmt.Errorf("invalid Format: empty")
} }
// For s16le, sample frame size is 2 bytes per sample per channel. // For s16, sample frame size is 2 bytes per sample per channel.
if r.config.Format == "s16le" { if r.config.Format == "s16" {
frameBytes := 2 * r.config.Channels frameBytes := 2 * r.config.Channels
if r.config.BufferSize%frameBytes != 0 { if r.config.BufferSize%frameBytes != 0 {
log.Printf("Recording: BufferSize %d not aligned to frame size %d; audio frames may split", log.Printf("Recording: BufferSize %d not aligned to frame size %d; audio frames may split",