3
3
package audio
4
4
5
5
import (
6
- "github.com/oakmound/alsa-go"
7
6
"github.com/pkg/errors"
7
+ "github.com/yobert/alsa"
8
8
)
9
9
10
10
type alsaAudio struct {
11
11
* Encoding
12
- * alsa.Handle
12
+ * alsa.Device
13
+ playAmount int
14
+ playProgress int
15
+ stopCh chan struct {}
16
+ playing bool
17
+ playCh chan error
18
+ period int
13
19
}
14
20
15
21
func (aa * alsaAudio ) Play () <- chan error {
16
- ch := make (chan error )
22
+ // If currently playing, restart
23
+ if aa .playing {
24
+ aa .playProgress = 0
25
+ return aa .playCh
26
+ }
27
+ aa .playing = true
28
+ aa .playCh = make (chan error )
17
29
go func () {
18
- // Todo: loop? library does not export loop
19
- _ , err := aa .Handle .Write (aa .Encoding .Data )
20
- ch <- err
30
+ for {
31
+ var data []byte
32
+ if len (aa .Encoding .Data )- aa .playProgress <= aa .playAmount {
33
+ data = aa .Encoding .Data [aa .playProgress :]
34
+ if aa .Loop {
35
+ delta := aa .playAmount - (len (aa .Encoding .Data ) - aa .playProgress )
36
+ data = append (data , aa .Encoding .Data [:delta ]... )
37
+ }
38
+ } else {
39
+ data = aa .Encoding .Data [aa .playProgress : aa .playProgress + aa .playAmount ]
40
+ }
41
+ if len (data ) != 0 {
42
+ err := aa .Device .Write (data , aa .period )
43
+ if err != nil {
44
+ select {
45
+ case aa .playCh <- err :
46
+ default :
47
+ }
48
+ break
49
+ }
50
+ }
51
+ aa .playProgress += aa .playAmount
52
+ if aa .playProgress > len (aa .Encoding .Data ) {
53
+ if aa .Loop {
54
+ aa .playProgress %= len (aa .Encoding .Data )
55
+ } else {
56
+ select {
57
+ case aa .playCh <- nil :
58
+ default :
59
+ }
60
+ break
61
+ }
62
+ }
63
+ select {
64
+ case <- aa .stopCh :
65
+ select {
66
+ case aa .playCh <- nil :
67
+ default :
68
+ }
69
+ break
70
+ default :
71
+ }
72
+ }
73
+ aa .playing = false
74
+ aa .playProgress = 0
21
75
}()
22
- return ch
76
+ return aa . playCh
23
77
}
24
78
25
79
func (aa * alsaAudio ) Stop () error {
26
- // Todo: don't just pause man, actually stop
27
- // library we are using does not export stop
28
- return aa .Pause ()
80
+ if aa .playing {
81
+ go func () {
82
+ aa .stopCh <- struct {}{}
83
+ }()
84
+ } else {
85
+ return errors .New ("Audio not playing, cannot stop" )
86
+ }
87
+ return nil
29
88
}
30
89
31
90
func (aa * alsaAudio ) Filter (fs ... Filter ) (Audio , error ) {
@@ -52,31 +111,84 @@ func (aa *alsaAudio) MustFilter(fs ...Filter) Audio {
52
111
}
53
112
54
113
func EncodeBytes (enc Encoding ) (Audio , error ) {
55
- handle := alsa .New ()
56
- err := handle .Open ("default" , alsa .StreamTypePlayback , alsa .ModeBlock )
114
+ handle , err := openDevice ()
57
115
if err != nil {
58
116
return nil , err
59
117
}
60
-
61
- handle .SampleFormat = alsaFormat (enc .Bits )
62
- handle .SampleRate = int (enc .SampleRate )
63
- handle .Channels = int (enc .Channels )
64
- err = handle .ApplyHwParams ()
118
+ // Todo: annotate these errors with more info
119
+ format , err := alsaFormat (enc .Bits )
120
+ if err != nil {
121
+ return nil , err
122
+ }
123
+ _ , err = handle .NegotiateFormat (format )
124
+ if err != nil {
125
+ return nil , err
126
+ }
127
+ _ , err = handle .NegotiateRate (int (enc .SampleRate ))
128
+ if err != nil {
129
+ return nil , err
130
+ }
131
+ _ , err = handle .NegotiateChannels (int (enc .Channels ))
132
+ if err != nil {
133
+ return nil , err
134
+ }
135
+ // Default value at recommendation of library
136
+ period , err := handle .NegotiatePeriodSize (2048 )
137
+ if err != nil {
138
+ return nil , err
139
+ }
140
+ _ , err = handle .NegotiateBufferSize (4096 )
141
+ if err != nil {
142
+ return nil , err
143
+ }
144
+ err = handle .Prepare ()
65
145
if err != nil {
66
146
return nil , err
67
147
}
68
148
return & alsaAudio {
69
- & enc ,
70
- handle ,
149
+ playAmount : period * int (enc .Bits ) / 4 ,
150
+ period : period ,
151
+ Encoding : & enc ,
152
+ Device : handle ,
153
+ stopCh : make (chan struct {}),
71
154
}, nil
72
155
}
73
156
74
- func alsaFormat (bits uint16 ) alsa.SampleFormat {
157
+ func openDevice () (* alsa.Device , error ) {
158
+ cards , err := alsa .OpenCards ()
159
+ if err != nil {
160
+ return nil , err
161
+ }
162
+ for i , c := range cards {
163
+ dvcs , err := c .Devices ()
164
+ if err != nil {
165
+ alsa .CloseCards ([]* alsa.Card {c })
166
+ continue
167
+ }
168
+ for _ , d := range dvcs {
169
+ if d .Type != alsa .PCM || ! d .Play {
170
+ continue
171
+ }
172
+ err := d .Open ()
173
+ if err != nil {
174
+ continue
175
+ }
176
+ // We've a found a device we can hypothetically use
177
+ // Close all other cards
178
+ alsa .CloseCards (cards [i + 1 :])
179
+ return d , nil
180
+ }
181
+ alsa .CloseCards ([]* alsa.Card {c })
182
+ }
183
+ return nil , errors .New ("No valid device found" )
184
+ }
185
+
186
+ func alsaFormat (bits uint16 ) (alsa.FormatType , error ) {
75
187
switch bits {
76
188
case 8 :
77
- return alsa .SampleFormatS8
189
+ return alsa .S8 , nil
78
190
case 16 :
79
- return alsa .SampleFormatS16LE
191
+ return alsa .S16_LE , nil
80
192
}
81
- return alsa . SampleFormatUnknown
193
+ return 0 , errors . New ( "Undefined alsa format for encoding bits" )
82
194
}
0 commit comments