OnlineLibrary
348 строк · 8.5 Кб
1package waveout
2
3import (
4"fmt"
5"sync"
6"unsafe"
7
8"golang.org/x/sys/windows"
9)
10
11const (
12WAVE_FORMAT_PCM = 1
13WAVE_MAPPER = -1
14WHDR_DONE = 1
15CALLBACK_EVENT = 0x50000
16MAXPNAMELEN = 32
17MMSYSERR_NOERROR = 0
18)
19
20var winmm = windows.NewLazySystemDLL("winmm.dll")
21
22// WaveOut functions
23var (
24procWaveOutGetNumDevs = winmm.NewProc("waveOutGetNumDevs")
25procWaveOutGetDevCapsW = winmm.NewProc("waveOutGetDevCapsW")
26procWaveOutOpen = winmm.NewProc("waveOutOpen")
27procWaveOutPrepareHeader = winmm.NewProc("waveOutPrepareHeader")
28procWaveOutUnprepareHeader = winmm.NewProc("waveOutUnprepareHeader")
29procWaveOutWrite = winmm.NewProc("waveOutWrite")
30procWaveOutPause = winmm.NewProc("waveOutPause")
31procWaveOutRestart = winmm.NewProc("waveOutRestart")
32procWaveOutReset = winmm.NewProc("waveOutReset")
33procWaveOutGetVolume = winmm.NewProc("waveOutGetVolume")
34procWaveOutSetVolume = winmm.NewProc("waveOutSetVolume")
35procWaveOutGetErrorTextW = winmm.NewProc("waveOutGetErrorTextW")
36procWaveOutClose = winmm.NewProc("waveOutClose")
37)
38
39// Some win types
40type WORD uint16
41type DWORD uint32
42
43// The WAVEFORMATEX structure defines the format of waveform-audio data
44type WAVEFORMATEX struct {
45wFormatTag uint16
46nChannels uint16
47nSamplesPerSec uint32
48nAvgBytesPerSec uint32
49nBlockAlign uint16
50wBitsPerSample uint16
51cbSize uint16
52}
53
54// The WAVEHDR structure defines the header used to identify a waveform-audio buffer
55type WAVEHDR struct {
56lpData uintptr
57dwBufferLength uint32
58dwBytesRecorded uint32
59dwUser uintptr
60dwFlags uint32
61dwLoops uint32
62lpNext uintptr
63reserved uintptr
64}
65
66// The WAVEOUTCAPS structure describes the capabilities of a waveform-audio output device
67type WAVEOUTCAPS struct {
68wMid WORD
69wPid WORD
70vDriverVersion uint32
71szPname [MAXPNAMELEN]uint16
72dwFormats DWORD
73wChannels WORD
74wReserved1 WORD
75dwSupport DWORD
76}
77
78func OutputDevices() func() (int, string, error) {
79caps := &WAVEOUTCAPS{}
80numDevs, _, _ := procWaveOutGetNumDevs.Call()
81devID := WAVE_MAPPER
82return func() (int, string, error) {
83if devID == int(numDevs) {
84return 0, "", fmt.Errorf("no output device")
85}
86procWaveOutGetDevCapsW.Call(uintptr(devID), uintptr(unsafe.Pointer(caps)), unsafe.Sizeof(*caps))
87devID++
88return devID - 1, windows.UTF16ToString(caps.szPname[:MAXPNAMELEN]), nil
89}
90}
91
92func OutputDeviceNames() []string {
93names := make([]string, 0)
94device := OutputDevices()
95for {
96_, name, err := device()
97if err != nil {
98return names
99}
100names = append(names, name)
101}
102}
103
104func OutputDeviceNameToID(devName string) (int, error) {
105device := OutputDevices()
106for {
107id, name, err := device()
108if err != nil {
109return 0, fmt.Errorf("no device name")
110}
111if devName == name {
112return id, nil
113}
114}
115}
116
117func mmcall(p *windows.LazyProc, args ...uintptr) error {
118mmrError, _, _ := p.Call(args...)
119if mmrError == MMSYSERR_NOERROR {
120return nil
121}
122
123// Buffer for description the error that occurred
124buf := make([]uint16, 256)
125
126r, _, _ := procWaveOutGetErrorTextW.Call(mmrError, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
127if r != MMSYSERR_NOERROR {
128return fmt.Errorf("%v: unknown error: code %v", p.Name, mmrError)
129}
130return fmt.Errorf("%v: %v", p.Name, windows.UTF16ToString(buf))
131}
132
133type WavePlayer struct {
134wfx *WAVEFORMATEX
135preferredDeviceName string
136pause bool
137callMutex sync.Mutex
138currentDeviceID int
139waveout uintptr
140waveout_event windows.Handle
141waitMutex sync.Mutex
142prev_whdr *WAVEHDR
143buffers chan []byte
144}
145
146func NewWavePlayer(channels, samplesPerSec, bitsPerSample, bufSize int, preferredDeviceName string) (*WavePlayer, error) {
147wp := &WavePlayer{
148preferredDeviceName: preferredDeviceName,
149buffers: make(chan []byte, 2),
150}
151
152wp.wfx = &WAVEFORMATEX{
153wFormatTag: WAVE_FORMAT_PCM,
154nChannels: uint16(channels),
155nSamplesPerSec: uint32(samplesPerSec),
156wBitsPerSample: uint16(bitsPerSample),
157nBlockAlign: uint16(bitsPerSample / 8 * channels),
158nAvgBytesPerSec: uint32(bitsPerSample) / 8 * uint32(channels) * uint32(samplesPerSec),
159}
160
161// Completely fill the buffer channel
162for i := 0; i < cap(wp.buffers); i++ {
163wp.buffers <- make([]byte, bufSize)
164}
165
166event, err := windows.CreateEvent(nil, 0, 0, nil)
167if err != nil {
168return nil, err
169}
170wp.waveout_event = event
171
172if wp.openByName(wp.preferredDeviceName) != nil {
173if err := wp.openByID(WAVE_MAPPER); err != nil {
174return nil, err
175}
176}
177
178// WAVE_MAPPER cannot be the preferred device
179if wp.currentDeviceID == WAVE_MAPPER {
180wp.preferredDeviceName = ""
181}
182
183return wp, nil
184}
185
186func (wp *WavePlayer) openByName(devName string) error {
187devID, err := OutputDeviceNameToID(devName)
188if err != nil {
189return err
190}
191return wp.openByID(devID)
192}
193
194func (wp *WavePlayer) openByID(devID int) error {
195var waveout uintptr
196err := mmcall(procWaveOutOpen, uintptr(unsafe.Pointer(&waveout)), uintptr(devID), uintptr(unsafe.Pointer(wp.wfx)), uintptr(wp.waveout_event), 0, CALLBACK_EVENT)
197if err != nil {
198return err
199}
200
201if wp.waveout != 0 {
202mmcall(procWaveOutReset, wp.waveout)
203mmcall(procWaveOutClose, wp.waveout)
204}
205
206wp.waveout = waveout
207wp.currentDeviceID = devID
208
209if wp.pause {
210mmcall(procWaveOutPause, wp.waveout)
211}
212return nil
213}
214
215func (wp *WavePlayer) SetOutputDevice(devName string) error {
216wp.callMutex.Lock()
217defer wp.callMutex.Unlock()
218
219if err := wp.openByName(devName); err != nil {
220return err
221}
222
223wp.preferredDeviceName = devName
224// WAVE_MAPPER cannot be the preferred device
225if wp.currentDeviceID == WAVE_MAPPER {
226wp.preferredDeviceName = ""
227}
228return nil
229}
230
231func (wp *WavePlayer) feed(whdr *WAVEHDR) error {
232err := mmcall(procWaveOutPrepareHeader, wp.waveout, uintptr(unsafe.Pointer(whdr)), unsafe.Sizeof(*whdr))
233if err != nil {
234return err
235}
236return mmcall(procWaveOutWrite, wp.waveout, uintptr(unsafe.Pointer(whdr)), unsafe.Sizeof(*whdr))
237}
238
239func (wp *WavePlayer) Write(data []byte) (int, error) {
240buffer := <-wp.buffers
241defer func() { wp.buffers <- buffer }()
242length := copy(buffer, data)
243if length == 0 {
244return 0, nil
245}
246
247whdr := &WAVEHDR{
248lpData: uintptr(unsafe.Pointer(&buffer[0])),
249dwBufferLength: uint32(length),
250}
251
252wp.callMutex.Lock()
253
254// Using WAVE_MAPPER instead of the preferred device means that it was previously disabled. Trying to restore it
255if wp.currentDeviceID == WAVE_MAPPER && wp.preferredDeviceName != "" {
256wp.openByName(wp.preferredDeviceName)
257}
258
259err := wp.feed(whdr)
260if err != nil && wp.currentDeviceID != WAVE_MAPPER {
261// Device was probably disconnected. Switch to WAVE_MAPPER and try again
262if wp.openByID(WAVE_MAPPER) == nil {
263err = wp.feed(whdr)
264}
265}
266
267wp.callMutex.Unlock()
268
269if err != nil {
270return 0, err
271}
272
273wp.wait(whdr)
274return length, nil
275}
276
277func (wp *WavePlayer) Sync() {
278wp.wait(nil)
279}
280
281func (wp *WavePlayer) wait(whdr *WAVEHDR) {
282wp.waitMutex.Lock()
283defer wp.waitMutex.Unlock()
284
285if wp.prev_whdr != nil {
286for wp.prev_whdr.dwFlags&WHDR_DONE == 0 {
287windows.WaitForSingleObject(wp.waveout_event, windows.INFINITE)
288}
289
290wp.callMutex.Lock()
291mmcall(procWaveOutUnprepareHeader, wp.waveout, uintptr(unsafe.Pointer(wp.prev_whdr)), unsafe.Sizeof(*wp.prev_whdr))
292wp.callMutex.Unlock()
293}
294wp.prev_whdr = whdr
295}
296
297func (wp *WavePlayer) Pause(pauseState bool) {
298wp.callMutex.Lock()
299defer wp.callMutex.Unlock()
300
301wp.pause = pauseState
302if wp.pause {
303mmcall(procWaveOutPause, wp.waveout)
304} else {
305mmcall(procWaveOutRestart, wp.waveout)
306}
307}
308
309func (wp *WavePlayer) Stop() {
310wp.callMutex.Lock()
311defer wp.callMutex.Unlock()
312
313// Pausing first seems to make waveOutReset respond faster on some systems.
314mmcall(procWaveOutPause, wp.waveout)
315mmcall(procWaveOutReset, wp.waveout)
316
317if wp.pause {
318mmcall(procWaveOutPause, wp.waveout)
319}
320}
321
322func (wp *WavePlayer) GetVolume() (uint16, uint16) {
323wp.callMutex.Lock()
324defer wp.callMutex.Unlock()
325
326var volume uint32
327mmcall(procWaveOutGetVolume, wp.waveout, uintptr(unsafe.Pointer(&volume)))
328return uint16(volume), uint16(volume >> 16)
329}
330
331func (wp *WavePlayer) SetVolume(l, r uint16) {
332wp.callMutex.Lock()
333defer wp.callMutex.Unlock()
334
335volume := uint32(r)<<16 + uint32(l)
336mmcall(procWaveOutSetVolume, wp.waveout, uintptr(volume))
337}
338
339func (wp *WavePlayer) Close() error {
340wp.callMutex.Lock()
341defer wp.callMutex.Unlock()
342
343err := mmcall(procWaveOutClose, wp.waveout)
344windows.CloseHandle(wp.waveout_event)
345wp.waveout = 0
346wp.waveout_event = 0
347return err
348}
349