Merge branch 'AlexxIT:master' into check-h265
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !(linux && (386 || amd64 || arm || arm64 || mipsle))
|
||||||
|
|
||||||
|
package alsa
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
// not supported
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//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.RangeRates()
|
||||||
|
c1, c2 := dev.RangeChannels()
|
||||||
|
source := &api.Source{
|
||||||
|
Name: info.ID,
|
||||||
|
Info: fmt.Sprintf("Formats: %s, Rates: %d-%d, Channels: %d-%d", formats, r1, r2, c1, c2),
|
||||||
|
URL: "alsa:device?audio=" + path,
|
||||||
|
}
|
||||||
|
if !strings.Contains(source.Name, info.Name) {
|
||||||
|
source.Name += ", " + info.Name
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -113,6 +113,7 @@ var defaults = map[string]string{
|
|||||||
"pcm/48000": "-c:a pcm_s16be -ar:a 48000 -ac:a 1",
|
"pcm/48000": "-c:a pcm_s16be -ar:a 48000 -ac:a 1",
|
||||||
"pcml": "-c:a pcm_s16le -ar:a 8000 -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/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",
|
"pcml/44100": "-c:a pcm_s16le -ar:a 44100 -ac:a 1",
|
||||||
|
|
||||||
// hardware Intel and AMD on Linux
|
// hardware Intel and AMD on Linux
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func NewProducer(url string) (core.Producer, error) {
|
|||||||
Codecs: []*core.Codec{
|
Codecs: []*core.Codec{
|
||||||
// OPUS will always marked as OPUS/48000/2
|
// OPUS will always marked as OPUS/48000/2
|
||||||
{Name: core.CodecOpus, ClockRate: 48000, Channels: 2},
|
{Name: core.CodecOpus, ClockRate: 48000, Channels: 2},
|
||||||
|
{Name: core.CodecPCML, ClockRate: 16000},
|
||||||
{Name: core.CodecPCM, ClockRate: 16000},
|
{Name: core.CodecPCM, ClockRate: 16000},
|
||||||
{Name: core.CodecPCMA, ClockRate: 16000},
|
{Name: core.CodecPCMA, ClockRate: 16000},
|
||||||
{Name: core.CodecPCMU, ClockRate: 16000},
|
{Name: core.CodecPCMU, ClockRate: 16000},
|
||||||
@@ -97,6 +98,8 @@ func (p *Producer) newURL() string {
|
|||||||
s += "#audio=opus"
|
s += "#audio=opus"
|
||||||
case core.CodecAAC:
|
case core.CodecAAC:
|
||||||
s += "#audio=aac/16000"
|
s += "#audio=aac/16000"
|
||||||
|
case core.CodecPCML:
|
||||||
|
s += "#audio=pcml/16000"
|
||||||
case core.CodecPCM:
|
case core.CodecPCM:
|
||||||
s += "#audio=pcm/" + strconv.Itoa(int(codec.ClockRate))
|
s += "#audio=pcm/" + strconv.Itoa(int(codec.ClockRate))
|
||||||
case core.CodecPCMA:
|
case core.CodecPCMA:
|
||||||
|
|||||||
@@ -140,10 +140,12 @@ func matchMedia(prod core.Producer, cons core.Consumer) bool {
|
|||||||
|
|
||||||
track, err := prod.GetTrack(prodMedia, prodCodec)
|
track, err := prod.GetTrack(prodMedia, prodCodec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("[streams] can't get track")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cons.AddTrack(consMedia, consCodec, track); err != nil {
|
if err = cons.AddTrack(consMedia, consCodec, track); err != nil {
|
||||||
|
log.Warn().Err(err).Msg("[streams] can't add track")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/alsa"
|
||||||
"github.com/AlexxIT/go2rtc/internal/api"
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
||||||
"github.com/AlexxIT/go2rtc/internal/app"
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
@@ -90,6 +91,7 @@ func main() {
|
|||||||
gopro.Init() // gopro source
|
gopro.Init() // gopro source
|
||||||
doorbird.Init() // doorbird source
|
doorbird.Init() // doorbird source
|
||||||
v4l2.Init() // v4l2 source
|
v4l2.Init() // v4l2 source
|
||||||
|
alsa.Init() // alsa source
|
||||||
flussonic.Init()
|
flussonic.Init()
|
||||||
eseecloud.Init()
|
eseecloud.Init()
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
| Format | Source protocols | Ingress protocols | Recevers codecs | Senders codecs | Example |
|
||||||
|--------------|------------------|-------------------|------------------------------|--------------------|---------------|
|
|--------------|------------------|-------------------|------------------------------|--------------------|---------------|
|
||||||
| adts | http,tcp,pipe | http | aac | | `http:` |
|
| adts | http,tcp,pipe | http | aac | | `http:` |
|
||||||
|
| alsa | pipe | | | pcm | `alsa:` |
|
||||||
| bubble | http | | h264,hevc,pcm_alaw | | `bubble:` |
|
| bubble | http | | h264,hevc,pcm_alaw | | `bubble:` |
|
||||||
| dvrip | tcp | | h264,hevc,pcm_alaw,pcm_mulaw | pcm_alaw | `dvrip:` |
|
| dvrip | tcp | | h264,hevc,pcm_alaw,pcm_mulaw | pcm_alaw | `dvrip:` |
|
||||||
| flv | http,tcp,pipe | http | h264,aac | | `http:` |
|
| flv | http,tcp,pipe | http | h264,aac | | `http:` |
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ func ConfigToCodec(conf []byte) *core.Codec {
|
|||||||
codec.ClockRate = rd.ReadBits(24)
|
codec.ClockRate = rd.ReadBits(24)
|
||||||
}
|
}
|
||||||
|
|
||||||
codec.Channels = rd.ReadBits16(4)
|
codec.Channels = rd.ReadBits8(4)
|
||||||
|
|
||||||
return codec
|
return codec
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ func ADTSToCodec(b []byte) *core.Codec {
|
|||||||
objType := rd.ReadBits8(2) + 1 // Profile, the MPEG-4 Audio Object Type minus 1
|
objType := rd.ReadBits8(2) + 1 // Profile, the MPEG-4 Audio Object Type minus 1
|
||||||
sampleRateIdx := rd.ReadBits8(4) // MPEG-4 Sampling Frequency Index
|
sampleRateIdx := rd.ReadBits8(4) // MPEG-4 Sampling Frequency Index
|
||||||
_ = rd.ReadBit() // Private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
|
_ = rd.ReadBit() // Private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
|
||||||
channels := rd.ReadBits16(3) // MPEG-4 Channel Configuration
|
channels := rd.ReadBits8(3) // MPEG-4 Channel Configuration
|
||||||
|
|
||||||
//_ = rd.ReadBit() // Originality, set to 1 to signal originality of the audio and 0 otherwise
|
//_ = rd.ReadBit() // Originality, set to 1 to signal originality of the audio and 0 otherwise
|
||||||
//_ = rd.ReadBit() // Home, set to 1 to signal home usage of the audio and 0 otherwise
|
//_ = rd.ReadBit() // Home, set to 1 to signal home usage of the audio and 0 otherwise
|
||||||
@@ -43,7 +43,7 @@ func ADTSToCodec(b []byte) *core.Codec {
|
|||||||
wr := bits.NewWriter(nil)
|
wr := bits.NewWriter(nil)
|
||||||
wr.WriteBits8(objType, 5)
|
wr.WriteBits8(objType, 5)
|
||||||
wr.WriteBits8(sampleRateIdx, 4)
|
wr.WriteBits8(sampleRateIdx, 4)
|
||||||
wr.WriteBits16(channels, 4)
|
wr.WriteBits8(channels, 4)
|
||||||
conf := wr.Bytes()
|
conf := wr.Bytes()
|
||||||
|
|
||||||
codec := &core.Codec{
|
codec := &core.Codec{
|
||||||
|
|||||||
@@ -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/
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Capture) Start() error {
|
||||||
|
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)
|
||||||
|
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: transcode(b[:n]),
|
||||||
|
}
|
||||||
|
c.Receivers[0].WriteRTP(pkt)
|
||||||
|
|
||||||
|
ts += uint32(n / frameBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 pcm.Transcode(dst, src)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
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) 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.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) 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, rate uint32, channels byte) error {
|
||||||
|
d.setInterval(SNDRV_PCM_HW_PARAM_CHANNELS, uint32(channels))
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package alsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
case 'c': // capture
|
||||||
|
return newCapture(dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = dev.Close()
|
||||||
|
return nil, fmt.Errorf("alsa: unknown path: %s", path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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 {
|
||||||
|
src := track.Codec
|
||||||
|
dst := &core.Codec{
|
||||||
|
Name: core.CodecPCML,
|
||||||
|
ClockRate: p.dev.GetRateNear(src.ClockRate),
|
||||||
|
Channels: p.dev.GetChannelsNear(src.Channels),
|
||||||
|
}
|
||||||
|
sender := core.NewSender(media, dst)
|
||||||
|
|
||||||
|
sender.Handler = func(pkt *rtp.Packet) {
|
||||||
|
if n, err := p.dev.Write(pkt.Payload); err == nil {
|
||||||
|
p.Send += n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender.Handler = pcm.TranscodeHandler(dst, src, sender.Handler); sender.Handler == nil {
|
||||||
|
return fmt.Errorf("alsa: can't convert %s to %s", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typical card support:
|
||||||
|
// - Formats: S16_LE, S32_LE
|
||||||
|
// - ClockRates: 8000 - 192000
|
||||||
|
// - Channels: 2 - 10
|
||||||
|
err := p.dev.SetHWParams(device.SNDRV_PCM_FORMAT_S16_LE, dst.ClockRate, byte(dst.Channels))
|
||||||
|
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()
|
||||||
|
}
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
type Codec struct {
|
type Codec struct {
|
||||||
Name string // H264, PCMU, PCMA, opus...
|
Name string // H264, PCMU, PCMA, opus...
|
||||||
ClockRate uint32 // 90000, 8000, 16000...
|
ClockRate uint32 // 90000, 8000, 16000...
|
||||||
Channels uint16 // 0, 1, 2
|
Channels uint8 // 0, 1, 2
|
||||||
FmtpLine string
|
FmtpLine string
|
||||||
PayloadType uint8
|
PayloadType uint8
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -139,7 +139,7 @@ func MarshalSDP(name string, medias []*Media) ([]byte, error) {
|
|||||||
Protos: []string{"RTP", "AVP"},
|
Protos: []string{"RTP", "AVP"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
md.WithCodec(codec.PayloadType, name, codec.ClockRate, codec.Channels, codec.FmtpLine)
|
md.WithCodec(codec.PayloadType, name, codec.ClockRate, uint16(codec.Channels), codec.FmtpLine)
|
||||||
|
|
||||||
if media.Direction != "" {
|
if media.Direction != "" {
|
||||||
md.WithPropertyAttribute(media.Direction)
|
md.WithPropertyAttribute(media.Direction)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func audioToMedia(codecs []camera.AudioCodec) *core.Media {
|
|||||||
mediaCodec := &core.Codec{
|
mediaCodec := &core.Codec{
|
||||||
Name: audioCodecs[codec.CodecType],
|
Name: audioCodecs[codec.CodecType],
|
||||||
ClockRate: audioSampleRates[sampleRate],
|
ClockRate: audioSampleRates[sampleRate],
|
||||||
Channels: uint16(param.Channels),
|
Channels: param.Channels,
|
||||||
}
|
}
|
||||||
|
|
||||||
if mediaCodec.Name == core.CodecELD {
|
if mediaCodec.Name == core.CodecELD {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# IOCTL
|
||||||
|
|
||||||
|
This is just an example how Linux IOCTL constants works.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ioctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Str(b []byte) string {
|
||||||
|
if i := bytes.IndexByte(b, 0); i >= 0 {
|
||||||
|
return string(b[:i])
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func io(mode byte, type_ byte, number byte, size uint16) uintptr {
|
||||||
|
return uintptr(mode)<<30 | uintptr(size)<<16 | uintptr(type_)<<8 | uintptr(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IOR(type_ byte, number byte, size uint16) uintptr {
|
||||||
|
return io(read, type_, number, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IOW(type_ byte, number byte, size uint16) uintptr {
|
||||||
|
return io(write, type_, number, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IORW(type_ byte, number byte, size uint16) uintptr {
|
||||||
|
return io(read|write, type_, number, size)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build arm || arm64 || 386 || amd64
|
||||||
|
|
||||||
|
package ioctl
|
||||||
|
|
||||||
|
const (
|
||||||
|
write = 1
|
||||||
|
read = 2
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build mipsle
|
||||||
|
|
||||||
|
package ioctl
|
||||||
|
|
||||||
|
const (
|
||||||
|
read = 1
|
||||||
|
write = 2
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ioctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Ioctl(fd int, req uint, arg unsafe.Pointer) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
||||||
|
if err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ioctl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIOR(t *testing.T) {
|
||||||
|
// #define SNDRV_PCM_IOCTL_INFO _IOR('A', 0x01, struct snd_pcm_info)
|
||||||
|
if runtime.GOARCH == "arm64" {
|
||||||
|
c := IOR('A', 0x01, 288)
|
||||||
|
require.Equal(t, uintptr(0x81204101), c)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -89,12 +89,12 @@ func (m *Muxer) GetInit() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mv.WriteAudioTrack(
|
mv.WriteAudioTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate, codec.Channels, b,
|
uint32(i+1), codec.Name, codec.ClockRate, uint16(codec.Channels), b,
|
||||||
)
|
)
|
||||||
|
|
||||||
case core.CodecOpus, core.CodecMP3, core.CodecPCMA, core.CodecPCMU, core.CodecFLAC:
|
case core.CodecOpus, core.CodecMP3, core.CodecPCMA, core.CodecPCMU, core.CodecFLAC:
|
||||||
mv.WriteAudioTrack(
|
mv.WriteAudioTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate, codec.Channels, nil,
|
uint32(i+1), codec.Name, codec.ClockRate, uint16(codec.Channels), nil,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package pcm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepackG711 - Repack G.711 PCMA/PCMU into frames of size 1024
|
||||||
|
// 1. Fixes WebRTC audio quality issue (monotonic timestamp)
|
||||||
|
// 2. Fixes Reolink Doorbell backchannel issue (zero timestamp)
|
||||||
|
// https://github.com/AlexxIT/go2rtc/issues/331
|
||||||
|
func RepackG711(zeroTS bool, handler core.HandlerFunc) core.HandlerFunc {
|
||||||
|
const PacketSize = 1024
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
var seq uint16
|
||||||
|
var ts uint32
|
||||||
|
|
||||||
|
// fix https://github.com/AlexxIT/go2rtc/issues/432
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
return func(packet *rtp.Packet) {
|
||||||
|
mu.Lock()
|
||||||
|
|
||||||
|
buf = append(buf, packet.Payload...)
|
||||||
|
if len(buf) < PacketSize {
|
||||||
|
mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := &rtp.Packet{
|
||||||
|
Header: rtp.Header{
|
||||||
|
Version: 2,
|
||||||
|
Marker: true, // should be true
|
||||||
|
PayloadType: packet.PayloadType, // will be owerwriten
|
||||||
|
SequenceNumber: seq,
|
||||||
|
SSRC: packet.SSRC,
|
||||||
|
},
|
||||||
|
Payload: buf[:PacketSize],
|
||||||
|
}
|
||||||
|
|
||||||
|
seq++
|
||||||
|
|
||||||
|
// don't know if zero TS important for Reolink Doorbell
|
||||||
|
// don't have this strange devices for tests
|
||||||
|
if !zeroTS {
|
||||||
|
pkt.Timestamp = ts
|
||||||
|
ts += PacketSize
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = buf[PacketSize:]
|
||||||
|
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
handler(pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LittleToBig - convert PCM little endian to PCM big endian
|
||||||
|
func LittleToBig(handler core.HandlerFunc) core.HandlerFunc {
|
||||||
|
return func(packet *rtp.Packet) {
|
||||||
|
clone := *packet
|
||||||
|
clone.Payload = FlipEndian(packet.Payload)
|
||||||
|
handler(&clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TranscodeHandler(dst, src *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||||
|
var ts uint32
|
||||||
|
k := float32(BytesPerFrame(dst)) / float32(BytesPerFrame(src))
|
||||||
|
f := Transcode(dst, src)
|
||||||
|
|
||||||
|
return func(packet *rtp.Packet) {
|
||||||
|
ts += uint32(k * float32(len(packet.Payload)))
|
||||||
|
|
||||||
|
clone := *packet
|
||||||
|
clone.Payload = f(packet.Payload)
|
||||||
|
clone.Timestamp = ts
|
||||||
|
handler(&clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BytesPerFrame(codec *core.Codec) byte {
|
||||||
|
channels := byte(codec.Channels)
|
||||||
|
if channels == 0 {
|
||||||
|
channels = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
switch codec.Name {
|
||||||
|
case core.CodecPCML, core.CodecPCM:
|
||||||
|
return 2 * channels
|
||||||
|
case core.CodecPCMU, core.CodecPCMA:
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
+155
-168
@@ -1,200 +1,187 @@
|
|||||||
package pcm
|
package pcm
|
||||||
|
|
||||||
import (
|
import "github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
func Downsample(k float32) func([]int16) []int16 {
|
||||||
"github.com/pion/rtp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResampleToG711 - convert PCMA/PCM/PCML to PCMA and PCMU to PCMU with decreasing sample rate
|
|
||||||
func ResampleToG711(codec *core.Codec, sampleRate uint32, handler core.HandlerFunc) core.HandlerFunc {
|
|
||||||
n := float32(codec.ClockRate) / float32(sampleRate)
|
|
||||||
|
|
||||||
if codec.Channels == 2 {
|
|
||||||
n *= 2 // hacky way for support two channels audio
|
|
||||||
}
|
|
||||||
|
|
||||||
switch codec.Name {
|
|
||||||
case core.CodecPCMA:
|
|
||||||
return DownsampleByte(PCMAtoPCM, PCMtoPCMA, n, handler)
|
|
||||||
case core.CodecPCMU:
|
|
||||||
return DownsampleByte(PCMUtoPCM, PCMtoPCMU, n, handler)
|
|
||||||
case core.CodecPCM, core.CodecPCML:
|
|
||||||
if n == 1 {
|
|
||||||
handler = ResamplePCM(PCMtoPCMA, handler)
|
|
||||||
} else {
|
|
||||||
handler = DownsamplePCM(PCMtoPCMA, n, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if codec.Name == core.CodecPCML {
|
|
||||||
return LittleToBig(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(core.Caller())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownsampleByte - convert PCMA/PCMU to PCMA/PCMU with decreasing sample rate (N times)
|
|
||||||
func DownsampleByte(
|
|
||||||
toPCM func(byte) int16, fromPCM func(int16) byte, n float32, handler core.HandlerFunc,
|
|
||||||
) core.HandlerFunc {
|
|
||||||
var sampleN, sampleSum float32
|
var sampleN, sampleSum float32
|
||||||
var ts uint32
|
|
||||||
|
|
||||||
return func(packet *rtp.Packet) {
|
|
||||||
samples := len(packet.Payload)
|
|
||||||
newLen := uint32((float32(samples) + sampleN) / n)
|
|
||||||
|
|
||||||
oldSamples := packet.Payload
|
|
||||||
newSamples := make([]byte, newLen)
|
|
||||||
|
|
||||||
|
return func(src []int16) (dst []int16) {
|
||||||
var i int
|
var i int
|
||||||
for _, sample := range oldSamples {
|
dst = make([]int16, int((float32(len(src))+sampleN)/k))
|
||||||
sampleSum += float32(toPCM(sample))
|
for _, sample := range src {
|
||||||
if sampleN++; sampleN >= n {
|
sampleSum += float32(sample)
|
||||||
newSamples[i] = fromPCM(int16(sampleSum / n))
|
sampleN++
|
||||||
|
if sampleN >= k {
|
||||||
|
dst[i] = int16(sampleSum / k)
|
||||||
i++
|
i++
|
||||||
|
|
||||||
sampleSum = 0
|
sampleSum = 0
|
||||||
sampleN -= n
|
sampleN -= k
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
ts += newLen
|
|
||||||
|
|
||||||
clone := *packet
|
|
||||||
clone.Payload = newSamples
|
|
||||||
clone.Timestamp = ts
|
|
||||||
handler(&clone)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LittleToBig - conver PCM little endian to PCM big endian
|
func Upsample(k float32) func([]int16) []int16 {
|
||||||
func LittleToBig(handler core.HandlerFunc) core.HandlerFunc {
|
var sampleN float32
|
||||||
return func(packet *rtp.Packet) {
|
|
||||||
size := len(packet.Payload)
|
|
||||||
b := make([]byte, size)
|
|
||||||
for i := 0; i < size; i += 2 {
|
|
||||||
b[i] = packet.Payload[i+1]
|
|
||||||
b[i+1] = packet.Payload[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
clone := *packet
|
return func(src []int16) (dst []int16) {
|
||||||
clone.Payload = b
|
var i int
|
||||||
handler(&clone)
|
dst = make([]int16, int(k*float32(len(src))))
|
||||||
}
|
for _, sample := range src {
|
||||||
}
|
sampleN += k
|
||||||
|
for sampleN > 0 {
|
||||||
|
dst[i] = sample
|
||||||
|
i++
|
||||||
|
|
||||||
// ResamplePCM - convert PCM to PCMA/PCMU with same sample rate
|
sampleN -= 1
|
||||||
func ResamplePCM(fromPCM func(int16) byte, handler core.HandlerFunc) core.HandlerFunc {
|
|
||||||
var ts uint32
|
|
||||||
|
|
||||||
return func(packet *rtp.Packet) {
|
|
||||||
len1 := len(packet.Payload)
|
|
||||||
len2 := len1 / 2
|
|
||||||
|
|
||||||
oldSamples := packet.Payload
|
|
||||||
newSamples := make([]byte, len2)
|
|
||||||
|
|
||||||
var i2 int
|
|
||||||
for i1 := 0; i1 < len1; i1 += 2 {
|
|
||||||
sample := int16(uint16(oldSamples[i1])<<8 | uint16(oldSamples[i1+1]))
|
|
||||||
newSamples[i2] = fromPCM(sample)
|
|
||||||
i2++
|
|
||||||
}
|
|
||||||
|
|
||||||
ts += uint32(len2)
|
|
||||||
|
|
||||||
clone := *packet
|
|
||||||
clone.Payload = newSamples
|
|
||||||
clone.Timestamp = ts
|
|
||||||
handler(&clone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownsamplePCM - convert PCM to PCMA/PCMU with decreasing sample rate (N times)
|
|
||||||
func DownsamplePCM(fromPCM func(int16) byte, n float32, handler core.HandlerFunc) core.HandlerFunc {
|
|
||||||
var sampleN, sampleSum float32
|
|
||||||
var ts uint32
|
|
||||||
|
|
||||||
return func(packet *rtp.Packet) {
|
|
||||||
samples := len(packet.Payload) / 2
|
|
||||||
newLen := uint32((float32(samples) + sampleN) / n)
|
|
||||||
|
|
||||||
oldSamples := packet.Payload
|
|
||||||
newSamples := make([]byte, newLen)
|
|
||||||
|
|
||||||
var i2 int
|
|
||||||
for i1 := 0; i1 < len(packet.Payload); i1 += 2 {
|
|
||||||
sampleSum += float32(int16(uint16(oldSamples[i1])<<8 | uint16(oldSamples[i1+1])))
|
|
||||||
if sampleN++; sampleN >= n {
|
|
||||||
newSamples[i2] = fromPCM(int16(sampleSum / n))
|
|
||||||
i2++
|
|
||||||
|
|
||||||
sampleSum = 0
|
|
||||||
sampleN -= n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
ts += newLen
|
|
||||||
|
|
||||||
clone := *packet
|
|
||||||
clone.Payload = newSamples
|
|
||||||
clone.Timestamp = ts
|
|
||||||
handler(&clone)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepackG711 - Repack G.711 PCMA/PCMU into frames of size 1024
|
func FlipEndian(src []byte) (dst []byte) {
|
||||||
// 1. Fixes WebRTC audio quality issue (monotonic timestamp)
|
var i, j int
|
||||||
// 2. Fixes Reolink Doorbell backchannel issue (zero timestamp)
|
n := len(src)
|
||||||
// https://github.com/AlexxIT/go2rtc/issues/331
|
dst = make([]byte, n)
|
||||||
func RepackG711(zeroTS bool, handler core.HandlerFunc) core.HandlerFunc {
|
for i < n {
|
||||||
const PacketSize = 1024
|
x := src[i]
|
||||||
|
i++
|
||||||
|
dst[j] = src[i]
|
||||||
|
j++
|
||||||
|
i++
|
||||||
|
dst[j] = x
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var buf []byte
|
func Transcode(dst, src *core.Codec) func([]byte) []byte {
|
||||||
var seq uint16
|
var reader func([]byte) []int16
|
||||||
var ts uint32
|
var writer func([]int16) []byte
|
||||||
|
var filters []func([]int16) []int16
|
||||||
|
|
||||||
// fix https://github.com/AlexxIT/go2rtc/issues/432
|
switch src.Name {
|
||||||
var mu sync.Mutex
|
case core.CodecPCML:
|
||||||
|
reader = func(src []byte) (dst []int16) {
|
||||||
return func(packet *rtp.Packet) {
|
var i, j int
|
||||||
mu.Lock()
|
n := len(src)
|
||||||
|
dst = make([]int16, n/2)
|
||||||
buf = append(buf, packet.Payload...)
|
for i < n {
|
||||||
if len(buf) < PacketSize {
|
lo := src[i]
|
||||||
mu.Unlock()
|
i++
|
||||||
|
hi := src[i]
|
||||||
|
i++
|
||||||
|
dst[j] = int16(hi)<<8 | int16(lo)
|
||||||
|
j++
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case core.CodecPCM:
|
||||||
pkt := &rtp.Packet{
|
reader = func(src []byte) (dst []int16) {
|
||||||
Header: rtp.Header{
|
var i, j int
|
||||||
Version: 2,
|
n := len(src)
|
||||||
Marker: true, // should be true
|
dst = make([]int16, n/2)
|
||||||
PayloadType: packet.PayloadType, // will be owerwriten
|
for i < n {
|
||||||
SequenceNumber: seq,
|
hi := src[i]
|
||||||
SSRC: packet.SSRC,
|
i++
|
||||||
},
|
lo := src[i]
|
||||||
Payload: buf[:PacketSize],
|
i++
|
||||||
|
dst[j] = int16(hi)<<8 | int16(lo)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
case core.CodecPCMU:
|
||||||
seq++
|
reader = func(src []byte) (dst []int16) {
|
||||||
|
var i int
|
||||||
// don't know if zero TS important for Reolink Doorbell
|
dst = make([]int16, len(src))
|
||||||
// don't have this strange devices for tests
|
for _, sample := range src {
|
||||||
if !zeroTS {
|
dst[i] = PCMUtoPCM(sample)
|
||||||
pkt.Timestamp = ts
|
i++
|
||||||
ts += PacketSize
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
case core.CodecPCMA:
|
||||||
|
reader = func(src []byte) (dst []int16) {
|
||||||
|
var i int
|
||||||
|
dst = make([]int16, len(src))
|
||||||
|
for _, sample := range src {
|
||||||
|
dst[i] = PCMAtoPCM(sample)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buf = buf[PacketSize:]
|
if src.Channels > 1 {
|
||||||
|
filters = append(filters, Downsample(float32(src.Channels)))
|
||||||
|
}
|
||||||
|
|
||||||
mu.Unlock()
|
if src.ClockRate > dst.ClockRate {
|
||||||
|
filters = append(filters, Downsample(float32(src.ClockRate)/float32(dst.ClockRate)))
|
||||||
|
} else if src.ClockRate < dst.ClockRate {
|
||||||
|
filters = append(filters, Upsample(float32(dst.ClockRate)/float32(src.ClockRate)))
|
||||||
|
}
|
||||||
|
|
||||||
handler(pkt)
|
if dst.Channels > 1 {
|
||||||
|
filters = append(filters, Upsample(float32(dst.Channels)))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dst.Name {
|
||||||
|
case core.CodecPCML:
|
||||||
|
writer = func(src []int16) (dst []byte) {
|
||||||
|
var i int
|
||||||
|
dst = make([]byte, len(src)*2)
|
||||||
|
for _, sample := range src {
|
||||||
|
dst[i] = byte(sample)
|
||||||
|
i++
|
||||||
|
dst[i] = byte(sample >> 8)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case core.CodecPCM:
|
||||||
|
writer = func(src []int16) (dst []byte) {
|
||||||
|
var i int
|
||||||
|
dst = make([]byte, len(src)*2)
|
||||||
|
for _, sample := range src {
|
||||||
|
dst[i] = byte(sample >> 8)
|
||||||
|
i++
|
||||||
|
dst[i] = byte(sample)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case core.CodecPCMU:
|
||||||
|
writer = func(src []int16) (dst []byte) {
|
||||||
|
var i int
|
||||||
|
dst = make([]byte, len(src))
|
||||||
|
for _, sample := range src {
|
||||||
|
dst[i] = PCMtoPCMU(sample)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case core.CodecPCMA:
|
||||||
|
writer = func(src []int16) (dst []byte) {
|
||||||
|
var i int
|
||||||
|
dst = make([]byte, len(src))
|
||||||
|
for _, sample := range src {
|
||||||
|
dst[i] = PCMtoPCMA(sample)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(b []byte) []byte {
|
||||||
|
samples := reader(b)
|
||||||
|
for _, filter := range filters {
|
||||||
|
samples = filter(samples)
|
||||||
|
}
|
||||||
|
return writer(samples)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package pcm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTranscode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
src core.Codec
|
||||||
|
dst core.Codec
|
||||||
|
source string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "s16be->s16be",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
|
||||||
|
expect: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s16be->s16le",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCML, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
|
||||||
|
expect: "CAFC1300430328061308510B9E0D760FDA101111EA13BD15F2168216D4156115",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s16be->mulaw",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCMU, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
|
||||||
|
expect: "52FDD1C5BEB8B3B0AEAEABA9A8A8A9AA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s16be->alaw",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCMA, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
|
||||||
|
expect: "7CD4FFED95939E9B8584868083838080",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2ch->1ch",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 2},
|
||||||
|
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
|
||||||
|
expect: "FCCA00130343062808130B510D9E0F76",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1ch->2ch",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 2},
|
||||||
|
source: "FCCA00130343062808130B510D9E0F76",
|
||||||
|
expect: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "16khz->8khz",
|
||||||
|
src: core.Codec{Name: core.CodecPCM, ClockRate: 16000, Channels: 1},
|
||||||
|
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
|
||||||
|
source: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
|
||||||
|
expect: "FCCA00130343062808130B510D9E0F76",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
f := Transcode(&test.dst, &test.src)
|
||||||
|
b, _ := hex.DecodeString(test.source)
|
||||||
|
b = f(b)
|
||||||
|
s := fmt.Sprintf("%X", b)
|
||||||
|
require.Equal(t, test.expect, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -45,7 +45,7 @@ func Open(r io.Reader) (*Producer, error) {
|
|||||||
codec.Name = core.CodecPCMU
|
codec.Name = core.CodecPCMU
|
||||||
}
|
}
|
||||||
|
|
||||||
codec.Channels = uint16(data[2])
|
codec.Channels = data[2]
|
||||||
codec.ClockRate = binary.LittleEndian.Uint32(data[4:])
|
codec.ClockRate = binary.LittleEndian.Uint32(data[4:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (c *Conn) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiv
|
|||||||
codec.Name = core.CodecPCMA
|
codec.Name = core.CodecPCMA
|
||||||
}
|
}
|
||||||
codec.ClockRate = 8000
|
codec.ClockRate = 8000
|
||||||
sender.Handler = pcm.ResampleToG711(track.Codec, 8000, sender.Handler)
|
sender.Handler = pcm.TranscodeHandler(codec, track.Codec, sender.Handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (c *Conn) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, e
|
|||||||
RTPCodecCapability: webrtc.RTPCodecCapability{
|
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||||
MimeType: MimeType(codec),
|
MimeType: MimeType(codec),
|
||||||
ClockRate: codec.ClockRate,
|
ClockRate: codec.ClockRate,
|
||||||
Channels: codec.Channels,
|
Channels: uint16(codec.Channels),
|
||||||
},
|
},
|
||||||
PayloadType: 0, // don't know if this necessary
|
PayloadType: 0, // don't know if this necessary
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -84,6 +84,18 @@
|
|||||||
</script>
|
</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>
|
<button id="homekit">Apple HomeKit</button>
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<form id="homekit-pair" style="margin-bottom: 10px">
|
<form id="homekit-pair" style="margin-bottom: 10px">
|
||||||
@@ -341,7 +353,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<button id="v4l2">V4L2 (USB)</button>
|
<button id="v4l2">V4L2 (Linux video)</button>
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<table id="v4l2-table"></table>
|
<table id="v4l2-table"></table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user