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"
24 #include "library/minmax.hpp"
28 #include <portaudio.h>
33 uint32_t current_rfreq
= 0;
34 PaDeviceIndex current_rdev
= paNoDevice
;
35 bool flag_rstereo
= false;
36 PaStream
* stream_r
= NULL
;
38 uint32_t current_pfreq
= 0;
39 PaDeviceIndex current_pdev
= paNoDevice
;
40 bool flag_pstereo
= false;
41 PaStream
* stream_p
= NULL
;
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
);
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);
60 for(size_t i
= 0; i
< limit
; i
++)
61 _output
[ptr
++] = voicebuf
[i
];
63 for(size_t i
= 0; i
< limit
; i
++)
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
];
73 while(rframe_count
> 0) {
74 unsigned bsize
= min(voice_blocksize
/ 2, static_cast<unsigned>(rframe_count
));
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;
82 for(size_t i
= 0; i
< bsize
; i
++)
83 voicebuf2
[i
] = _input
[iptr
++];
84 audioapi_put_voice(voicebuf2
, bsize
);
85 rframe_count
-= bsize
;
91 PaStreamParameters
* get_output_parameters(PaDeviceIndex idx_in
, PaDeviceIndex idx_out
)
93 static PaStreamParameters output
;
94 if(idx_out
== paNoDevice
)
96 const PaDeviceInfo
* inf
= Pa_GetDeviceInfo(idx_out
);
97 if(!inf
|| !inf
->maxOutputChannels
)
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
;
108 PaStreamParameters
* get_input_parameters(PaDeviceIndex idx_in
, PaDeviceIndex idx_out
)
110 static PaStreamParameters input
;
111 if(idx_in
== paNoDevice
)
113 const PaDeviceInfo
* inf
= Pa_GetDeviceInfo(idx_in
);
114 if(!inf
|| !inf
->maxInputChannels
)
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
;
125 bool dispose_stream(PaStream
* s
)
130 err
= Pa_StopStream(s
);
131 if(err
!= paNoError
) {
132 messages
<< "Portaudio error (stop): " << Pa_GetErrorText(err
)
136 err
= Pa_CloseStream(s
);
137 if(err
!= paNoError
) {
138 messages
<< "Portaudio error (close): " << Pa_GetErrorText(err
) << std::endl
;
144 bool check_indev(PaDeviceIndex dev
)
146 if(getenv("LSNES_NO_SOUND_IN"))
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
;
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
;
172 dispose_stream(stream_p
);
174 current_pdev
= paNoDevice
;
176 audioapi_send_rate_change(current_rfreq
, current_pfreq
);
181 dispose_stream(stream_r
);
183 current_rdev
= paNoDevice
;
185 audioapi_send_rate_change(current_rfreq
, current_pfreq
);
194 void print_status(unsigned flags
)
198 messages
<< "Portaudio: Output: " << current_pfreq
<< "Hz "
199 << (flag_pstereo
? "Stereo" : "Mono") << " on '"
200 << Pa_GetDeviceInfo(current_pdev
)->name
<< std::endl
;
202 messages
<< "Portaudio: No sound output" << std::endl
;
206 messages
<< "Portaudio: Input: " << current_rfreq
<< "Hz "
207 << (flag_rstereo
? "Stereo" : "Mono") << " on '"
208 << Pa_GetDeviceInfo(current_rdev
)->name
<< std::endl
;
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
))
220 if(dev
== paNoDevice
) {
221 messages
<< "Sound output closed." << std::endl
;
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
);
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
);
239 const PaStreamInfo
* si
= Pa_GetStreamInfo(stream_p
);
240 current_pfreq
= output
? si
->sampleRate
: 0;
242 audioapi_send_rate_change(current_rfreq
, current_pfreq
);
247 //Switch input device only in split-duplex configuration.
248 unsigned switch_devices_inonly(PaDeviceIndex dev
)
250 if(!check_indev(dev
))
253 if(dev
== paNoDevice
) {
254 messages
<< "Sound input closed." << std::endl
;
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
);
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
);
272 const PaStreamInfo
* si
= Pa_GetStreamInfo(stream_r
);
273 current_rfreq
= input
? si
->sampleRate
: 0;
275 audioapi_send_rate_change(current_rfreq
, current_pfreq
);
280 //Switch to split-duplex configuration.
281 unsigned switch_devices_split(PaDeviceIndex rdev
, PaDeviceIndex pdev
)
284 if(!check_indev(rdev
) || !check_outdev(pdev
))
287 status
&= switch_devices_outonly(pdev
);
288 status
&= switch_devices_inonly(rdev
);
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
)
299 return switch_devices_outonly(new_out
);
301 return switch_devices_inonly(new_in
);
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
);
316 PaDeviceIndex
output_to_index(const std::string
& dev
)
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
+ "'");
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
;
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
;
357 bool any_success
= initial_switch_devices(forcedevice_in
, forcedevice_out
);
359 messages
<< "Portaudio: Can't open any sound device, audio disabled" << std::endl
;
361 .quit
= []() -> void {
364 switch_devices(paNoDevice
, paNoDevice
);
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
);
375 throw std::runtime_error("Can't change output device");
377 throw std::runtime_error("Can't change input device");
379 throw std::runtime_error("Can't change audio devices");
381 .get_device
= [](bool rec
) -> std::string
{
383 if(current_rdev
== paNoDevice
)
386 return (stringfmt() << current_rdev
).str();
388 if(current_pdev
== paNoDevice
)
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
;
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
;
407 .name
= []() -> const char* { return "Portaudio sound plugin"; }
409 struct audioapi_driver
_drv(drv
);