mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
separate audio stream
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
set +H
|
||||||
|
|
||||||
|
ENV_FILE="${VIDEO_ENV_FILE:-/var/lib/roverd/video.env}"
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Environment file ${ENV_FILE} missing; cannot publish audio" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
: "${AUDIO_PUBLISH_URL:?AUDIO_PUBLISH_URL not set in ${ENV_FILE}}"
|
||||||
|
|
||||||
|
AUDIO_DEVICE="${AUDIO_DEVICE:-hw:0,0}"
|
||||||
|
AUDIO_RATE="${AUDIO_RATE:-48000}"
|
||||||
|
AUDIO_CHANNELS="${AUDIO_CHANNELS:-2}"
|
||||||
|
|
||||||
|
if [[ -n "${FFMPEG_BIN:-}" ]]; then
|
||||||
|
FFMPEG_BIN_PATH="$FFMPEG_BIN"
|
||||||
|
elif command -v ffmpeg >/dev/null 2>&1; then
|
||||||
|
FFMPEG_BIN_PATH="$(command -v ffmpeg)"
|
||||||
|
else
|
||||||
|
echo "ffmpeg not found; install it via apt install ffmpeg." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_pipeline() {
|
||||||
|
"${FFMPEG_BIN_PATH}" \
|
||||||
|
-hide_banner \
|
||||||
|
-loglevel warning \
|
||||||
|
-fflags nobuffer \
|
||||||
|
-rtbufsize 0 \
|
||||||
|
-thread_queue_size 4096 \
|
||||||
|
-f s32le \
|
||||||
|
-ar "${AUDIO_RATE}" \
|
||||||
|
-ac "${AUDIO_CHANNELS}" \
|
||||||
|
-i <(arecord -D "${AUDIO_DEVICE}" -f S32_LE -c "${AUDIO_CHANNELS}" -r "${AUDIO_RATE}" -B 65536 -F 2048 -q -t raw) \
|
||||||
|
-af "aresample=16000,pan=mono|c0=0.5*FL+0.5*FR" \
|
||||||
|
-c:a libopus \
|
||||||
|
-b:a 24000 \
|
||||||
|
-ar:a 16000 \
|
||||||
|
-ac:a 1 \
|
||||||
|
-application lowdelay \
|
||||||
|
-frame_duration 20 \
|
||||||
|
-compression_level 0 \
|
||||||
|
-f mpegts \
|
||||||
|
"${AUDIO_PUBLISH_URL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if run_pipeline; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Audio-only publisher exited, restarting in 2s..." >&2
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
@@ -206,6 +206,10 @@ install -D -o root -g root -m 0755 pi/bin/video-publisher.sh /usr/local/bin/vide
|
|||||||
log "Installed video-publisher helper"
|
log "Installed video-publisher helper"
|
||||||
install -m 0644 pi/systemd/video-publisher.service /etc/systemd/system/video-publisher.service
|
install -m 0644 pi/systemd/video-publisher.service /etc/systemd/system/video-publisher.service
|
||||||
log "Installed video-publisher systemd unit"
|
log "Installed video-publisher systemd unit"
|
||||||
|
# Install audio-only publisher assets
|
||||||
|
install -D -o root -g root -m 0755 pi/bin/audio-only-publisher.sh /usr/local/bin/audio-only-publisher
|
||||||
|
install -m 0644 pi/systemd/audio-only-publisher.service /etc/systemd/system/audio-only-publisher.service
|
||||||
|
log "Installed audio-only publisher helper + systemd unit"
|
||||||
# Install audio capture assets (arecord -> FIFO) [optional, not enabled by default]
|
# Install audio capture assets (arecord -> FIFO) [optional, not enabled by default]
|
||||||
install -D -o root -g root -m 0755 pi/bin/audio-capture.sh /usr/local/bin/audio-capture
|
install -D -o root -g root -m 0755 pi/bin/audio-capture.sh /usr/local/bin/audio-capture
|
||||||
install -m 0644 pi/systemd/audio-capture.service /etc/systemd/system/audio-capture.service
|
install -m 0644 pi/systemd/audio-capture.service /etc/systemd/system/audio-capture.service
|
||||||
@@ -214,6 +218,7 @@ install -d -o roverd -g roverd /var/lib/roverd
|
|||||||
cat > /var/lib/roverd/video.env <<'ENV'
|
cat > /var/lib/roverd/video.env <<'ENV'
|
||||||
# Managed by roverd; placeholder values will be overwritten at runtime.
|
# Managed by roverd; placeholder values will be overwritten at runtime.
|
||||||
PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316
|
PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316
|
||||||
|
AUDIO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-audio,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316
|
||||||
VIDEO_WIDTH=1280
|
VIDEO_WIDTH=1280
|
||||||
VIDEO_HEIGHT=720
|
VIDEO_HEIGHT=720
|
||||||
VIDEO_FPS=30
|
VIDEO_FPS=30
|
||||||
@@ -245,12 +250,14 @@ install_audio_support
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable roverd.service
|
systemctl enable roverd.service
|
||||||
systemctl enable video-publisher.service
|
systemctl enable video-publisher.service
|
||||||
|
systemctl enable audio-only-publisher.service
|
||||||
if [[ $CONFIG_EXISTS -eq 1 ]]; then
|
if [[ $CONFIG_EXISTS -eq 1 ]]; then
|
||||||
systemctl restart roverd.service
|
systemctl restart roverd.service
|
||||||
systemctl restart video-publisher.service
|
systemctl restart video-publisher.service
|
||||||
log "Restarted roverd + video publisher"
|
systemctl restart audio-only-publisher.service
|
||||||
|
log "Restarted roverd + video/audio publishers"
|
||||||
else
|
else
|
||||||
log "Skipped auto-start because config is the sample; edit $CONFIG_DEST then run: sudo systemctl restart roverd video-publisher"
|
log "Skipped auto-start because config is the sample; edit $CONFIG_DEST then run: sudo systemctl restart roverd video-publisher audio-only-publisher"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Install complete"
|
log "Install complete"
|
||||||
|
|||||||
+25
-15
@@ -66,16 +66,19 @@ type AudioConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MediaConfig struct {
|
type MediaConfig struct {
|
||||||
PublishURL string `yaml:"publishUrl" json:"publishUrl,omitempty"`
|
PublishURL string `yaml:"publishUrl" json:"publishUrl,omitempty"`
|
||||||
PublishPort int `yaml:"publishPort" json:"-"`
|
AudioPublishURL string `yaml:"audioPublishUrl" json:"audioPublishUrl,omitempty"`
|
||||||
Manage bool `yaml:"manage"`
|
PublishPort int `yaml:"publishPort" json:"-"`
|
||||||
Service string `yaml:"service"`
|
Manage bool `yaml:"manage"`
|
||||||
HealthURL string `yaml:"healthUrl"`
|
ManageAudio bool `yaml:"manageAudio"`
|
||||||
HealthInterval Duration `yaml:"healthInterval"`
|
Service string `yaml:"service"`
|
||||||
VideoWidth int `yaml:"videoWidth" json:"-"`
|
AudioService string `yaml:"audioService"`
|
||||||
VideoHeight int `yaml:"videoHeight" json:"-"`
|
HealthURL string `yaml:"healthUrl"`
|
||||||
VideoFPS int `yaml:"videoFps" json:"-"`
|
HealthInterval Duration `yaml:"healthInterval"`
|
||||||
VideoBitrate int `yaml:"videoBitrate" json:"-"`
|
VideoWidth int `yaml:"videoWidth" json:"-"`
|
||||||
|
VideoHeight int `yaml:"videoHeight" json:"-"`
|
||||||
|
VideoFPS int `yaml:"videoFps" json:"-"`
|
||||||
|
VideoBitrate int `yaml:"videoBitrate" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CameraServoConfig struct {
|
type CameraServoConfig struct {
|
||||||
@@ -201,6 +204,13 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
cfg.Media.PublishURL = derived
|
cfg.Media.PublishURL = derived
|
||||||
}
|
}
|
||||||
|
if cfg.Media.AudioPublishURL == "" {
|
||||||
|
derived, err := derivePublishURL(cfg.ServerURL, cfg.Name+"-audio", cfg.Media.PublishPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("derive audioPublishUrl: %w", err)
|
||||||
|
}
|
||||||
|
cfg.Media.AudioPublishURL = derived
|
||||||
|
}
|
||||||
if err := validateServoConfig(&cfg.CameraServo); err != nil {
|
if err := validateServoConfig(&cfg.CameraServo); err != nil {
|
||||||
return nil, fmt.Errorf("cameraServo: %w", err)
|
return nil, fmt.Errorf("cameraServo: %w", err)
|
||||||
}
|
}
|
||||||
@@ -268,9 +278,9 @@ func validateAudioConfig(cfg *AudioConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func derivePublishURL(serverURL, roverName string, port int) (string, error) {
|
func derivePublishURL(serverURL, streamName string, port int) (string, error) {
|
||||||
if roverName == "" {
|
if streamName == "" {
|
||||||
return "", errors.New("missing rover name for publishUrl")
|
return "", errors.New("missing stream name for publishUrl")
|
||||||
}
|
}
|
||||||
parsed, err := url.Parse(serverURL)
|
parsed, err := url.Parse(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -283,6 +293,6 @@ func derivePublishURL(serverURL, roverName string, port int) (string, error) {
|
|||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
port = 9000
|
port = 9000
|
||||||
}
|
}
|
||||||
streamName := url.PathEscape(roverName)
|
escaped := url.PathEscape(streamName)
|
||||||
return fmt.Sprintf("srt://%s:%d?streamid=#!::r=%s,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316", host, port, streamName), nil
|
return fmt.Sprintf("srt://%s:%d?streamid=#!::r=%s,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316", host, port, escaped), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
|
|||||||
if media.PublishURL == "" {
|
if media.PublishURL == "" {
|
||||||
return fmt.Errorf("media publishUrl missing")
|
return fmt.Errorf("media publishUrl missing")
|
||||||
}
|
}
|
||||||
|
if media.AudioPublishURL == "" && audio.CaptureEnabled {
|
||||||
|
return fmt.Errorf("audio publishUrl missing")
|
||||||
|
}
|
||||||
if media.VideoWidth <= 0 || media.VideoHeight <= 0 || media.VideoFPS <= 0 || media.VideoBitrate <= 0 {
|
if media.VideoWidth <= 0 || media.VideoHeight <= 0 || media.VideoFPS <= 0 || media.VideoBitrate <= 0 {
|
||||||
return fmt.Errorf("invalid media dimensions/bitrate")
|
return fmt.Errorf("invalid media dimensions/bitrate")
|
||||||
}
|
}
|
||||||
@@ -21,6 +24,9 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
|
|||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
fmt.Fprintf(&buf, "PUBLISH_URL=%s\n", media.PublishURL)
|
fmt.Fprintf(&buf, "PUBLISH_URL=%s\n", media.PublishURL)
|
||||||
|
if audio.CaptureEnabled && media.AudioPublishURL != "" {
|
||||||
|
fmt.Fprintf(&buf, "AUDIO_PUBLISH_URL=%s\n", media.AudioPublishURL)
|
||||||
|
}
|
||||||
fmt.Fprintf(&buf, "VIDEO_WIDTH=%d\n", media.VideoWidth)
|
fmt.Fprintf(&buf, "VIDEO_WIDTH=%d\n", media.VideoWidth)
|
||||||
fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight)
|
fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight)
|
||||||
fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS)
|
fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rover Audio Publisher (ALSA -> SRT)
|
||||||
|
After=network-online.target roverd.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=roverd
|
||||||
|
Group=roverd
|
||||||
|
EnvironmentFile=/var/lib/roverd/video.env
|
||||||
|
ExecStart=/usr/local/bin/audio-only-publisher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user