1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
#pragma once
#include "ClickStream.h"
#include "config.h"
#include <alsa/asoundlib.h>
#include <fmt/format.h>
#include <iostream>
#include <string>
using namespace std::string_literals;
class PCM
{
public:
PCM(ClickStream& stream): m_stream(stream)
{
// non-blocking
if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err)));
}
if ((err = snd_pcm_set_params(handle,
SND_PCM_FORMAT_S16_LE,
SND_PCM_ACCESS_RW_INTERLEAVED,
1,
f_sample,
1,
100000)) < 0) { // latency in us
throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err)));
}
m_stream.generate();
npfd = snd_pcm_poll_descriptors_count(handle);
if (npfd < 0) {
throw std::runtime_error("snd_pcm_poll_descriptors_count() failed");
}
pfd = (struct pollfd *)malloc(npfd * sizeof(struct pollfd));
if (pfd == nullptr) {
throw std::runtime_error("alloca() error for PCM");
}
if (0 > snd_pcm_poll_descriptors(handle, pfd, npfd))
{
throw std::runtime_error("snd_pcm_poll_descriptors() failure");
}
if (0 > snd_pcm_start(handle))
{
throw std::runtime_error("PCM could not be started");
}
if (npfd != 1) {
std::cout << "Warning: " << std::to_string(npfd) << " poll fds for pcm" << std::endl;
} else if (fd() <= 2) {
std::cout << "Warning: Bad PCM fd: " << std::to_string(fd()) << std::endl;
}
}
~PCM()
{
// pass the remaining samples, otherwise they're dropped in close
err = snd_pcm_drain(handle);
if (err < 0)
std::cerr << fmt::format("snd_pcm_drain failed: {}", snd_strerror(err)) << std::endl;
snd_pcm_close(handle);
free(pfd);
}
int fd()
{
return pfd->fd;
}
// write from buffer to ALSA PCM
void write()
{
snd_pcm_sframes_t written = snd_pcm_writei(handle, m_stream.get_buffer(), nframes);
if (written < 0) {
if (written == -EPIPE) {
std::cout << "Warning: PCM underrun" << std::endl;
}
std::cout << "Recovering." << std::endl;
written = snd_pcm_recover(handle, written, 0);
}
if (written < 0) {
throw std::runtime_error("snd_pcm_writei failed: "s + snd_strerror(written));
}
if (written != nframes) {
std::cout << "Warning: written " << std::to_string(written) << " frames instead of "<< std::to_string(nframes) << std::endl;
}
m_stream.generate();
}
bool wait_for_event()
{
int result = poll(pfd, npfd, 10000);
if (result > 0)
{
// event
return true;
}
else if (result == 0) {
// timeout
return false;
} else {
throw std::runtime_error("Poll unsuccessful");
}
}
bool write_available()
{
snd_pcm_sframes_t result = snd_pcm_avail(handle);
if (0 > result) {
std::cerr << "Error: snd_pcm_avail()" << std::endl;
//throw std::runtime_error("snd_pcm_avail()");
}
return result >= nframes;
}
private:
int err;
snd_pcm_t *handle;
int npfd;
struct pollfd* pfd;
ClickStream& m_stream;
};
|