Доработка документации и вывода сообщений при ошибках
This commit is contained in:
@@ -19,6 +19,7 @@ type Monitor struct {
|
||||
mu sync.RWMutex
|
||||
currentLevel int
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewMonitor() *Monitor {
|
||||
@@ -35,6 +36,17 @@ func (m *Monitor) Start() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m.cancel = cancel
|
||||
|
||||
if err := m.startCapture(ctx); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startCapture запускает arecord и горутину чтения потока. Может вызываться
|
||||
// повторно при пересборке пайпа после сбоя (см. handleCrash).
|
||||
func (m *Monitor) startCapture(ctx context.Context) error {
|
||||
// Используем гибкий поиск USB устройств
|
||||
device := m.findUSBDevice()
|
||||
log.Printf("Используем аудиоустройство: %s", device)
|
||||
@@ -48,56 +60,17 @@ func (m *Monitor) Start() error {
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Аудио-монитор запущен с устройством: %s", device)
|
||||
|
||||
go func() {
|
||||
buffer := make([]byte, 3200)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := io.ReadFull(stdout, buffer)
|
||||
if err != nil {
|
||||
log.Printf("Ошибка чтения аудио: %v", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
samplesCount := len(buffer) / 2
|
||||
samples := make([]int16, samplesCount)
|
||||
reader := bytes.NewReader(buffer)
|
||||
if err := binary.Read(reader, binary.LittleEndian, &samples); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var sum float64
|
||||
for _, sample := range samples {
|
||||
val := float64(sample)
|
||||
sum += val * val
|
||||
}
|
||||
|
||||
rms := math.Sqrt(sum / float64(samplesCount))
|
||||
level := int((rms / 32768.0) * 100 * 4)
|
||||
if level > 100 {
|
||||
level = 100
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.currentLevel = level
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
m.wg.Add(1)
|
||||
go m.readLoop(ctx, stdout)
|
||||
|
||||
go func() {
|
||||
_ = cmd.Wait()
|
||||
@@ -106,6 +79,83 @@ func (m *Monitor) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) readLoop(ctx context.Context, stdout io.ReadCloser) {
|
||||
defer m.wg.Done()
|
||||
|
||||
buffer := make([]byte, 3200)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
_, err := io.ReadFull(stdout, buffer)
|
||||
if err != nil {
|
||||
// Контекст отменён (Stop()) — пайп закрыт намеренно, тихо выходим.
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
// Дескриптор закрыт (например, cmd.Wait() уже реаппнул процесс) —
|
||||
// arecord умер сам по себе, пробуем восстановиться.
|
||||
if strings.Contains(err.Error(), "file already closed") {
|
||||
log.Printf("Аудио-пайп закрыт: %v", err)
|
||||
} else {
|
||||
log.Printf("Ошибка чтения аудио: %v", err)
|
||||
}
|
||||
m.handleCrash(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
samplesCount := len(buffer) / 2
|
||||
samples := make([]int16, samplesCount)
|
||||
reader := bytes.NewReader(buffer)
|
||||
if err := binary.Read(reader, binary.LittleEndian, &samples); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var sum float64
|
||||
for _, sample := range samples {
|
||||
val := float64(sample)
|
||||
sum += val * val
|
||||
}
|
||||
|
||||
rms := math.Sqrt(sum / float64(samplesCount))
|
||||
level := int((rms / 32768.0) * 100 * 4)
|
||||
if level > 100 {
|
||||
level = 100
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.currentLevel = level
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// handleCrash пытается пересобрать пайп arecord после того, как микрофон
|
||||
// отвалился на физическом уровне, не роняя всё приложение.
|
||||
func (m *Monitor) handleCrash(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Микрофон отключился, повторная попытка через 5 секунд...")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := m.startCapture(ctx); err != nil {
|
||||
log.Printf("Не удалось пересобрать аудио-пайп: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) findUSBDevice() string {
|
||||
cmd := exec.Command("arecord", "-l")
|
||||
output, err := cmd.Output()
|
||||
@@ -155,4 +205,5 @@ func (m *Monitor) Stop() {
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
m.wg.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user