Skip to content

Commit c50e894

Browse files
committed
Add PlayFile function to wyoming server
1 parent 890fd78 commit c50e894

File tree

8 files changed

+226
-162
lines changed

8 files changed

+226
-162
lines changed

pkg/pcm/handlers.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package pcm
22

33
import (
44
"sync"
5+
"time"
56

67
"github.com/AlexxIT/go2rtc/pkg/core"
78
"github.com/pion/rtp"
@@ -82,18 +83,27 @@ func TranscodeHandler(dst, src *core.Codec, handler core.HandlerFunc) core.Handl
8283
}
8384
}
8485

85-
func BytesPerFrame(codec *core.Codec) byte {
86-
channels := byte(codec.Channels)
87-
if channels == 0 {
88-
channels = 1
89-
}
90-
86+
func BytesPerSample(codec *core.Codec) int {
9187
switch codec.Name {
9288
case core.CodecPCML, core.CodecPCM:
93-
return 2 * channels
89+
return 2
9490
case core.CodecPCMU, core.CodecPCMA:
95-
return channels
91+
return 1
9692
}
97-
9893
return 0
9994
}
95+
96+
func BytesPerFrame(codec *core.Codec) int {
97+
if codec.Channels <= 1 {
98+
return BytesPerSample(codec)
99+
}
100+
return int(codec.Channels) * BytesPerSample(codec)
101+
}
102+
103+
func FramesPerDuration(codec *core.Codec, duration time.Duration) int {
104+
return int(time.Duration(codec.ClockRate) * duration / time.Second)
105+
}
106+
107+
func BytesPerDuration(codec *core.Codec, duration time.Duration) int {
108+
return BytesPerFrame(codec) * FramesPerDuration(codec, duration)
109+
}

pkg/pcm/pcm.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
package pcm
22

3-
import "github.com/AlexxIT/go2rtc/pkg/core"
3+
import (
4+
"math"
5+
6+
"github.com/AlexxIT/go2rtc/pkg/core"
7+
)
8+
9+
func ceil(x float32) int {
10+
d, fract := math.Modf(float64(x))
11+
if fract == 0.0 {
12+
return int(d)
13+
}
14+
return int(d) + 1
15+
}
416

517
func Downsample(k float32) func([]int16) []int16 {
618
var sampleN, sampleSum float32
719

820
return func(src []int16) (dst []int16) {
921
var i int
10-
dst = make([]int16, int((float32(len(src))+sampleN)/k))
22+
dst = make([]int16, ceil((float32(len(src))+sampleN)/k))
1123
for _, sample := range src {
1224
sampleSum += float32(sample)
1325
sampleN++
@@ -28,7 +40,7 @@ func Upsample(k float32) func([]int16) []int16 {
2840

2941
return func(src []int16) (dst []int16) {
3042
var i int
31-
dst = make([]int16, int(k*float32(len(src))))
43+
dst = make([]int16, ceil(k*float32(len(src))))
3244
for _, sample := range src {
3345
sampleN += k
3446
for sampleN > 0 {

pkg/pcm/producer_sync.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package pcm
2+
3+
import (
4+
"io"
5+
"time"
6+
7+
"github.com/AlexxIT/go2rtc/pkg/core"
8+
"github.com/pion/rtp"
9+
)
10+
11+
type ProducerSync struct {
12+
core.Connection
13+
src *core.Codec
14+
rd io.Reader
15+
onClose func()
16+
}
17+
18+
func OpenSync(codec *core.Codec, rd io.Reader) *ProducerSync {
19+
medias := []*core.Media{
20+
{
21+
Kind: core.KindAudio,
22+
Direction: core.DirectionRecvonly,
23+
Codecs: ProducerCodecs(),
24+
},
25+
}
26+
27+
return &ProducerSync{
28+
Connection: core.Connection{
29+
ID: core.NewID(),
30+
FormatName: "pcm",
31+
Medias: medias,
32+
Transport: rd,
33+
},
34+
src: codec,
35+
rd: rd,
36+
}
37+
}
38+
39+
func (p *ProducerSync) OnClose(f func()) {
40+
p.onClose = f
41+
}
42+
43+
func (p *ProducerSync) Start() error {
44+
if len(p.Receivers) == 0 {
45+
return nil
46+
}
47+
48+
var pktSeq uint16
49+
var pktTS uint32 // time in frames
50+
var pktTime time.Duration // time in seconds
51+
52+
t0 := time.Now()
53+
54+
dst := p.Receivers[0].Codec
55+
transcode := Transcode(dst, p.src)
56+
57+
const chunkDuration = 20 * time.Millisecond
58+
chunkBytes := BytesPerDuration(p.src, chunkDuration)
59+
chunkFrames := uint32(FramesPerDuration(dst, chunkDuration))
60+
61+
for {
62+
buf := make([]byte, chunkBytes)
63+
n, _ := io.ReadFull(p.rd, buf)
64+
65+
if n == 0 {
66+
break
67+
}
68+
69+
pkt := &core.Packet{
70+
Header: rtp.Header{
71+
Version: 2,
72+
Marker: true,
73+
SequenceNumber: pktSeq,
74+
Timestamp: pktTS,
75+
},
76+
Payload: transcode(buf[:n]),
77+
}
78+
79+
if d := pktTime - time.Since(t0); d > 0 {
80+
time.Sleep(d)
81+
}
82+
83+
p.Receivers[0].WriteRTP(pkt)
84+
p.Recv += n
85+
86+
pktSeq++
87+
pktTS += chunkFrames
88+
pktTime += chunkDuration
89+
}
90+
91+
if p.onClose != nil {
92+
p.onClose()
93+
}
94+
95+
return nil
96+
}

pkg/wav/producer.go

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package wav
22

33
import (
44
"bufio"
5-
"encoding/binary"
65
"errors"
76
"io"
87

@@ -17,39 +16,11 @@ func Open(r io.Reader) (*Producer, error) {
1716
// https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
1817
rd := bufio.NewReaderSize(r, core.BufferSize)
1918

20-
// skip Master RIFF chunk
21-
if _, err := rd.Discard(12); err != nil {
19+
codec, err := ReadHeader(r)
20+
if err != nil {
2221
return nil, err
2322
}
2423

25-
codec := &core.Codec{}
26-
27-
for {
28-
chunkID, data, err := readChunk(rd)
29-
if err != nil {
30-
return nil, err
31-
}
32-
33-
if chunkID == "data" {
34-
break
35-
}
36-
37-
if chunkID == "fmt " {
38-
// https://audiocoding.cc/articles/2008-05-22-wav-file-structure/wav_formats.txt
39-
switch data[0] {
40-
case 1:
41-
codec.Name = core.CodecPCML
42-
case 6:
43-
codec.Name = core.CodecPCMA
44-
case 7:
45-
codec.Name = core.CodecPCMU
46-
}
47-
48-
codec.Channels = data[2]
49-
codec.ClockRate = binary.LittleEndian.Uint32(data[4:])
50-
}
51-
}
52-
5324
if codec.Name == "" {
5425
return nil, errors.New("waw: unsupported codec")
5526
}
@@ -110,18 +81,3 @@ func (c *Producer) Start() error {
11081
ts += PacketSize
11182
}
11283
}
113-
114-
func readChunk(r io.Reader) (chunkID string, data []byte, err error) {
115-
b := make([]byte, 8)
116-
if _, err = io.ReadFull(r, b); err != nil {
117-
return
118-
}
119-
120-
if chunkID = string(b[:4]); chunkID != "data" {
121-
size := binary.LittleEndian.Uint32(b[4:])
122-
data = make([]byte, size)
123-
_, err = io.ReadFull(r, data)
124-
}
125-
126-
return
127-
}

pkg/wav/wav.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package wav
22

33
import (
44
"encoding/binary"
5+
"io"
56

67
"github.com/AlexxIT/go2rtc/pkg/core"
78
)
@@ -48,3 +49,55 @@ func Header(codec *core.Codec) []byte {
4849

4950
return b
5051
}
52+
53+
func ReadHeader(r io.Reader) (*core.Codec, error) {
54+
// skip Master RIFF chunk
55+
if _, err := io.ReadFull(r, make([]byte, 12)); err != nil {
56+
return nil, err
57+
}
58+
59+
var codec core.Codec
60+
61+
for {
62+
chunkID, data, err := readChunk(r)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
if chunkID == "data" {
68+
break
69+
}
70+
71+
if chunkID == "fmt " {
72+
// https://audiocoding.cc/articles/2008-05-22-wav-file-structure/wav_formats.txt
73+
switch data[0] {
74+
case 1:
75+
codec.Name = core.CodecPCML
76+
case 6:
77+
codec.Name = core.CodecPCMA
78+
case 7:
79+
codec.Name = core.CodecPCMU
80+
}
81+
82+
codec.Channels = data[2]
83+
codec.ClockRate = binary.LittleEndian.Uint32(data[4:])
84+
}
85+
}
86+
87+
return &codec, nil
88+
}
89+
90+
func readChunk(r io.Reader) (chunkID string, data []byte, err error) {
91+
b := make([]byte, 8)
92+
if _, err = io.ReadFull(r, b); err != nil {
93+
return
94+
}
95+
96+
if chunkID = string(b[:4]); chunkID != "data" {
97+
size := binary.LittleEndian.Uint32(b[4:])
98+
data = make([]byte, size)
99+
_, err = io.ReadFull(r, data)
100+
}
101+
102+
return
103+
}

pkg/wyoming/expr.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package wyoming
22

33
import (
4+
"bytes"
45
"fmt"
6+
"os"
57
"time"
68

79
"github.com/AlexxIT/go2rtc/pkg/expr"
8-
"golang.org/x/net/context"
10+
"github.com/AlexxIT/go2rtc/pkg/wav"
911
)
1012

1113
type env struct {
@@ -109,16 +111,21 @@ func (s *satellite) WriteEvent(args ...string) bool {
109111
}
110112

111113
func (s *satellite) PlayAudio() bool {
112-
ctx, cancel := context.WithCancel(context.Background())
113-
defer cancel()
114+
return s.playAudio(sndCodec, bytes.NewReader(s.sndAudio))
115+
}
114116

115-
prod := newSndProducer(s.sndAudio, cancel)
116-
if err := s.srv.SndHandler(prod); err != nil {
117+
func (s *satellite) PlayFile(path string) bool {
118+
f, err := os.Open(path)
119+
if err != nil {
117120
return false
118-
} else {
119-
<-ctx.Done()
120-
return true
121121
}
122+
123+
codec, err := wav.ReadHeader(f)
124+
if err != nil {
125+
return false
126+
}
127+
128+
return s.playAudio(codec, f)
122129
}
123130

124131
func (e *env) Sleep(s string) bool {

0 commit comments

Comments
 (0)