Add supported codec check for alsa source

This commit is contained in:
Alex X
2025-03-25 15:52:24 +03:00
parent e1342f06b7
commit 1b41f61247
5 changed files with 63 additions and 41 deletions

View File

@@ -44,7 +44,7 @@ func apiAlsa(w http.ResponseWriter, r *http.Request) {
info, err := dev.Info()
if err == nil {
formats := formatsToString(dev.ListFormats())
r1, r2 := dev.RangeSampleRates()
r1, r2 := dev.RangeRates()
c1, c2 := dev.RangeChannels()
source := &api.Source{
Name: info.ID + " / " + info.Name + " / " + info.SubName,

View File

@@ -3,6 +3,7 @@ package alsa
import (
"github.com/AlexxIT/go2rtc/pkg/alsa/device"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/pcm"
"github.com/pion/rtp"
)
@@ -33,19 +34,25 @@ func newCapture(dev *device.Device) (*Capture, error) {
}, nil
}
// readBufferSize - 20ms * 2 bytes per sample * 16000 frames per second * 2 channels / 1000ms per second
const readBufferSize = 20 * 2 * 16000 * 2 / 1000
// bytesPerFrame - 2 bytes per sample * 2 channels
const bytesPerFrame = 2 * 2
func (c *Capture) Start() error {
if err := c.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, 16000, 2); err != nil {
dst := c.Medias[0].Codecs[0]
src := &core.Codec{
Name: dst.Name,
ClockRate: c.dev.GetRateNear(dst.ClockRate),
Channels: c.dev.GetChannelsNear(dst.Channels),
}
if err := c.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, src.ClockRate, src.Channels); err != nil {
return err
}
transcode := transcodeFunc(dst, src)
frameBytes := int(pcm.BytesPerFrame(src))
var ts uint32
// readBufferSize for 20ms interval
readBufferSize := 20 * frameBytes * int(src.ClockRate) / 1000
b := make([]byte, readBufferSize)
for {
n, err := c.dev.Read(b)
@@ -65,25 +72,19 @@ func (c *Capture) Start() error {
Marker: true,
Timestamp: ts,
},
Payload: stereoToMono(b[:n]),
Payload: transcode(b[:n]),
}
c.Receivers[0].WriteRTP(pkt)
ts += uint32(n / bytesPerFrame)
ts += uint32(n / frameBytes)
}
}
func stereoToMono(stereo []byte) (mono []byte) {
n := len(stereo)
mono = make([]byte, n/2)
var i, j int
for i < n {
mono[j] = stereo[i]
j++
i++
mono[j] = stereo[i]
j++
i += 3
func transcodeFunc(dst, src *core.Codec) func([]byte) []byte {
if dst.ClockRate == src.ClockRate && dst.Channels == src.Channels {
return func(b []byte) []byte {
return b
}
}
return
return pcm.Transcode(dst, src)
}

View File

@@ -75,29 +75,55 @@ func (d *Device) Info() (*Info, error) {
}, nil
}
func (d *Device) CheckFormat(format byte) bool {
return d.checkMask(SNDRV_PCM_HW_PARAM_FORMAT, uint32(format))
}
func (d *Device) ListFormats() (formats []byte) {
for i := byte(0); i <= 28; i++ {
if d.checkMask(SNDRV_PCM_HW_PARAM_FORMAT, uint32(i)) {
if d.CheckFormat(i) {
formats = append(formats, i)
}
}
return
}
func (d *Device) RangeRates() (uint32, uint32) {
return d.getInterval(SNDRV_PCM_HW_PARAM_RATE)
}
func (d *Device) RangeChannels() (byte, byte) {
minCh, maxCh := d.getInterval(SNDRV_PCM_HW_PARAM_CHANNELS)
return byte(minCh), byte(maxCh)
}
func (d *Device) RangeSampleRates() (uint32, uint32) {
return d.getInterval(SNDRV_PCM_HW_PARAM_RATE)
func (d *Device) GetRateNear(rate uint32) uint32 {
r1, r2 := d.RangeRates()
if rate < r1 {
return r1
}
if rate > r2 {
return r2
}
return rate
}
func (d *Device) GetChannelsNear(channels byte) byte {
c1, c2 := d.RangeChannels()
if channels < c1 {
return c1
}
if channels > c2 {
return c2
}
return channels
}
const bufferSize = 4096
func (d *Device) SetHWParams(format byte, sampleRate uint32, channels byte) error {
func (d *Device) SetHWParams(format byte, rate uint32, channels byte) error {
d.setInterval(SNDRV_PCM_HW_PARAM_CHANNELS, uint32(channels))
d.setInterval(SNDRV_PCM_HW_PARAM_RATE, sampleRate)
d.setInterval(SNDRV_PCM_HW_PARAM_RATE, rate)
d.setMask(SNDRV_PCM_HW_PARAM_FORMAT, uint32(format))
//d.setMask(SNDRV_PCM_HW_PARAM_SUBFORMAT, 0)

View File

@@ -1,6 +1,7 @@
package alsa
import (
"errors"
"fmt"
"net/url"
@@ -26,6 +27,11 @@ func Open(rawURL string) (core.Producer, error) {
return nil, err
}
if !dev.CheckFormat(device.SNDRV_PCM_FORMAT_S16_LE) {
_ = dev.Close()
return nil, errors.New("alsa: format S16LE not supported")
}
switch path[len(path)-1] {
case 'p': // playback
return newPlayback(dev)
@@ -34,6 +40,5 @@ func Open(rawURL string) (core.Producer, error) {
}
_ = dev.Close()
return nil, fmt.Errorf("alsa: unknown path: %s", path)
}

View File

@@ -43,20 +43,10 @@ func (p *Playback) GetTrack(media *core.Media, codec *core.Codec) (*core.Receive
func (p *Playback) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
src := track.Codec
// support probe
if src.Name == core.CodecAny {
src = &core.Codec{
Name: core.CodecPCML,
ClockRate: 16000,
Channels: 2,
}
}
dst := &core.Codec{
Name: core.CodecPCML,
ClockRate: src.ClockRate,
Channels: 2,
ClockRate: p.dev.GetRateNear(src.ClockRate),
Channels: p.dev.GetChannelsNear(src.Channels),
}
sender := core.NewSender(media, dst)
@@ -74,7 +64,7 @@ func (p *Playback) AddTrack(media *core.Media, codec *core.Codec, track *core.Re
// - Formats: S16_LE, S32_LE
// - ClockRates: 8000 - 192000
// - Channels: 2 - 10
err := p.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, dst.ClockRate, 2)
err := p.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, dst.ClockRate, byte(dst.Channels))
if err != nil {
return err
}