Add support alsa source

This commit is contained in:
Alex X
2025-03-20 20:58:38 +03:00
parent bad7caa187
commit 7415776e4d
18 changed files with 1265 additions and 1 deletions

7
internal/alsa/alsa.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build !(linux && (386 || amd64 || arm || arm64 || mipsle))
package alsa
func Init() {
// not supported
}

View File

@@ -0,0 +1,80 @@
//go:build linux && (386 || amd64 || arm || arm64 || mipsle)
package alsa
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/alsa"
"github.com/AlexxIT/go2rtc/pkg/alsa/device"
)
func Init() {
streams.HandleFunc("alsa", alsa.Open)
api.HandleFunc("api/alsa", apiAlsa)
}
func apiAlsa(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir("/dev/snd/")
if err != nil {
return
}
var sources []*api.Source
for _, file := range files {
if !strings.HasPrefix(file.Name(), "pcm") {
continue
}
path := "/dev/snd/" + file.Name()
dev, err := device.Open(path)
if err != nil {
continue
}
info, err := dev.Info()
if err == nil {
formats := formatsToString(dev.ListFormats())
r1, r2 := dev.RangeSampleRates()
c1, c2 := dev.RangeChannels()
source := &api.Source{
Name: info.ID + " / " + info.Name + " / " + info.SubName,
Info: fmt.Sprintf("Formats: %s, Rates: %d-%d, Channels: %d-%d", formats, r1, r2, c1, c2),
URL: "alsa:device?audio=" + path,
}
sources = append(sources, source)
}
_ = dev.Close()
}
api.ResponseSources(w, sources)
}
func formatsToString(formats []byte) string {
var s string
for i, format := range formats {
if i > 0 {
s += " "
}
switch format {
case 2:
s += "s16le"
case 10:
s += "s32le"
default:
s += strconv.Itoa(int(format))
}
}
return s
}

View File

@@ -113,6 +113,7 @@ var defaults = map[string]string{
"pcm/48000": "-c:a pcm_s16be -ar:a 48000 -ac:a 1",
"pcml": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
"pcml/8000": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
"pcml/16000": "-c:a pcm_s16le -ar:a 16000 -ac:a 1",
"pcml/44100": "-c:a pcm_s16le -ar:a 44100 -ac:a 1",
// hardware Intel and AMD on Linux

View File

@@ -42,6 +42,7 @@ func NewProducer(url string) (core.Producer, error) {
Codecs: []*core.Codec{
// OPUS will always marked as OPUS/48000/2
{Name: core.CodecOpus, ClockRate: 48000, Channels: 2},
{Name: core.CodecPCML, ClockRate: 16000},
{Name: core.CodecPCM, ClockRate: 16000},
{Name: core.CodecPCMA, ClockRate: 16000},
{Name: core.CodecPCMU, ClockRate: 16000},
@@ -97,6 +98,8 @@ func (p *Producer) newURL() string {
s += "#audio=opus"
case core.CodecAAC:
s += "#audio=aac/16000"
case core.CodecPCML:
s += "#audio=pcml/16000"
case core.CodecPCM:
s += "#audio=pcm/" + strconv.Itoa(int(codec.ClockRate))
case core.CodecPCMA:

View File

@@ -1,6 +1,7 @@
package main
import (
"github.com/AlexxIT/go2rtc/internal/alsa"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/api/ws"
"github.com/AlexxIT/go2rtc/internal/app"
@@ -90,6 +91,7 @@ func main() {
gopro.Init() // gopro source
doorbird.Init() // doorbird source
v4l2.Init() // v4l2 source
alsa.Init() // alsa source
flussonic.Init()
eseecloud.Init()

View File

@@ -13,6 +13,7 @@ Some formats and protocols go2rtc supports exclusively. They have no equivalent
| Format | Source protocols | Ingress protocols | Recevers codecs | Senders codecs | Example |
|--------------|------------------|-------------------|------------------------------|--------------------|---------------|
| adts | http,tcp,pipe | http | aac | | `http:` |
| alsa | pipe | | | pcm | `alsa:` |
| bubble | http | | h264,hevc,pcm_alaw | | `bubble:` |
| dvrip | tcp | | h264,hevc,pcm_alaw,pcm_mulaw | pcm_alaw | `dvrip:` |
| flv | http,tcp,pipe | http | h264,aac | | `http:` |

23
pkg/alsa/README.md Normal file
View File

@@ -0,0 +1,23 @@
## Build
```shell
x86_64-linux-gnu-gcc -w -static asound_arch.c -o asound_amd64
i686-linux-gnu-gcc -w -static asound_arch.c -o asound_i386
aarch64-linux-gnu-gcc -w -static asound_arch.c -o asound_arm64
arm-linux-gnueabihf-gcc -w -static asound_arch.c -o asound_arm
mipsel-linux-gnu-gcc -w -static asound_arch.c -o asound_mipsle -D_TIME_BITS=32
```
## Useful links
- https://github.com/torvalds/linux/blob/master/include/uapi/sound/asound.h
- https://github.com/yobert/alsa
- https://github.com/Narsil/alsa-go
- https://github.com/alsa-project/alsa-lib
- https://github.com/anisse/alsa
- https://github.com/tinyalsa/tinyalsa
**Broken pipe**
- https://stackoverflow.com/questions/26545139/alsa-cannot-recovery-from-underrun-prepare-failed-broken-pipe
- https://klipspringer.avadeaux.net/alsa-broken-pipe-errors/

89
pkg/alsa/capture_linux.go Normal file
View File

@@ -0,0 +1,89 @@
package alsa
import (
"github.com/AlexxIT/go2rtc/pkg/alsa/device"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/rtp"
)
type Capture struct {
core.Connection
dev *device.Device
closed core.Waiter
}
func newCapture(dev *device.Device) (*Capture, error) {
medias := []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{Name: core.CodecPCML, ClockRate: 16000},
},
},
}
return &Capture{
Connection: core.Connection{
ID: core.NewID(),
FormatName: "alsa",
Medias: medias,
Transport: dev,
},
dev: dev,
}, 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 {
return err
}
var ts uint32
b := make([]byte, readBufferSize)
for {
n, err := c.dev.Read(b)
if err != nil {
return err
}
c.Recv += n
if len(c.Receivers) == 0 {
continue
}
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
Timestamp: ts,
},
Payload: stereoToMono(b[:n]),
}
c.Receivers[0].WriteRTP(pkt)
ts += uint32(n / bytesPerFrame)
}
}
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
}
return
}

View File

@@ -0,0 +1,148 @@
//go:build 386 || arm
package device
type unsigned_char = byte
type signed_int = int32
type unsigned_int = uint32
type signed_long = int64
type unsigned_long = uint64
type __u32 = uint32
type void__user = uintptr
const (
SNDRV_PCM_STREAM_PLAYBACK = 0
SNDRV_PCM_STREAM_CAPTURE = 1
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED = 0
SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED = 1
SNDRV_PCM_ACCESS_MMAP_COMPLEX = 2
SNDRV_PCM_ACCESS_RW_INTERLEAVED = 3
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED = 4
SNDRV_PCM_FORMAT_S8 = 0
SNDRV_PCM_FORMAT_U8 = 1
SNDRV_PCM_FORMAT_S16_LE = 2
SNDRV_PCM_FORMAT_S16_BE = 3
SNDRV_PCM_FORMAT_U16_LE = 4
SNDRV_PCM_FORMAT_U16_BE = 5
SNDRV_PCM_FORMAT_S24_LE = 6
SNDRV_PCM_FORMAT_S24_BE = 7
SNDRV_PCM_FORMAT_U24_LE = 8
SNDRV_PCM_FORMAT_U24_BE = 9
SNDRV_PCM_FORMAT_S32_LE = 10
SNDRV_PCM_FORMAT_S32_BE = 11
SNDRV_PCM_FORMAT_U32_LE = 12
SNDRV_PCM_FORMAT_U32_BE = 13
SNDRV_PCM_FORMAT_FLOAT_LE = 14
SNDRV_PCM_FORMAT_FLOAT_BE = 15
SNDRV_PCM_FORMAT_FLOAT64_LE = 16
SNDRV_PCM_FORMAT_FLOAT64_BE = 17
SNDRV_PCM_FORMAT_MU_LAW = 20
SNDRV_PCM_FORMAT_A_LAW = 21
SNDRV_PCM_FORMAT_MPEG = 23
SNDRV_PCM_IOCTL_PVERSION = 0x80044100
SNDRV_PCM_IOCTL_INFO = 0x81204101
SNDRV_PCM_IOCTL_HW_REFINE = 0xc25c4110
SNDRV_PCM_IOCTL_HW_PARAMS = 0xc25c4111
SNDRV_PCM_IOCTL_SW_PARAMS = 0xc0684113
SNDRV_PCM_IOCTL_PREPARE = 0x00004140
SNDRV_PCM_IOCTL_WRITEI_FRAMES = 0x400c4150
SNDRV_PCM_IOCTL_READI_FRAMES = 0x800c4151
)
type snd_pcm_info struct { // size 288
device unsigned_int // offset 0, size 4
subdevice unsigned_int // offset 4, size 4
stream signed_int // offset 8, size 4
card signed_int // offset 12, size 4
id [64]unsigned_char // offset 16, size 64
name [80]unsigned_char // offset 80, size 80
subname [32]unsigned_char // offset 160, size 32
dev_class signed_int // offset 192, size 4
dev_subclass signed_int // offset 196, size 4
subdevices_count unsigned_int // offset 200, size 4
subdevices_avail unsigned_int // offset 204, size 4
pad1 [16]unsigned_char
reserved [64]unsigned_char // offset 224, size 64
}
type snd_pcm_uframes_t = unsigned_long
type snd_pcm_sframes_t = signed_long
type snd_xferi struct { // size 12
result snd_pcm_sframes_t // offset 0, size 4
buf void__user // offset 4, size 4
frames snd_pcm_uframes_t // offset 8, size 4
}
const (
SNDRV_PCM_HW_PARAM_ACCESS = 0
SNDRV_PCM_HW_PARAM_FORMAT = 1
SNDRV_PCM_HW_PARAM_SUBFORMAT = 2
SNDRV_PCM_HW_PARAM_FIRST_MASK = 0
SNDRV_PCM_HW_PARAM_LAST_MASK = 2
SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8
SNDRV_PCM_HW_PARAM_FRAME_BITS = 9
SNDRV_PCM_HW_PARAM_CHANNELS = 10
SNDRV_PCM_HW_PARAM_RATE = 11
SNDRV_PCM_HW_PARAM_PERIOD_TIME = 12
SNDRV_PCM_HW_PARAM_PERIOD_SIZE = 13
SNDRV_PCM_HW_PARAM_PERIOD_BYTES = 14
SNDRV_PCM_HW_PARAM_PERIODS = 15
SNDRV_PCM_HW_PARAM_BUFFER_TIME = 16
SNDRV_PCM_HW_PARAM_BUFFER_SIZE = 17
SNDRV_PCM_HW_PARAM_BUFFER_BYTES = 18
SNDRV_PCM_HW_PARAM_TICK_TIME = 19
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8
SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
SNDRV_MASK_MAX = 256
SNDRV_PCM_TSTAMP_NONE = 0
SNDRV_PCM_TSTAMP_ENABLE = 1
)
type snd_mask struct { // size 32
bits [(SNDRV_MASK_MAX + 31) / 32]__u32 // offset 0, size 32
}
type snd_interval struct { // size 12
min unsigned_int // offset 0, size 4
max unsigned_int // offset 4, size 4
bit unsigned_int
}
type snd_pcm_hw_params struct { // size 604
flags unsigned_int // offset 0, size 4
masks [SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]snd_mask // offset 4, size 96
mres [5]snd_mask // offset 100, size 160
intervals [SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]snd_interval // offset 260, size 144
ires [9]snd_interval // offset 404, size 108
rmask unsigned_int // offset 512, size 4
cmask unsigned_int // offset 516, size 4
info unsigned_int // offset 520, size 4
msbits unsigned_int // offset 524, size 4
rate_num unsigned_int // offset 528, size 4
rate_den unsigned_int // offset 532, size 4
fifo_size snd_pcm_uframes_t // offset 536, size 4
reserved [64]unsigned_char // offset 540, size 64
}
type snd_pcm_sw_params struct { // size 104
tstamp_mode signed_int // offset 0, size 4
period_step unsigned_int // offset 4, size 4
sleep_min unsigned_int // offset 8, size 4
avail_min snd_pcm_uframes_t // offset 12, size 4
xfer_align snd_pcm_uframes_t // offset 16, size 4
start_threshold snd_pcm_uframes_t // offset 20, size 4
stop_threshold snd_pcm_uframes_t // offset 24, size 4
silence_threshold snd_pcm_uframes_t // offset 28, size 4
silence_size snd_pcm_uframes_t // offset 32, size 4
boundary snd_pcm_uframes_t // offset 36, size 4
proto unsigned_int // offset 40, size 4
tstamp_type unsigned_int // offset 44, size 4
reserved [56]unsigned_char // offset 48, size 56
}

View File

@@ -0,0 +1,148 @@
//go:build amd64 || arm64
package device
type unsigned_char = byte
type signed_int = int32
type unsigned_int = uint32
type signed_long = int64
type unsigned_long = uint64
type __u32 = uint32
type void__user = uintptr
const (
SNDRV_PCM_STREAM_PLAYBACK = 0
SNDRV_PCM_STREAM_CAPTURE = 1
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED = 0
SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED = 1
SNDRV_PCM_ACCESS_MMAP_COMPLEX = 2
SNDRV_PCM_ACCESS_RW_INTERLEAVED = 3
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED = 4
SNDRV_PCM_FORMAT_S8 = 0
SNDRV_PCM_FORMAT_U8 = 1
SNDRV_PCM_FORMAT_S16_LE = 2
SNDRV_PCM_FORMAT_S16_BE = 3
SNDRV_PCM_FORMAT_U16_LE = 4
SNDRV_PCM_FORMAT_U16_BE = 5
SNDRV_PCM_FORMAT_S24_LE = 6
SNDRV_PCM_FORMAT_S24_BE = 7
SNDRV_PCM_FORMAT_U24_LE = 8
SNDRV_PCM_FORMAT_U24_BE = 9
SNDRV_PCM_FORMAT_S32_LE = 10
SNDRV_PCM_FORMAT_S32_BE = 11
SNDRV_PCM_FORMAT_U32_LE = 12
SNDRV_PCM_FORMAT_U32_BE = 13
SNDRV_PCM_FORMAT_FLOAT_LE = 14
SNDRV_PCM_FORMAT_FLOAT_BE = 15
SNDRV_PCM_FORMAT_FLOAT64_LE = 16
SNDRV_PCM_FORMAT_FLOAT64_BE = 17
SNDRV_PCM_FORMAT_MU_LAW = 20
SNDRV_PCM_FORMAT_A_LAW = 21
SNDRV_PCM_FORMAT_MPEG = 23
SNDRV_PCM_IOCTL_PVERSION = 0x80044100
SNDRV_PCM_IOCTL_INFO = 0x81204101
SNDRV_PCM_IOCTL_HW_REFINE = 0xc2604110
SNDRV_PCM_IOCTL_HW_PARAMS = 0xc2604111
SNDRV_PCM_IOCTL_SW_PARAMS = 0xc0884113
SNDRV_PCM_IOCTL_PREPARE = 0x00004140
SNDRV_PCM_IOCTL_WRITEI_FRAMES = 0x40184150
SNDRV_PCM_IOCTL_READI_FRAMES = 0x80184151
)
type snd_pcm_info struct { // size 288
device unsigned_int // offset 0, size 4
subdevice unsigned_int // offset 4, size 4
stream signed_int // offset 8, size 4
card signed_int // offset 12, size 4
id [64]unsigned_char // offset 16, size 64
name [80]unsigned_char // offset 80, size 80
subname [32]unsigned_char // offset 160, size 32
dev_class signed_int // offset 192, size 4
dev_subclass signed_int // offset 196, size 4
subdevices_count unsigned_int // offset 200, size 4
subdevices_avail unsigned_int // offset 204, size 4
pad1 [16]unsigned_char
reserved [64]unsigned_char // offset 224, size 64
}
type snd_pcm_uframes_t = unsigned_long
type snd_pcm_sframes_t = signed_long
type snd_xferi struct { // size 24
result snd_pcm_sframes_t // offset 0, size 8
buf void__user // offset 8, size 8
frames snd_pcm_uframes_t // offset 16, size 8
}
const (
SNDRV_PCM_HW_PARAM_ACCESS = 0
SNDRV_PCM_HW_PARAM_FORMAT = 1
SNDRV_PCM_HW_PARAM_SUBFORMAT = 2
SNDRV_PCM_HW_PARAM_FIRST_MASK = 0
SNDRV_PCM_HW_PARAM_LAST_MASK = 2
SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8
SNDRV_PCM_HW_PARAM_FRAME_BITS = 9
SNDRV_PCM_HW_PARAM_CHANNELS = 10
SNDRV_PCM_HW_PARAM_RATE = 11
SNDRV_PCM_HW_PARAM_PERIOD_TIME = 12
SNDRV_PCM_HW_PARAM_PERIOD_SIZE = 13
SNDRV_PCM_HW_PARAM_PERIOD_BYTES = 14
SNDRV_PCM_HW_PARAM_PERIODS = 15
SNDRV_PCM_HW_PARAM_BUFFER_TIME = 16
SNDRV_PCM_HW_PARAM_BUFFER_SIZE = 17
SNDRV_PCM_HW_PARAM_BUFFER_BYTES = 18
SNDRV_PCM_HW_PARAM_TICK_TIME = 19
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8
SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
SNDRV_MASK_MAX = 256
SNDRV_PCM_TSTAMP_NONE = 0
SNDRV_PCM_TSTAMP_ENABLE = 1
)
type snd_mask struct { // size 32
bits [(SNDRV_MASK_MAX + 31) / 32]__u32 // offset 0, size 32
}
type snd_interval struct { // size 12
min unsigned_int // offset 0, size 4
max unsigned_int // offset 4, size 4
bit unsigned_int
}
type snd_pcm_hw_params struct { // size 608
flags unsigned_int // offset 0, size 4
masks [SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]snd_mask // offset 4, size 96
mres [5]snd_mask // offset 100, size 160
intervals [SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]snd_interval // offset 260, size 144
ires [9]snd_interval // offset 404, size 108
rmask unsigned_int // offset 512, size 4
cmask unsigned_int // offset 516, size 4
info unsigned_int // offset 520, size 4
msbits unsigned_int // offset 524, size 4
rate_num unsigned_int // offset 528, size 4
rate_den unsigned_int // offset 532, size 4
fifo_size snd_pcm_uframes_t // offset 536, size 8
reserved [64]unsigned_char // offset 544, size 64
}
type snd_pcm_sw_params struct { // size 136
tstamp_mode signed_int // offset 0, size 4
period_step unsigned_int // offset 4, size 4
sleep_min unsigned_int // offset 8, size 4
avail_min snd_pcm_uframes_t // offset 16, size 8
xfer_align snd_pcm_uframes_t // offset 24, size 8
start_threshold snd_pcm_uframes_t // offset 32, size 8
stop_threshold snd_pcm_uframes_t // offset 40, size 8
silence_threshold snd_pcm_uframes_t // offset 48, size 8
silence_size snd_pcm_uframes_t // offset 56, size 8
boundary snd_pcm_uframes_t // offset 64, size 8
proto unsigned_int // offset 72, size 4
tstamp_type unsigned_int // offset 76, size 4
reserved [56]unsigned_char // offset 80, size 56
}

View File

@@ -0,0 +1,163 @@
#include <stdio.h>
#include <stddef.h>
#include <sys/ioctl.h>
#include <sound/asound.h>
#define print_line(text) printf("%s\n", text)
#define print_hex_const(name) printf("\t%s = 0x%08lx\n", #name, name)
#define print_int_const(con) printf("\t%s = %d\n", #con, con)
#define print_struct_header(str) printf("type %s struct { // size %lu\n", #str, sizeof(struct str))
#define print_struct_member(str, mem, typ) printf("\t%s %s // offset %lu, size %lu\n", #mem == "type" ? "typ" : #mem, typ, offsetof(struct str, mem), sizeof((struct str){0}.mem))
// https://github.com/torvalds/linux/blob/master/include/uapi/sound/asound.h
int main() {
print_line("package device\n");
print_line("type unsigned_char = byte");
print_line("type signed_int = int32");
print_line("type unsigned_int = uint32");
print_line("type signed_long = int64");
print_line("type unsigned_long = uint64");
print_line("type __u32 = uint32");
print_line("type void__user = uintptr\n");
print_line("const (");
print_int_const(SNDRV_PCM_STREAM_PLAYBACK);
print_int_const(SNDRV_PCM_STREAM_CAPTURE);
print_line("");
print_int_const(SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
print_int_const(SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED);
print_int_const(SNDRV_PCM_ACCESS_MMAP_COMPLEX);
print_int_const(SNDRV_PCM_ACCESS_RW_INTERLEAVED);
print_int_const(SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
print_line("");
print_int_const(SNDRV_PCM_FORMAT_S8);
print_int_const(SNDRV_PCM_FORMAT_U8);
print_int_const(SNDRV_PCM_FORMAT_S16_LE);
print_int_const(SNDRV_PCM_FORMAT_S16_BE);
print_int_const(SNDRV_PCM_FORMAT_U16_LE);
print_int_const(SNDRV_PCM_FORMAT_U16_BE);
print_int_const(SNDRV_PCM_FORMAT_S24_LE);
print_int_const(SNDRV_PCM_FORMAT_S24_BE);
print_int_const(SNDRV_PCM_FORMAT_U24_LE);
print_int_const(SNDRV_PCM_FORMAT_U24_BE);
print_int_const(SNDRV_PCM_FORMAT_S32_LE);
print_int_const(SNDRV_PCM_FORMAT_S32_BE);
print_int_const(SNDRV_PCM_FORMAT_U32_LE);
print_int_const(SNDRV_PCM_FORMAT_U32_BE);
print_int_const(SNDRV_PCM_FORMAT_FLOAT_LE);
print_int_const(SNDRV_PCM_FORMAT_FLOAT_BE);
print_int_const(SNDRV_PCM_FORMAT_FLOAT64_LE);
print_int_const(SNDRV_PCM_FORMAT_FLOAT64_BE);
print_int_const(SNDRV_PCM_FORMAT_MU_LAW);
print_int_const(SNDRV_PCM_FORMAT_A_LAW);
print_int_const(SNDRV_PCM_FORMAT_MPEG);
print_line("");
print_hex_const(SNDRV_PCM_IOCTL_PVERSION); // A 0x00
print_hex_const(SNDRV_PCM_IOCTL_INFO); // A 0x01
print_hex_const(SNDRV_PCM_IOCTL_HW_REFINE); // A 0x10
print_hex_const(SNDRV_PCM_IOCTL_HW_PARAMS); // A 0x11
print_hex_const(SNDRV_PCM_IOCTL_SW_PARAMS); // A 0x13
print_hex_const(SNDRV_PCM_IOCTL_PREPARE); // A 0x40
print_hex_const(SNDRV_PCM_IOCTL_WRITEI_FRAMES); // A 0x50
print_hex_const(SNDRV_PCM_IOCTL_READI_FRAMES); // A 0x51
print_line(")\n");
print_struct_header(snd_pcm_info);
print_struct_member(snd_pcm_info, device, "unsigned_int");
print_struct_member(snd_pcm_info, subdevice, "unsigned_int");
print_struct_member(snd_pcm_info, stream, "signed_int");
print_struct_member(snd_pcm_info, card, "signed_int");
print_struct_member(snd_pcm_info, id, "[64]unsigned_char");
print_struct_member(snd_pcm_info, name, "[80]unsigned_char");
print_struct_member(snd_pcm_info, subname, "[32]unsigned_char");
print_struct_member(snd_pcm_info, dev_class, "signed_int");
print_struct_member(snd_pcm_info, dev_subclass, "signed_int");
print_struct_member(snd_pcm_info, subdevices_count, "unsigned_int");
print_struct_member(snd_pcm_info, subdevices_avail, "unsigned_int");
print_line("\tpad1 [16]unsigned_char");
print_struct_member(snd_pcm_info, reserved, "[64]unsigned_char");
print_line("}\n");
print_line("type snd_pcm_uframes_t = unsigned_long");
print_line("type snd_pcm_sframes_t = signed_long\n");
print_struct_header(snd_xferi);
print_struct_member(snd_xferi, result, "snd_pcm_sframes_t");
print_struct_member(snd_xferi, buf, "void__user");
print_struct_member(snd_xferi, frames, "snd_pcm_uframes_t");
print_line("}\n");
print_line("const (");
print_int_const(SNDRV_PCM_HW_PARAM_ACCESS);
print_int_const(SNDRV_PCM_HW_PARAM_FORMAT);
print_int_const(SNDRV_PCM_HW_PARAM_SUBFORMAT);
print_int_const(SNDRV_PCM_HW_PARAM_FIRST_MASK);
print_int_const(SNDRV_PCM_HW_PARAM_LAST_MASK);
print_line("");
print_int_const(SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
print_int_const(SNDRV_PCM_HW_PARAM_FRAME_BITS);
print_int_const(SNDRV_PCM_HW_PARAM_CHANNELS);
print_int_const(SNDRV_PCM_HW_PARAM_RATE);
print_int_const(SNDRV_PCM_HW_PARAM_PERIOD_TIME);
print_int_const(SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
print_int_const(SNDRV_PCM_HW_PARAM_PERIOD_BYTES);
print_int_const(SNDRV_PCM_HW_PARAM_PERIODS);
print_int_const(SNDRV_PCM_HW_PARAM_BUFFER_TIME);
print_int_const(SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
print_int_const(SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
print_int_const(SNDRV_PCM_HW_PARAM_TICK_TIME);
print_int_const(SNDRV_PCM_HW_PARAM_FIRST_INTERVAL);
print_int_const(SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
print_line("");
print_int_const(SNDRV_MASK_MAX);
print_line("");
print_int_const(SNDRV_PCM_TSTAMP_NONE);
print_int_const(SNDRV_PCM_TSTAMP_ENABLE);
print_line(")\n");
print_struct_header(snd_mask);
print_struct_member(snd_mask, bits, "[(SNDRV_MASK_MAX+31)/32]__u32");
print_line("}\n");
print_struct_header(snd_interval);
print_struct_member(snd_interval, min, "unsigned_int");
print_struct_member(snd_interval, max, "unsigned_int");
print_line("\tbit unsigned_int");
print_line("}\n");
print_struct_header(snd_pcm_hw_params);
print_struct_member(snd_pcm_hw_params, flags, "unsigned_int");
print_struct_member(snd_pcm_hw_params, masks, "[SNDRV_PCM_HW_PARAM_LAST_MASK-SNDRV_PCM_HW_PARAM_FIRST_MASK+1]snd_mask");
print_struct_member(snd_pcm_hw_params, mres, "[5]snd_mask");
print_struct_member(snd_pcm_hw_params, intervals, "[SNDRV_PCM_HW_PARAM_LAST_INTERVAL-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL+1]snd_interval");
print_struct_member(snd_pcm_hw_params, ires, "[9]snd_interval");
print_struct_member(snd_pcm_hw_params, rmask, "unsigned_int");
print_struct_member(snd_pcm_hw_params, cmask, "unsigned_int");
print_struct_member(snd_pcm_hw_params, info, "unsigned_int");
print_struct_member(snd_pcm_hw_params, msbits, "unsigned_int");
print_struct_member(snd_pcm_hw_params, rate_num, "unsigned_int");
print_struct_member(snd_pcm_hw_params, rate_den, "unsigned_int");
print_struct_member(snd_pcm_hw_params, fifo_size, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_hw_params, reserved, "[64]unsigned_char");
print_line("}\n");
print_struct_header(snd_pcm_sw_params);
print_struct_member(snd_pcm_sw_params, tstamp_mode, "signed_int");
print_struct_member(snd_pcm_sw_params, period_step, "unsigned_int");
print_struct_member(snd_pcm_sw_params, sleep_min, "unsigned_int");
print_struct_member(snd_pcm_sw_params, avail_min, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, xfer_align, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, start_threshold, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, stop_threshold, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, silence_threshold, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, silence_size, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, boundary, "snd_pcm_uframes_t");
print_struct_member(snd_pcm_sw_params, proto, "unsigned_int");
print_struct_member(snd_pcm_sw_params, tstamp_type, "unsigned_int");
print_struct_member(snd_pcm_sw_params, reserved, "[56]unsigned_char");
print_line("}\n");
return 0;
}

View File

@@ -0,0 +1,146 @@
package device
type unsigned_char = byte
type signed_int = int32
type unsigned_int = uint32
type signed_long = int64
type unsigned_long = uint64
type __u32 = uint32
type void__user = uintptr
const (
SNDRV_PCM_STREAM_PLAYBACK = 0
SNDRV_PCM_STREAM_CAPTURE = 1
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED = 0
SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED = 1
SNDRV_PCM_ACCESS_MMAP_COMPLEX = 2
SNDRV_PCM_ACCESS_RW_INTERLEAVED = 3
SNDRV_PCM_ACCESS_RW_NONINTERLEAVED = 4
SNDRV_PCM_FORMAT_S8 = 0
SNDRV_PCM_FORMAT_U8 = 1
SNDRV_PCM_FORMAT_S16_LE = 2
SNDRV_PCM_FORMAT_S16_BE = 3
SNDRV_PCM_FORMAT_U16_LE = 4
SNDRV_PCM_FORMAT_U16_BE = 5
SNDRV_PCM_FORMAT_S24_LE = 6
SNDRV_PCM_FORMAT_S24_BE = 7
SNDRV_PCM_FORMAT_U24_LE = 8
SNDRV_PCM_FORMAT_U24_BE = 9
SNDRV_PCM_FORMAT_S32_LE = 10
SNDRV_PCM_FORMAT_S32_BE = 11
SNDRV_PCM_FORMAT_U32_LE = 12
SNDRV_PCM_FORMAT_U32_BE = 13
SNDRV_PCM_FORMAT_FLOAT_LE = 14
SNDRV_PCM_FORMAT_FLOAT_BE = 15
SNDRV_PCM_FORMAT_FLOAT64_LE = 16
SNDRV_PCM_FORMAT_FLOAT64_BE = 17
SNDRV_PCM_FORMAT_MU_LAW = 20
SNDRV_PCM_FORMAT_A_LAW = 21
SNDRV_PCM_FORMAT_MPEG = 23
SNDRV_PCM_IOCTL_PVERSION = 0x40044100
SNDRV_PCM_IOCTL_INFO = 0x41204101
SNDRV_PCM_IOCTL_HW_REFINE = 0xc25c4110
SNDRV_PCM_IOCTL_HW_PARAMS = 0xc25c4111
SNDRV_PCM_IOCTL_SW_PARAMS = 0xc0684113
SNDRV_PCM_IOCTL_PREPARE = 0x20004140
SNDRV_PCM_IOCTL_WRITEI_FRAMES = 0x800c4150
SNDRV_PCM_IOCTL_READI_FRAMES = 0x400c4151
)
type snd_pcm_info struct { // size 288
device unsigned_int // offset 0, size 4
subdevice unsigned_int // offset 4, size 4
stream signed_int // offset 8, size 4
card signed_int // offset 12, size 4
id [64]unsigned_char // offset 16, size 64
name [80]unsigned_char // offset 80, size 80
subname [32]unsigned_char // offset 160, size 32
dev_class signed_int // offset 192, size 4
dev_subclass signed_int // offset 196, size 4
subdevices_count unsigned_int // offset 200, size 4
subdevices_avail unsigned_int // offset 204, size 4
pad1 [16]unsigned_char
reserved [64]unsigned_char // offset 224, size 64
}
type snd_pcm_uframes_t = unsigned_long
type snd_pcm_sframes_t = signed_long
type snd_xferi struct { // size 12
result snd_pcm_sframes_t // offset 0, size 4
buf void__user // offset 4, size 4
frames snd_pcm_uframes_t // offset 8, size 4
}
const (
SNDRV_PCM_HW_PARAM_ACCESS = 0
SNDRV_PCM_HW_PARAM_FORMAT = 1
SNDRV_PCM_HW_PARAM_SUBFORMAT = 2
SNDRV_PCM_HW_PARAM_FIRST_MASK = 0
SNDRV_PCM_HW_PARAM_LAST_MASK = 2
SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8
SNDRV_PCM_HW_PARAM_FRAME_BITS = 9
SNDRV_PCM_HW_PARAM_CHANNELS = 10
SNDRV_PCM_HW_PARAM_RATE = 11
SNDRV_PCM_HW_PARAM_PERIOD_TIME = 12
SNDRV_PCM_HW_PARAM_PERIOD_SIZE = 13
SNDRV_PCM_HW_PARAM_PERIOD_BYTES = 14
SNDRV_PCM_HW_PARAM_PERIODS = 15
SNDRV_PCM_HW_PARAM_BUFFER_TIME = 16
SNDRV_PCM_HW_PARAM_BUFFER_SIZE = 17
SNDRV_PCM_HW_PARAM_BUFFER_BYTES = 18
SNDRV_PCM_HW_PARAM_TICK_TIME = 19
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8
SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
SNDRV_MASK_MAX = 256
SNDRV_PCM_TSTAMP_NONE = 0
SNDRV_PCM_TSTAMP_ENABLE = 1
)
type snd_mask struct { // size 32
bits [(SNDRV_MASK_MAX + 31) / 32]__u32 // offset 0, size 32
}
type snd_interval struct { // size 12
min unsigned_int // offset 0, size 4
max unsigned_int // offset 4, size 4
bit unsigned_int
}
type snd_pcm_hw_params struct { // size 604
flags unsigned_int // offset 0, size 4
masks [SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]snd_mask // offset 4, size 96
mres [5]snd_mask // offset 100, size 160
intervals [SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]snd_interval // offset 260, size 144
ires [9]snd_interval // offset 404, size 108
rmask unsigned_int // offset 512, size 4
cmask unsigned_int // offset 516, size 4
info unsigned_int // offset 520, size 4
msbits unsigned_int // offset 524, size 4
rate_num unsigned_int // offset 528, size 4
rate_den unsigned_int // offset 532, size 4
fifo_size snd_pcm_uframes_t // offset 536, size 4
reserved [64]unsigned_char // offset 540, size 64
}
type snd_pcm_sw_params struct { // size 104
tstamp_mode signed_int // offset 0, size 4
period_step unsigned_int // offset 4, size 4
sleep_min unsigned_int // offset 8, size 4
avail_min snd_pcm_uframes_t // offset 12, size 4
xfer_align snd_pcm_uframes_t // offset 16, size 4
start_threshold snd_pcm_uframes_t // offset 20, size 4
stop_threshold snd_pcm_uframes_t // offset 24, size 4
silence_threshold snd_pcm_uframes_t // offset 28, size 4
silence_size snd_pcm_uframes_t // offset 32, size 4
boundary snd_pcm_uframes_t // offset 36, size 4
proto unsigned_int // offset 40, size 4
tstamp_type unsigned_int // offset 44, size 4
reserved [56]unsigned_char // offset 48, size 56
}

View File

@@ -0,0 +1,205 @@
package device
import (
"fmt"
"syscall"
"unsafe"
)
type Device struct {
fd uintptr
path string
hwparams snd_pcm_hw_params
frameBytes int // sample size * channels
}
func Open(path string) (*Device, error) {
// important to use nonblock because can get lock
fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, err
}
// important to remove nonblock because better to handle reads and writes
if err = syscall.SetNonblock(fd, false); err != nil {
return nil, err
}
d := &Device{fd: uintptr(fd), path: path}
d.init()
// load all supported formats, channels, rates, etc.
if err = ioctl(d.fd, SNDRV_PCM_IOCTL_HW_REFINE, &d.hwparams); err != nil {
_ = d.Close()
return nil, err
}
d.setMask(SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED)
return d, nil
}
func (d *Device) Close() error {
return syscall.Close(int(d.fd))
}
func (d *Device) IsCapture() bool {
// path: /dev/snd/pcmC0D0c, where p - playback, c - capture
return d.path[len(d.path)-1] == 'c'
}
type Info struct {
Card int
Device int
SubDevice int
Stream int
ID string
Name string
SubName string
}
func (d *Device) Info() (*Info, error) {
var info snd_pcm_info
if err := ioctl(d.fd, SNDRV_PCM_IOCTL_INFO, &info); err != nil {
return nil, err
}
return &Info{
Card: int(info.card),
Device: int(info.device),
SubDevice: int(info.subdevice),
Stream: int(info.stream),
ID: str(info.id[:]),
Name: str(info.name[:]),
SubName: str(info.subname[:]),
}, nil
}
func (d *Device) ListFormats() (formats []byte) {
for i := byte(0); i <= 28; i++ {
if d.checkMask(SNDRV_PCM_HW_PARAM_FORMAT, uint32(i)) {
formats = append(formats, i)
}
}
return
}
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)
}
const bufferSize = 4096
func (d *Device) SetHWParams(format byte, sampleRate uint32, channels byte) error {
d.setInterval(SNDRV_PCM_HW_PARAM_CHANNELS, uint32(channels))
d.setInterval(SNDRV_PCM_HW_PARAM_RATE, sampleRate)
d.setMask(SNDRV_PCM_HW_PARAM_FORMAT, uint32(format))
//d.setMask(SNDRV_PCM_HW_PARAM_SUBFORMAT, 0)
// important for smooth playback
d.setInterval(SNDRV_PCM_HW_PARAM_BUFFER_SIZE, bufferSize)
//d.setInterval(SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2000)
if err := ioctl(d.fd, SNDRV_PCM_IOCTL_HW_PARAMS, &d.hwparams); err != nil {
return fmt.Errorf("[alsa] set hw_params: %w", err)
}
_, i := d.getInterval(SNDRV_PCM_HW_PARAM_FRAME_BITS)
d.frameBytes = int(i / 8)
_, periods := d.getInterval(SNDRV_PCM_HW_PARAM_PERIODS)
_, periodSize := d.getInterval(SNDRV_PCM_HW_PARAM_PERIOD_SIZE)
threshold := snd_pcm_uframes_t(periods * periodSize) // same as bufferSize
swparams := snd_pcm_sw_params{
//tstamp_mode: SNDRV_PCM_TSTAMP_ENABLE,
period_step: 1,
avail_min: 1, // start as soon as possible
stop_threshold: threshold,
}
if d.IsCapture() {
swparams.start_threshold = 1
} else {
swparams.start_threshold = threshold
}
if err := ioctl(d.fd, SNDRV_PCM_IOCTL_SW_PARAMS, &swparams); err != nil {
return fmt.Errorf("[alsa] set sw_params: %w", err)
}
if err := ioctl(d.fd, SNDRV_PCM_IOCTL_PREPARE, nil); err != nil {
return fmt.Errorf("[alsa] prepare: %w", err)
}
return nil
}
func (d *Device) Write(b []byte) (n int, err error) {
xfer := &snd_xferi{
buf: uintptr(unsafe.Pointer(&b[0])),
frames: snd_pcm_uframes_t(len(b) / d.frameBytes),
}
err = ioctl(d.fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, xfer)
if err == syscall.EPIPE {
// auto handle underrun state
// https://stackoverflow.com/questions/59396728/how-to-properly-handle-xrun-in-alsa-programming-when-playing-audio-with-snd-pcm
err = ioctl(d.fd, SNDRV_PCM_IOCTL_PREPARE, nil)
}
n = int(xfer.result) * d.frameBytes
return
}
func (d *Device) Read(b []byte) (n int, err error) {
xfer := &snd_xferi{
buf: uintptr(unsafe.Pointer(&b[0])),
frames: snd_pcm_uframes_t(len(b) / d.frameBytes),
}
err = ioctl(d.fd, SNDRV_PCM_IOCTL_READI_FRAMES, xfer)
n = int(xfer.result) * d.frameBytes
return
}
func (d *Device) init() {
for i := range d.hwparams.masks {
d.hwparams.masks[i].bits[0] = 0xFFFFFFFF
d.hwparams.masks[i].bits[1] = 0xFFFFFFFF
}
for i := range d.hwparams.intervals {
d.hwparams.intervals[i].max = 0xFFFFFFFF
}
d.hwparams.rmask = 0xFFFFFFFF
d.hwparams.cmask = 0
d.hwparams.info = 0xFFFFFFFF
}
func (d *Device) setInterval(param, val uint32) {
d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min = val
d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].max = val
d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].bit = 0b0100 // integer
}
func (d *Device) setIntervalMin(param, val uint32) {
d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min = val
}
func (d *Device) getInterval(param uint32) (uint32, uint32) {
return d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].min,
d.hwparams.intervals[param-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL].max
}
func (d *Device) setMask(mask, val uint32) {
d.hwparams.masks[mask].bits[0] = 0
d.hwparams.masks[mask].bits[1] = 0
d.hwparams.masks[mask].bits[val>>5] = 1 << (val & 0x1F)
}
func (d *Device) checkMask(mask, val uint32) bool {
return d.hwparams.masks[mask].bits[val>>5]&(1<<(val&0x1F)) > 0
}

View File

@@ -0,0 +1,26 @@
package device
import (
"bytes"
"reflect"
"syscall"
)
func ioctl(fd, req uintptr, arg any) error {
var ptr uintptr
if arg != nil {
ptr = reflect.ValueOf(arg).Pointer()
}
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, req, ptr)
if err != 0 {
return err
}
return nil
}
func str(b []byte) string {
if i := bytes.IndexByte(b, 0); i >= 0 {
return string(b[:i])
}
return string(b)
}

39
pkg/alsa/open_linux.go Normal file
View File

@@ -0,0 +1,39 @@
package alsa
import (
"fmt"
"net/url"
"github.com/AlexxIT/go2rtc/pkg/alsa/device"
"github.com/AlexxIT/go2rtc/pkg/core"
)
func Open(rawURL string) (core.Producer, error) {
// Example (ffmpeg source compatible):
// alsa:device?audio=/dev/snd/pcmC0D0p
// TODO: ?audio=default
// TODO: ?audio=hw:0,0
// TODO: &sample_rate=48000&channels=2
// TODO: &backchannel=1
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
path := u.Query().Get("audio")
dev, err := device.Open(path)
if err != nil {
return nil, err
}
switch path[len(path)-1] {
case 'p': // playback
return newPlayback(dev)
case 'c': // capture
return newCapture(dev)
}
_ = dev.Close()
return nil, fmt.Errorf("alsa: unknown path: %s", path)
}

View File

@@ -0,0 +1,94 @@
package alsa
import (
"fmt"
"github.com/AlexxIT/go2rtc/pkg/alsa/device"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/pcm"
"github.com/pion/rtp"
)
type Playback struct {
core.Connection
dev *device.Device
closed core.Waiter
}
func newPlayback(dev *device.Device) (*Playback, error) {
medias := []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionSendonly,
Codecs: []*core.Codec{
{Name: core.CodecPCML}, // support ffmpeg producer (auto transcode)
{Name: core.CodecPCMA, ClockRate: 8000}, // support webrtc producer
},
},
}
return &Playback{
Connection: core.Connection{
ID: core.NewID(),
FormatName: "alsa",
Medias: medias,
Transport: dev,
},
dev: dev,
}, nil
}
func (p *Playback) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
return nil, core.ErrCantGetTrack
}
func (p *Playback) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
in := track.Codec
// support probe
if in.Name == core.CodecAny {
in = &core.Codec{
Name: core.CodecPCML,
ClockRate: 16000,
Channels: 2,
}
}
out := &core.Codec{
Name: core.CodecPCML,
ClockRate: in.ClockRate,
Channels: 2,
}
sender := core.NewSender(media, out)
sender.Handler = func(pkt *rtp.Packet) {
if n, err := p.dev.Write(pkt.Payload); err == nil {
p.Send += n
}
}
if sender.Handler = pcm.Convert(in, out, sender.Handler); sender.Handler == nil {
return fmt.Errorf("alsa: can't convert %s to %s", in, out)
}
// typical card support:
// - Formats: S16_LE, S32_LE
// - ClockRates: 8000 - 192000
// - Channels: 2 - 10
err := p.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, out.ClockRate, 2)
if err != nil {
return err
}
sender.HandleRTP(track)
p.Senders = append(p.Senders, sender)
return nil
}
func (p *Playback) Start() (err error) {
return p.closed.Wait()
}
func (p *Playback) Stop() error {
p.closed.Done(nil)
return p.Connection.Stop()
}

View File

@@ -198,3 +198,80 @@ func RepackG711(zeroTS bool, handler core.HandlerFunc) core.HandlerFunc {
handler(pkt)
}
}
func Convert(in, out *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
if in.Name == out.Name && in.Channels == out.Channels && in.ClockRate == out.ClockRate {
return handler
}
switch {
case in.Name == core.CodecPCML && in.Channels <= 1 &&
out.Name == core.CodecPCML && out.Channels == 2:
return func(pkt *core.Packet) {
n := len(pkt.Payload)
payload := make([]byte, 2*n)
for i, j := 0, 0; i < n; {
hi := pkt.Payload[i]
i++
lo := pkt.Payload[i]
i++
payload[j] = hi
j++
payload[j] = lo
j++
payload[j] = hi
j++
payload[j] = lo
j++
}
pkt.Payload = payload
handler(pkt)
}
case in.Name == core.CodecPCM && in.Channels <= 1 &&
out.Name == core.CodecPCML && out.Channels == 2:
return func(pkt *core.Packet) {
n := len(pkt.Payload)
payload := make([]byte, 2*n)
for i, j := 0, 0; i < n; {
hi := pkt.Payload[i]
i++
lo := pkt.Payload[i]
i++
payload[j] = lo
j++
payload[j] = hi
j++
payload[j] = lo
j++
payload[j] = hi
j++
}
pkt.Payload = payload
handler(pkt)
}
case in.Name == core.CodecPCMA && in.Channels <= 1 &&
out.Name == core.CodecPCML && out.Channels == 2:
return func(pkt *core.Packet) {
payload := make([]byte, 4*len(pkt.Payload))
var i int
for _, b := range pkt.Payload {
s16 := PCMAtoPCM(b)
hi := byte(s16 >> 8)
lo := byte(s16)
payload[i] = hi
i++
payload[i] = lo
i++
payload[i] = hi
i++
payload[i] = lo
i++
}
pkt.Payload = payload
handler(pkt)
}
}
return nil
}

View File

@@ -84,6 +84,18 @@
</script>
<button id="alsa">ALSA (Linux audio)</button>
<div class="module">
<table id="alsa-table"></table>
</div>
<script>
document.getElementById('alsa').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block';
await getSources('alsa-table', 'api/alsa');
});
</script>
<button id="homekit">Apple HomeKit</button>
<div class="module">
<form id="homekit-pair" style="margin-bottom: 10px">
@@ -341,7 +353,7 @@
</script>
<button id="v4l2">V4L2 (USB)</button>
<button id="v4l2">V4L2 (Linux video)</button>
<div class="module">
<table id="v4l2-table"></table>
</div>