-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAudioOutput.cpp
331 lines (291 loc) · 9.3 KB
/
AudioOutput.cpp
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include "SoapyHifiBerry.h"
#include "AudioOutput.h"
/*
* HifiBerryAudioOutput fills the audio output buffer.
* If there are no samples available (underrun) mutted sound is send
* Sound data is pulled from databuffer and copied to rtaudio buffer
* A underrun counter is increased for adjusting samplerate of the radio
**/
int HifiBerryAudioOutput_Audioout( void *outputBuffer,void *inputBuffer,unsigned int nBufferFrames,double streamTime,RtAudioStreamStatus status, void *userData)
{
IQSample *buffer = (IQSample *) outputBuffer;
if (status)
std::cout << "Stream underflow detected!\n" << std::endl;
// Write interleaved audio data.
if (((SoapyHifiBerryDataBuffer<IQSample> *)userData)->queued_samples() == 0)
{
for (int i = 0; i < nBufferFrames; i++)
{
buffer[i] = IQSample(0,0);
}
return 0;
}
IQSampleVector samples = ((SoapyHifiBerryDataBuffer<IQSample> *)userData)->pull();
int i = 0;
for (auto& col : samples)
{
IQSample v = col;
((IQSample *)buffer)[i++] = v;
}
return 0;
}
/*
* List alsa audio devices but skip the Monitor ones
*
**/
void HifiBerryAudioOutputput::listDevices(std::vector<std::string> &devices)
{
for (auto const &dev : device_map)
{
if (dev.second.size() > 0 && dev.second.find("Monitor") == std::string::npos)
devices.push_back(dev.second);
}
}
/*
* Search for device number based on device name
* Issue -> very slow when user is not allowed to open device
*
*/
int HifiBerryAudioOutputput::getDevices(std::string device)
{
RtAudio::DeviceInfo dev_info;
int noDevices = this->getDeviceCount();
int retval = 0;
if (noDevices < 1) {
std::cout << "\nNo audio devices found!\n";
return -1;
}
for (int i = 0; i < noDevices; i++)
{
dev_info = getDeviceInfo(i);
printf("%d device: %s input %d output %d\n", i, info.name.c_str(), info.inputChannels, info.outputChannels);
if (dev_info.name.find(device) != std::string::npos && dev_info.outputChannels > 0)
{
printf("audio device = %s Samplerate %d\n", info.name.c_str(), info.preferredSampleRate);
info = dev_info;
retval = i;
}
}
return retval; // return default device
}
HifiBerryAudioOutputput::HifiBerryAudioOutputput(int pcmrate, SoapyHifiBerryDataBuffer<IQSample> *AudioBuffer, RtAudio::Api api)
: RtAudio(api),
parameters{}, bufferFrames{}, m_volume{}, underrun{0}, info{0}
{
m_sampleRate = pcmrate;
databuffer = AudioBuffer;
bufferFrames = hifiBerry_BufferSize;
parameters.nChannels = 2;
parameters.firstChannel = 0;
parameters.deviceId = 0;
DigitalPlaybackMin = 0;
DigitalPlaybackMax = 100;
}
/*
* Open sound device based on name
* if name is default open default device
* GetDevics() fills the map with device names and ID's
* Use samplerate which is optimized for device
**/
bool HifiBerryAudioOutputput::open(std::string device)
{
int retry{0};
RtAudioErrorType err;
StreamOptions option{{0}, {0}, {0}, {0}};
option.flags = RTAUDIO_MINIMIZE_LATENCY;
parameters.deviceId = 0;
parameters.firstChannel = 0;
parameters.nChannels = 2;
if (device == "default")
parameters.deviceId = this->getDefaultInputDevice();
else
parameters.deviceId = find_device(device);
info = this->getDeviceInfo(parameters.deviceId);
alsa_device = parameters.deviceId - 1;
parameters.nChannels = info.outputChannels;
printf("HifiBerry audio device = %d %s samplerate %d channels %d\n", parameters.deviceId, device.c_str(), m_sampleRate, parameters.nChannels);
err = this->openStream(¶meters, NULL, RTAUDIO_FLOAT32, m_sampleRate, &bufferFrames, &HifiBerryAudioOutput_Audioout, (void *)databuffer, NULL);
if (err != RTAUDIO_NO_ERROR)
{
printf("Cannot open audio output stream\n");
return false;
}
get_alsa_range(1, "Digital Playback Volume");
this->startStream();
return true;
}
/*
* Set volume of output use log scale
**/
void HifiBerryAudioOutputput::set_volume(int vol)
{
// log volume
m_volume = exp(((double)vol * 6.908) / 100.0) / 1000;
//fprintf(stderr,"vol %f\n", (float)m_volume);
}
void HifiBerryAudioOutputput::adjust_gain(SampleVector& samples)
{
for (unsigned int i = 0, n = samples.size(); i < n; i++) {
samples[i] *= m_volume;
}
}
void HifiBerryAudioOutputput::close()
{
if (isStreamOpen())
{
stopStream();
closeStream();
}
}
HifiBerryAudioOutputput::~HifiBerryAudioOutputput()
{
close();
}
/*
* Write data to audio buffer
**/
bool HifiBerryAudioOutputput::write(IQSampleVector& audiosamples)
{
if (databuffer && isStreamOpen())
databuffer->push(move(audiosamples));
else
audiosamples.clear();
return true;
}
int HifiBerryAudioOutputput::queued_samples()
{
if (databuffer != nullptr)
return databuffer->queued_samples();
return 0;
}
unsigned int HifiBerryAudioOutputput::find_device(std::string name)
{
int devices = getDeviceCount();
unsigned int device = 0;
RtAudio::DeviceInfo info;
for (int i = 1; i <= devices; i++)
{
info = getDeviceInfo(i);
// Print, for example, the maximum number of output channels for each device
std::cout << "device = " << i << " device name " << info.name;
std::cout << ": maximum output channels = " << info.outputChannels << "\n";
if (std::string(info.name).find(name) != string::npos && info.outputChannels > 1)
device = i;
}
return device;
}
int HifiBerryAudioOutputput::lookup_id(snd_ctl_elem_id_t* id, snd_ctl_t* handle)
{
int err;
snd_ctl_elem_info_t* info;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_info_set_id(info, id);
if ((err = snd_ctl_elem_info(handle, info)) < 0) {
fprintf(stderr, "Cannot find the given element from card\n");
return err;
}
snd_ctl_elem_info_get_id(info, id);
return 0;
}
int HifiBerryAudioOutputput::controle_alsa(int element, int ivalue)
{
char str[80];
int err;
snd_ctl_t* handle;
snd_ctl_elem_id_t* id;
snd_ctl_elem_value_t* value;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&value);
sprintf(str, "hw:%d", alsa_device);
if ((err = snd_ctl_open(&handle, str, 0)) < 0) {
fprintf(stderr, "Card open error: %s\n", snd_strerror(err));
return err;
}
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_numid(id, element);
if (err = lookup_id(id, handle))
{
snd_ctl_close(handle);
return err;
}
snd_ctl_elem_value_set_id(value, id);
snd_ctl_elem_value_set_integer(value, 0, ivalue);
snd_ctl_elem_value_set_integer(value, 1, ivalue);
//snd_ctl_elem_value_set_integer(value, 1, 77);
if ((err = snd_ctl_elem_write(handle, value)) < 0) {
fprintf(stderr, "Control element write error: %s\n",
snd_strerror(err));
snd_ctl_close(handle);
return err;
}
snd_ctl_close(handle);
return 0;
}
int HifiBerryAudioOutputput::get_alsa_range(int element, std::string name)
{
char str[80];
snd_ctl_t *ctl;
snd_ctl_elem_id_t *id;
snd_ctl_elem_info_t *info;
snd_ctl_elem_value_t *control;
int err;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_value_alloca(&control);
// Open the control interface for the specified sound card
sprintf(str, "hw:%d", alsa_device);
if ((err = snd_ctl_open(&ctl, str, 0)) < 0)
{
printf("Error opening control interface: %s\n", snd_strerror(err));
return err;
}
// Set the ID of the element we want to retrieve information for
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, name.c_str());
// Retrieve the element information
snd_ctl_elem_info_set_id(info, id);
if ((err = snd_ctl_elem_info(ctl, info)) < 0)
{
printf("Error retrieving element info: %s\n", snd_strerror(err));
snd_ctl_close(ctl);
return err;
}
// Retrieve the element range
DigitalPlaybackMin = snd_ctl_elem_info_get_min(info);
DigitalPlaybackMax = snd_ctl_elem_info_get_max(info);
printf("Digital Playback Volume range: [%ld, %ld]\n", DigitalPlaybackMin, DigitalPlaybackMax);
snd_ctl_close(ctl);
return 0;
}
/*
pi@pi3:~$ amixer --debug - c 3 controls
numid = 22, iface = MIXER, name = 'Headphone Playback Volume'
numid = 6, iface = MIXER, name = 'DSP Program'
numid = 28, iface = MIXER, name = 'ADC Left Capture Source'
numid = 23, iface = MIXER, name = 'ADC Left Input'
numid = 25, iface = MIXER, name = 'ADC Mic Bias'
numid = 29, iface = MIXER, name = 'ADC Right Capture Source'
numid = 24, iface = MIXER, name = 'ADC Right Input'
numid = 21, iface = MIXER, name = 'ADC Capture Volume'
numid = 3, iface = MIXER, name = 'Analogue Playback Boost Volume'
numid = 2, iface = MIXER, name = 'Analogue Playback Volume'
numid = 10, iface = MIXER, name = 'Auto Mute Mono Switch'
numid = 11, iface = MIXER, name = 'Auto Mute Switch'
numid = 8, iface = MIXER, name = 'Auto Mute Time Left'
numid = 9, iface = MIXER, name = 'Auto Mute Time Right'
numid = 7, iface = MIXER, name = 'Clock Missing Period'
numid = 5, iface = MIXER, name = 'Deemphasis Switch'
numid = 4, iface = MIXER, name = 'Digital Playback Switch'
numid = 1, iface = MIXER, name = 'Digital Playback Volume'
numid = 20, iface = MIXER, name = 'Max Overclock DAC'
numid = 19, iface = MIXER, name = 'Max Overclock DSP'
numid = 18, iface = MIXER, name = 'Max Overclock PLL'
numid = 26, iface = MIXER, name = 'PGA Gain Left'
numid = 27, iface = MIXER, name = 'PGA Gain Right'
numid = 16, iface = MIXER, name = 'Volume Ramp Down Emergency Rate'
numid = 17, iface = MIXER, name = 'Volume Ramp Down Emergency Step'
numid = 12, iface = MIXER, name = 'Volume Ramp Down Rate'
numid = 13, iface = MIXER, name = 'Volume Ramp Down Step'
numid = 14, iface = MIXER, name = 'Volume Ramp Up Rate'
numid = 15, iface = MIXER, name = 'Volume Ramp Up Step'
*/