Accept Windows surround audio on Mini 2
This commit is contained in:
+1
-1
@@ -88,7 +88,7 @@ mount -o remount,ro /
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags='-s -w' -o scream-receiver ./cmd/scream-receiver
|
||||
```
|
||||
|
||||
Скопируй бинарник на станцию в `/data/opt/alice-backend` и запусти из этого каталога. Укажи адрес станции и адрес Windows. Приёмник работает с запущенным PulseAudio, принимает моно или стерео PCM в пакетах Scream и воспроизводит их через вход `music`. На этот вход действуют Room EQ и приглушение на время разговора.
|
||||
Скопируй бинарник на станцию в `/data/opt/alice-backend` и запусти из этого каталога. Укажи адрес станции и адрес Windows. Приёмник работает с запущенным PulseAudio, принимает до восьми каналов PCM в пакетах Scream и сводит их в PulseAudio и воспроизводит их через вход `music`. На этот вход действуют Room EQ и приглушение на время разговора.
|
||||
|
||||
```sh
|
||||
./scream-receiver -listen 192.168.1.135:4010 -source 192.168.1.170
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -21,13 +23,48 @@ const (
|
||||
)
|
||||
|
||||
type format struct {
|
||||
rate int
|
||||
channels byte
|
||||
sample string
|
||||
rate int
|
||||
channels byte
|
||||
sample string
|
||||
channelMap string
|
||||
}
|
||||
|
||||
// Windows sends channels in ascending order of the speaker mask bits.
|
||||
var speakerPositions = [...]string{
|
||||
"front-left", "front-right", "front-center", "lfe",
|
||||
"rear-left", "rear-right", "front-left-of-center", "front-right-of-center",
|
||||
"rear-center", "side-left", "side-right", "top-center",
|
||||
"top-front-left", "top-front-center", "top-front-right", "top-rear-left",
|
||||
}
|
||||
|
||||
func channelMap(channels byte, mask uint16) string {
|
||||
if channels == 1 {
|
||||
return "mono"
|
||||
}
|
||||
if channels == 2 {
|
||||
return "front-left,front-right"
|
||||
}
|
||||
positions := make([]string, 0, channels)
|
||||
for bit, position := range speakerPositions {
|
||||
if mask&(1<<bit) != 0 {
|
||||
positions = append(positions, position)
|
||||
}
|
||||
}
|
||||
if len(positions) == int(channels) {
|
||||
return strings.Join(positions, ",")
|
||||
}
|
||||
// Some endpoints omit the speaker mask. Use the conventional surround order.
|
||||
if channels == 6 {
|
||||
return "front-left,front-right,front-center,lfe,rear-left,rear-right"
|
||||
}
|
||||
if channels == 8 {
|
||||
return "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func packetFormat(packet []byte) (format, bool) {
|
||||
if len(packet) != packetBytes || packet[2] < 1 || packet[2] > 2 {
|
||||
if len(packet) != packetBytes || packet[2] < 1 || packet[2] > 8 {
|
||||
return format{}, false
|
||||
}
|
||||
rate := 48000
|
||||
@@ -49,7 +86,11 @@ func packetFormat(packet []byte) (format, bool) {
|
||||
default:
|
||||
return format{}, false
|
||||
}
|
||||
return format{rate * multiple, packet[2], sample}, true
|
||||
if (packetBytes-headerBytes)%(int(packet[2])*int(packet[1]/8)) != 0 {
|
||||
return format{}, false
|
||||
}
|
||||
layout := channelMap(packet[2], binary.BigEndian.Uint16(packet[3:5]))
|
||||
return format{rate * multiple, packet[2], sample, layout}, layout != ""
|
||||
}
|
||||
|
||||
const pulse = "/data/opt/alice-backend/spotify"
|
||||
@@ -63,7 +104,8 @@ type playback struct {
|
||||
func startPlayback(f format, latency int) (*playback, error) {
|
||||
cmd := exec.Command(pulse+"/run", pulse+"/bin/pacat", "--playback", "--raw",
|
||||
"--device=music", "--format="+f.sample, "--rate="+strconv.Itoa(f.rate),
|
||||
"--channels="+strconv.Itoa(int(f.channels)), "--latency-msec="+strconv.Itoa(latency),
|
||||
"--channels="+strconv.Itoa(int(f.channels)), "--channel-map="+f.channelMap,
|
||||
"--latency-msec="+strconv.Itoa(latency),
|
||||
"--client-name=PC Audio", "--stream-name=Scream")
|
||||
cmd.Env = append(os.Environ(), "PULSE_SERVER=unix:/tmp/alice-pulse/native",
|
||||
"PULSE_RUNTIME_PATH=/tmp/alice-pulse", "PULSE_COOKIE=/tmp/alice-pulse/cookie")
|
||||
@@ -103,6 +145,7 @@ func receive(conn *net.UDPConn, peer netip.Addr, latency int) error {
|
||||
packet := make([]byte, packetBytes+1)
|
||||
var lastSeen time.Time
|
||||
var retryAfter time.Time
|
||||
var lastRejected time.Time
|
||||
for {
|
||||
if current != nil {
|
||||
if err := conn.SetReadDeadline(lastSeen.Add(idleTimeout)); err != nil {
|
||||
@@ -126,6 +169,10 @@ func receive(conn *net.UDPConn, peer netip.Addr, latency int) error {
|
||||
}
|
||||
f, valid := packetFormat(packet[:n])
|
||||
if !valid {
|
||||
if time.Since(lastRejected) >= 30*time.Second {
|
||||
log.Printf("unsupported Scream packet from %s: %d bytes, header %x", peer, n, packet[:min(n, headerBytes)])
|
||||
lastRejected = time.Now()
|
||||
}
|
||||
continue
|
||||
}
|
||||
lastSeen = time.Now()
|
||||
|
||||
Reference in New Issue
Block a user