OnlineLibrary

Форк
0
348 строк · 8.5 Кб
1
package waveout
2

3
import (
4
	"fmt"
5
	"sync"
6
	"unsafe"
7

8
	"golang.org/x/sys/windows"
9
)
10

11
const (
12
	WAVE_FORMAT_PCM  = 1
13
	WAVE_MAPPER      = -1
14
	WHDR_DONE        = 1
15
	CALLBACK_EVENT   = 0x50000
16
	MAXPNAMELEN      = 32
17
	MMSYSERR_NOERROR = 0
18
)
19

20
var winmm = windows.NewLazySystemDLL("winmm.dll")
21

22
// WaveOut functions
23
var (
24
	procWaveOutGetNumDevs      = winmm.NewProc("waveOutGetNumDevs")
25
	procWaveOutGetDevCapsW     = winmm.NewProc("waveOutGetDevCapsW")
26
	procWaveOutOpen            = winmm.NewProc("waveOutOpen")
27
	procWaveOutPrepareHeader   = winmm.NewProc("waveOutPrepareHeader")
28
	procWaveOutUnprepareHeader = winmm.NewProc("waveOutUnprepareHeader")
29
	procWaveOutWrite           = winmm.NewProc("waveOutWrite")
30
	procWaveOutPause           = winmm.NewProc("waveOutPause")
31
	procWaveOutRestart         = winmm.NewProc("waveOutRestart")
32
	procWaveOutReset           = winmm.NewProc("waveOutReset")
33
	procWaveOutGetVolume       = winmm.NewProc("waveOutGetVolume")
34
	procWaveOutSetVolume       = winmm.NewProc("waveOutSetVolume")
35
	procWaveOutGetErrorTextW   = winmm.NewProc("waveOutGetErrorTextW")
36
	procWaveOutClose           = winmm.NewProc("waveOutClose")
37
)
38

39
// Some win types
40
type WORD uint16
41
type DWORD uint32
42

43
// The WAVEFORMATEX structure defines the format of waveform-audio data
44
type WAVEFORMATEX struct {
45
	wFormatTag      uint16
46
	nChannels       uint16
47
	nSamplesPerSec  uint32
48
	nAvgBytesPerSec uint32
49
	nBlockAlign     uint16
50
	wBitsPerSample  uint16
51
	cbSize          uint16
52
}
53

54
// The WAVEHDR structure defines the header used to identify a waveform-audio buffer
55
type WAVEHDR struct {
56
	lpData          uintptr
57
	dwBufferLength  uint32
58
	dwBytesRecorded uint32
59
	dwUser          uintptr
60
	dwFlags         uint32
61
	dwLoops         uint32
62
	lpNext          uintptr
63
	reserved        uintptr
64
}
65

66
// The WAVEOUTCAPS structure describes the capabilities of a waveform-audio output device
67
type WAVEOUTCAPS struct {
68
	wMid           WORD
69
	wPid           WORD
70
	vDriverVersion uint32
71
	szPname        [MAXPNAMELEN]uint16
72
	dwFormats      DWORD
73
	wChannels      WORD
74
	wReserved1     WORD
75
	dwSupport      DWORD
76
}
77

78
func OutputDevices() func() (int, string, error) {
79
	caps := &WAVEOUTCAPS{}
80
	numDevs, _, _ := procWaveOutGetNumDevs.Call()
81
	devID := WAVE_MAPPER
82
	return func() (int, string, error) {
83
		if devID == int(numDevs) {
84
			return 0, "", fmt.Errorf("no output device")
85
		}
86
		procWaveOutGetDevCapsW.Call(uintptr(devID), uintptr(unsafe.Pointer(caps)), unsafe.Sizeof(*caps))
87
		devID++
88
		return devID - 1, windows.UTF16ToString(caps.szPname[:MAXPNAMELEN]), nil
89
	}
90
}
91

92
func OutputDeviceNames() []string {
93
	names := make([]string, 0)
94
	device := OutputDevices()
95
	for {
96
		_, name, err := device()
97
		if err != nil {
98
			return names
99
		}
100
		names = append(names, name)
101
	}
102
}
103

104
func OutputDeviceNameToID(devName string) (int, error) {
105
	device := OutputDevices()
106
	for {
107
		id, name, err := device()
108
		if err != nil {
109
			return 0, fmt.Errorf("no device name")
110
		}
111
		if devName == name {
112
			return id, nil
113
		}
114
	}
115
}
116

117
func mmcall(p *windows.LazyProc, args ...uintptr) error {
118
	mmrError, _, _ := p.Call(args...)
119
	if mmrError == MMSYSERR_NOERROR {
120
		return nil
121
	}
122

123
	// Buffer for description the error that occurred
124
	buf := make([]uint16, 256)
125

126
	r, _, _ := procWaveOutGetErrorTextW.Call(mmrError, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
127
	if r != MMSYSERR_NOERROR {
128
		return fmt.Errorf("%v: unknown error: code %v", p.Name, mmrError)
129
	}
130
	return fmt.Errorf("%v: %v", p.Name, windows.UTF16ToString(buf))
131
}
132

133
type WavePlayer struct {
134
	wfx                 *WAVEFORMATEX
135
	preferredDeviceName string
136
	pause               bool
137
	callMutex           sync.Mutex
138
	currentDeviceID     int
139
	waveout             uintptr
140
	waveout_event       windows.Handle
141
	waitMutex           sync.Mutex
142
	prev_whdr           *WAVEHDR
143
	buffers             chan []byte
144
}
145

146
func NewWavePlayer(channels, samplesPerSec, bitsPerSample, bufSize int, preferredDeviceName string) (*WavePlayer, error) {
147
	wp := &WavePlayer{
148
		preferredDeviceName: preferredDeviceName,
149
		buffers:             make(chan []byte, 2),
150
	}
151

152
	wp.wfx = &WAVEFORMATEX{
153
		wFormatTag:      WAVE_FORMAT_PCM,
154
		nChannels:       uint16(channels),
155
		nSamplesPerSec:  uint32(samplesPerSec),
156
		wBitsPerSample:  uint16(bitsPerSample),
157
		nBlockAlign:     uint16(bitsPerSample / 8 * channels),
158
		nAvgBytesPerSec: uint32(bitsPerSample) / 8 * uint32(channels) * uint32(samplesPerSec),
159
	}
160

161
	// Completely fill the buffer channel
162
	for i := 0; i < cap(wp.buffers); i++ {
163
		wp.buffers <- make([]byte, bufSize)
164
	}
165

166
	event, err := windows.CreateEvent(nil, 0, 0, nil)
167
	if err != nil {
168
		return nil, err
169
	}
170
	wp.waveout_event = event
171

172
	if wp.openByName(wp.preferredDeviceName) != nil {
173
		if err := wp.openByID(WAVE_MAPPER); err != nil {
174
			return nil, err
175
		}
176
	}
177

178
	// WAVE_MAPPER cannot be the preferred device
179
	if wp.currentDeviceID == WAVE_MAPPER {
180
		wp.preferredDeviceName = ""
181
	}
182

183
	return wp, nil
184
}
185

186
func (wp *WavePlayer) openByName(devName string) error {
187
	devID, err := OutputDeviceNameToID(devName)
188
	if err != nil {
189
		return err
190
	}
191
	return wp.openByID(devID)
192
}
193

194
func (wp *WavePlayer) openByID(devID int) error {
195
	var waveout uintptr
196
	err := mmcall(procWaveOutOpen, uintptr(unsafe.Pointer(&waveout)), uintptr(devID), uintptr(unsafe.Pointer(wp.wfx)), uintptr(wp.waveout_event), 0, CALLBACK_EVENT)
197
	if err != nil {
198
		return err
199
	}
200

201
	if wp.waveout != 0 {
202
		mmcall(procWaveOutReset, wp.waveout)
203
		mmcall(procWaveOutClose, wp.waveout)
204
	}
205

206
	wp.waveout = waveout
207
	wp.currentDeviceID = devID
208

209
	if wp.pause {
210
		mmcall(procWaveOutPause, wp.waveout)
211
	}
212
	return nil
213
}
214

215
func (wp *WavePlayer) SetOutputDevice(devName string) error {
216
	wp.callMutex.Lock()
217
	defer wp.callMutex.Unlock()
218

219
	if err := wp.openByName(devName); err != nil {
220
		return err
221
	}
222

223
	wp.preferredDeviceName = devName
224
	// WAVE_MAPPER cannot be the preferred device
225
	if wp.currentDeviceID == WAVE_MAPPER {
226
		wp.preferredDeviceName = ""
227
	}
228
	return nil
229
}
230

231
func (wp *WavePlayer) feed(whdr *WAVEHDR) error {
232
	err := mmcall(procWaveOutPrepareHeader, wp.waveout, uintptr(unsafe.Pointer(whdr)), unsafe.Sizeof(*whdr))
233
	if err != nil {
234
		return err
235
	}
236
	return mmcall(procWaveOutWrite, wp.waveout, uintptr(unsafe.Pointer(whdr)), unsafe.Sizeof(*whdr))
237
}
238

239
func (wp *WavePlayer) Write(data []byte) (int, error) {
240
	buffer := <-wp.buffers
241
	defer func() { wp.buffers <- buffer }()
242
	length := copy(buffer, data)
243
	if length == 0 {
244
		return 0, nil
245
	}
246

247
	whdr := &WAVEHDR{
248
		lpData:         uintptr(unsafe.Pointer(&buffer[0])),
249
		dwBufferLength: uint32(length),
250
	}
251

252
	wp.callMutex.Lock()
253

254
	// Using WAVE_MAPPER instead of the preferred device means that it was previously disabled. Trying to restore it
255
	if wp.currentDeviceID == WAVE_MAPPER && wp.preferredDeviceName != "" {
256
		wp.openByName(wp.preferredDeviceName)
257
	}
258

259
	err := wp.feed(whdr)
260
	if err != nil && wp.currentDeviceID != WAVE_MAPPER {
261
		// Device was probably disconnected. Switch to WAVE_MAPPER and try again
262
		if wp.openByID(WAVE_MAPPER) == nil {
263
			err = wp.feed(whdr)
264
		}
265
	}
266

267
	wp.callMutex.Unlock()
268

269
	if err != nil {
270
		return 0, err
271
	}
272

273
	wp.wait(whdr)
274
	return length, nil
275
}
276

277
func (wp *WavePlayer) Sync() {
278
	wp.wait(nil)
279
}
280

281
func (wp *WavePlayer) wait(whdr *WAVEHDR) {
282
	wp.waitMutex.Lock()
283
	defer wp.waitMutex.Unlock()
284

285
	if wp.prev_whdr != nil {
286
		for wp.prev_whdr.dwFlags&WHDR_DONE == 0 {
287
			windows.WaitForSingleObject(wp.waveout_event, windows.INFINITE)
288
		}
289

290
		wp.callMutex.Lock()
291
		mmcall(procWaveOutUnprepareHeader, wp.waveout, uintptr(unsafe.Pointer(wp.prev_whdr)), unsafe.Sizeof(*wp.prev_whdr))
292
		wp.callMutex.Unlock()
293
	}
294
	wp.prev_whdr = whdr
295
}
296

297
func (wp *WavePlayer) Pause(pauseState bool) {
298
	wp.callMutex.Lock()
299
	defer wp.callMutex.Unlock()
300

301
	wp.pause = pauseState
302
	if wp.pause {
303
		mmcall(procWaveOutPause, wp.waveout)
304
	} else {
305
		mmcall(procWaveOutRestart, wp.waveout)
306
	}
307
}
308

309
func (wp *WavePlayer) Stop() {
310
	wp.callMutex.Lock()
311
	defer wp.callMutex.Unlock()
312

313
	// Pausing first seems to make waveOutReset respond faster on some systems.
314
	mmcall(procWaveOutPause, wp.waveout)
315
	mmcall(procWaveOutReset, wp.waveout)
316

317
	if wp.pause {
318
		mmcall(procWaveOutPause, wp.waveout)
319
	}
320
}
321

322
func (wp *WavePlayer) GetVolume() (uint16, uint16) {
323
	wp.callMutex.Lock()
324
	defer wp.callMutex.Unlock()
325

326
	var volume uint32
327
	mmcall(procWaveOutGetVolume, wp.waveout, uintptr(unsafe.Pointer(&volume)))
328
	return uint16(volume), uint16(volume >> 16)
329
}
330

331
func (wp *WavePlayer) SetVolume(l, r uint16) {
332
	wp.callMutex.Lock()
333
	defer wp.callMutex.Unlock()
334

335
	volume := uint32(r)<<16 + uint32(l)
336
	mmcall(procWaveOutSetVolume, wp.waveout, uintptr(volume))
337
}
338

339
func (wp *WavePlayer) Close() error {
340
	wp.callMutex.Lock()
341
	defer wp.callMutex.Unlock()
342

343
	err := mmcall(procWaveOutClose, wp.waveout)
344
	windows.CloseHandle(wp.waveout_event)
345
	wp.waveout = 0
346
	wp.waveout_event = 0
347
	return err
348
}
349

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.