Make wrapper for boost::lexical_cast
[lsnes.git] / src / platform / portaudio / sound.cpp
blobdf180c7fecc78a10f42fed3594c76f491dfb9790
1 #include "lsnes.hpp"
3 #include "core/audioapi-driver.hpp"
4 #include "core/command.hpp"
5 #include "core/dispatch.hpp"
6 #include "core/framerate.hpp"
7 #include "core/instance.hpp"
8 #include "core/messages.hpp"
9 #include "core/framerate.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/settings.hpp"
12 #include "core/window.hpp"
13 #include "library/framebuffer.hpp"
14 #include "library/string.hpp"
16 #include <cstring>
17 #include <cstdlib>
18 #include <vector>
19 #include <iostream>
20 #include <csignal>
21 #include <sstream>
22 #include <fstream>
23 #include <cassert>
24 #include "library/minmax.hpp"
25 #include <string>
26 #include <map>
27 #include <stdexcept>
28 #include <portaudio.h>
30 namespace
32 //Input stream info.
33 uint32_t current_rfreq = 0;
34 PaDeviceIndex current_rdev = paNoDevice;
35 bool flag_rstereo = false;
36 PaStream* stream_r = NULL;
37 //Output stream info.
38 uint32_t current_pfreq = 0;
39 PaDeviceIndex current_pdev = paNoDevice;
40 bool flag_pstereo = false;
41 PaStream* stream_p = NULL;
42 //Some global info.
43 bool init_flag = false;
44 bool was_enabled = false;
46 int audiocb(const void *input, void *output, unsigned long frame_count,
47 const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags flags, void* user)
49 const unsigned voice_blocksize = 256;
50 if(output && current_pfreq) {
51 int16_t voicebuf[voice_blocksize];
52 unsigned long pframe_count = frame_count;
53 int16_t* _output = reinterpret_cast<int16_t*>(output);
54 size_t ptr = 0;
55 while(pframe_count > 0) {
56 unsigned bsize = min(voice_blocksize / 2, static_cast<unsigned>(pframe_count));
57 audioapi_get_mixed(voicebuf, bsize, flag_pstereo);
58 unsigned limit = bsize * (flag_pstereo ? 2 : 1);
59 if(was_enabled)
60 for(size_t i = 0; i < limit; i++)
61 _output[ptr++] = voicebuf[i];
62 else
63 for(size_t i = 0; i < limit; i++)
64 _output[ptr++] = 0;
65 pframe_count -= bsize;
68 if(input && current_rfreq) {
69 const int16_t* _input = reinterpret_cast<const int16_t*>(input);
70 unsigned long rframe_count = frame_count;
71 float voicebuf2[voice_blocksize];
72 size_t iptr = 0;
73 while(rframe_count > 0) {
74 unsigned bsize = min(voice_blocksize / 2, static_cast<unsigned>(rframe_count));
75 if(flag_rstereo)
76 for(size_t i = 0; i < bsize; i++) {
77 float l = _input[iptr++];
78 float r = _input[iptr++];
79 voicebuf2[i] = (l + r) / 2;
81 else
82 for(size_t i = 0; i < bsize; i++)
83 voicebuf2[i] = _input[iptr++];
84 audioapi_put_voice(voicebuf2, bsize);
85 rframe_count -= bsize;
88 return 0;
91 PaStreamParameters* get_output_parameters(PaDeviceIndex idx_in, PaDeviceIndex idx_out)
93 static PaStreamParameters output;
94 if(idx_out == paNoDevice)
95 return NULL;
96 const PaDeviceInfo* inf = Pa_GetDeviceInfo(idx_out);
97 if(!inf || !inf->maxOutputChannels)
98 return NULL;
99 memset(&output, 0, sizeof(output));
100 output.device = idx_out;
101 output.channelCount = (inf->maxOutputChannels > 1) ? 2 : 1;
102 output.sampleFormat = paInt16;
103 output.suggestedLatency = inf->defaultLowOutputLatency;
104 output.hostApiSpecificStreamInfo = NULL;
105 return &output;
108 PaStreamParameters* get_input_parameters(PaDeviceIndex idx_in, PaDeviceIndex idx_out)
110 static PaStreamParameters input;
111 if(idx_in == paNoDevice)
112 return NULL;
113 const PaDeviceInfo* inf = Pa_GetDeviceInfo(idx_in);
114 if(!inf || !inf->maxInputChannels)
115 return NULL;
116 memset(&input, 0, sizeof(input));
117 input.device = idx_in;
118 input.channelCount = (inf->maxInputChannels > 1) ? 2 : 1;
119 input.sampleFormat = paInt16;
120 input.suggestedLatency = inf->defaultLowInputLatency;
121 input.hostApiSpecificStreamInfo = NULL;
122 return &input;
125 bool dispose_stream(PaStream* s)
127 if(!s)
128 return true;
129 PaError err;
130 err = Pa_StopStream(s);
131 if(err != paNoError) {
132 messages << "Portaudio error (stop): " << Pa_GetErrorText(err)
133 << std::endl;
134 return false;
136 err = Pa_CloseStream(s);
137 if(err != paNoError) {
138 messages << "Portaudio error (close): " << Pa_GetErrorText(err) << std::endl;
139 return false;
141 return true;
144 bool check_indev(PaDeviceIndex dev)
146 if(getenv("LSNES_NO_SOUND_IN"))
147 return false;
148 const PaDeviceInfo* inf = NULL;
149 if(dev != paNoDevice)
150 inf = Pa_GetDeviceInfo(dev);
151 if(dev != paNoDevice && (!inf || !inf->maxInputChannels)) {
152 messages << "Portaudio: Trying to switch to invalid input device" << std::endl;
153 return false;
155 return true;
158 bool check_outdev(PaDeviceIndex dev)
160 const PaDeviceInfo* inf = NULL;
161 if(dev != paNoDevice)
162 inf = Pa_GetDeviceInfo(dev);
163 if(dev != paNoDevice && (!inf || !inf->maxOutputChannels)) {
164 messages << "Portaudio: Trying to switch to invalid output device" << std::endl;
165 return false;
167 return true;
170 void close_output()
172 dispose_stream(stream_p);
173 stream_p = NULL;
174 current_pdev = paNoDevice;
175 current_pfreq = 0;
176 audioapi_send_rate_change(current_rfreq, current_pfreq);
179 void close_input()
181 dispose_stream(stream_r);
182 stream_r = NULL;
183 current_rdev = paNoDevice;
184 current_rfreq = 0;
185 audioapi_send_rate_change(current_rfreq, current_pfreq);
188 void close_all()
190 close_input();
191 close_output();
194 void print_status(unsigned flags)
196 if(flags & 2) {
197 if(current_pfreq)
198 messages << "Portaudio: Output: " << current_pfreq << "Hz "
199 << (flag_pstereo ? "Stereo" : "Mono") << " on '"
200 << Pa_GetDeviceInfo(current_pdev)->name << std::endl;
201 else
202 messages << "Portaudio: No sound output" << std::endl;
204 if(flags & 1) {
205 if(current_rfreq)
206 messages << "Portaudio: Input: " << current_rfreq << "Hz "
207 << (flag_rstereo ? "Stereo" : "Mono") << " on '"
208 << Pa_GetDeviceInfo(current_rdev)->name << std::endl;
209 else
210 messages << "Portaudio: No sound input" << std::endl;
214 //Switch output device only in split-duplex configuration.
215 unsigned switch_devices_outonly(PaDeviceIndex dev)
217 if(!check_outdev(dev))
218 return 1;
219 close_output();
220 if(dev == paNoDevice) {
221 messages << "Sound output closed." << std::endl;
222 return 3;
224 PaStreamParameters* output = get_output_parameters(current_rdev, dev);
225 auto inf = Pa_GetDeviceInfo(dev);
226 PaError err = Pa_OpenStream(&stream_p, NULL, output, inf->defaultSampleRate, 0, 0, audiocb, NULL);
227 if(err != paNoError) {
228 messages << "Portaudio: error (open): " << Pa_GetErrorText(err) << std::endl;
229 audioapi_send_rate_change(current_rfreq, current_pfreq);
230 return 1;
232 flag_pstereo = (output && output->channelCount == 2);
233 err = Pa_StartStream(stream_p);
234 if(err != paNoError) {
235 messages << "Portaudio error (start): " << Pa_GetErrorText(err) << std::endl;
236 audioapi_send_rate_change(current_rfreq, current_pfreq);
237 return 1;
239 const PaStreamInfo* si = Pa_GetStreamInfo(stream_p);
240 current_pfreq = output ? si->sampleRate : 0;
241 current_pdev = dev;
242 audioapi_send_rate_change(current_rfreq, current_pfreq);
243 print_status(2);
244 return 3;
247 //Switch input device only in split-duplex configuration.
248 unsigned switch_devices_inonly(PaDeviceIndex dev)
250 if(!check_indev(dev))
251 return 2;
252 close_input();
253 if(dev == paNoDevice) {
254 messages << "Sound input closed." << std::endl;
255 return 3;
257 PaStreamParameters* input = get_input_parameters(dev, current_pdev);
258 auto inf = Pa_GetDeviceInfo(dev);
259 PaError err = Pa_OpenStream(&stream_r, input, NULL, inf->defaultSampleRate, 0, 0, audiocb, NULL);
260 if(err != paNoError) {
261 messages << "Portaudio: error (open): " << Pa_GetErrorText(err) << std::endl;
262 audioapi_send_rate_change(current_rfreq, current_pfreq);
263 return 2;
265 flag_rstereo = (input && input->channelCount == 2);
266 err = Pa_StartStream(stream_r);
267 if(err != paNoError) {
268 messages << "Portaudio error (start): " << Pa_GetErrorText(err) << std::endl;
269 audioapi_send_rate_change(current_rfreq, current_pfreq);
270 return 2;
272 const PaStreamInfo* si = Pa_GetStreamInfo(stream_r);
273 current_rfreq = input ? si->sampleRate : 0;
274 current_rdev = dev;
275 audioapi_send_rate_change(current_rfreq, current_pfreq);
276 print_status(1);
277 return 3;
280 //Switch to split-duplex configuration.
281 unsigned switch_devices_split(PaDeviceIndex rdev, PaDeviceIndex pdev)
283 unsigned status = 3;
284 if(!check_indev(rdev) || !check_outdev(pdev))
285 return 0;
286 close_all();
287 status &= switch_devices_outonly(pdev);
288 status &= switch_devices_inonly(rdev);
289 return status;
292 unsigned switch_devices(PaDeviceIndex new_in, PaDeviceIndex new_out)
294 bool switch_in = (new_in != current_rdev);
295 bool switch_out = (new_out != current_pdev);
296 if(!switch_in && !switch_out)
297 return 3;
298 else if(!switch_in)
299 return switch_devices_outonly(new_out);
300 else if(!switch_out)
301 return switch_devices_inonly(new_in);
302 else
303 return switch_devices_split(new_in, new_out);
306 bool initial_switch_devices(PaDeviceIndex force_in, PaDeviceIndex force_out)
308 if(force_in == paNoDevice)
309 force_in = Pa_GetDefaultInputDevice();
310 if(force_out == paNoDevice)
311 force_out = Pa_GetDefaultOutputDevice();
312 unsigned r = switch_devices(force_in, force_out);
313 return (r != 0);
316 PaDeviceIndex output_to_index(const std::string& dev)
318 PaDeviceIndex idx;
319 if(dev == "null") {
320 idx = paNoDevice;
321 } else {
322 try {
323 idx = raw_lexical_cast<PaDeviceIndex>(dev);
324 if(idx < 0 || !Pa_GetDeviceInfo(idx))
325 throw std::runtime_error("foo");
326 } catch(std::exception& e) {
327 throw std::runtime_error("Invalid device '" + dev + "'");
330 return idx;
333 struct _audioapi_driver drv = {
334 .init = []() -> void {
335 PaError err = Pa_Initialize();
336 if(err != paNoError) {
337 messages << "Portaudio error (init): " << Pa_GetErrorText(err) << std::endl;
338 messages << "Audio can't be initialized, audio playback disabled" << std::endl;
339 return;
341 init_flag = true;
343 PaDeviceIndex forcedevice_in = paNoDevice;
344 PaDeviceIndex forcedevice_out = paNoDevice;
345 if(getenv("LSNES_FORCE_AUDIO_OUT")) {
346 forcedevice_out = atoi(getenv("LSNES_FORCE_AUDIO_OUT"));
347 messages << "Attempting to force sound output " << forcedevice_out << std::endl;
349 if(getenv("LSNES_FORCE_AUDIO_IN")) {
350 forcedevice_in = atoi(getenv("LSNES_FORCE_AUDIO_IN"));
351 messages << "Attempting to force sound input " << forcedevice_in << std::endl;
353 messages << "Detected " << Pa_GetDeviceCount() << " sound devices." << std::endl;
354 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++)
355 messages << "Audio device " << j << ": " << Pa_GetDeviceInfo(j)->name << std::endl;
356 was_enabled = true;
357 bool any_success = initial_switch_devices(forcedevice_in, forcedevice_out);
358 if(!any_success)
359 messages << "Portaudio: Can't open any sound device, audio disabled" << std::endl;
361 .quit = []() -> void {
362 if(!init_flag)
363 return;
364 switch_devices(paNoDevice, paNoDevice);
365 Pa_Terminate();
366 init_flag = false;
368 .enable = [](bool _enable) -> void { was_enabled = _enable; },
369 .initialized = []() -> bool { return init_flag; },
370 .set_device = [](const std::string& pdev, const std::string& rdev) -> void {
371 PaDeviceIndex pidx = output_to_index(pdev);
372 PaDeviceIndex ridx = output_to_index(rdev);
373 auto ret = switch_devices(ridx, pidx);
374 if(ret == 1)
375 throw std::runtime_error("Can't change output device");
376 if(ret == 2)
377 throw std::runtime_error("Can't change input device");
378 if(ret == 0)
379 throw std::runtime_error("Can't change audio devices");
381 .get_device = [](bool rec) -> std::string {
382 if(rec)
383 if(current_rdev == paNoDevice)
384 return "null";
385 else
386 return (stringfmt() << current_rdev).str();
387 else
388 if(current_pdev == paNoDevice)
389 return "null";
390 else
391 return (stringfmt() << current_pdev).str();
393 .get_devices = [](bool rec) -> std::map<std::string, std::string> {
394 std::map<std::string, std::string> ret;
395 ret["null"] = rec ? "null sound input" : "null sound output";
396 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++) {
397 std::ostringstream str;
398 str << j;
399 auto devinfo = Pa_GetDeviceInfo(j);
400 if(rec && !getenv("LSNES_NO_SOUND_IN") && devinfo->maxInputChannels)
401 ret[str.str()] = devinfo->name;
402 if(!rec && devinfo->maxOutputChannels)
403 ret[str.str()] = devinfo->name;
405 return ret;
407 .name = []() -> const char* { return "Portaudio sound plugin"; }
409 struct audioapi_driver _drv(drv);