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"
23 #include "core/audioapi.hpp"
24 #include "library/minmax.hpp"
28 #include <portaudio.h>
29 #include <boost/lexical_cast.hpp>
34 uint32_t current_rfreq
= 0;
35 PaDeviceIndex current_rdev
= paNoDevice
;
36 bool flag_rstereo
= false;
37 PaStream
* stream_r
= NULL
;
39 uint32_t current_pfreq
= 0;
40 PaDeviceIndex current_pdev
= paNoDevice
;
41 bool flag_pstereo
= false;
42 PaStream
* stream_p
= NULL
;
44 bool init_flag
= false;
45 bool was_enabled
= false;
47 int audiocb(const void *input
, void *output
, unsigned long frame_count
,
48 const PaStreamCallbackTimeInfo
* time_info
, PaStreamCallbackFlags flags
, void* user
)
50 const unsigned voice_blocksize
= 256;
51 if(output
&& current_pfreq
) {
52 int16_t voicebuf
[voice_blocksize
];
53 unsigned long pframe_count
= frame_count
;
54 int16_t* _output
= reinterpret_cast<int16_t*>(output
);
56 while(pframe_count
> 0) {
57 unsigned bsize
= min(voice_blocksize
/ 2, static_cast<unsigned>(pframe_count
));
58 lsnes_instance
.audio
->get_mixed(voicebuf
, bsize
, flag_pstereo
);
59 unsigned limit
= bsize
* (flag_pstereo
? 2 : 1);
61 for(size_t i
= 0; i
< limit
; i
++)
62 _output
[ptr
++] = voicebuf
[i
];
64 for(size_t i
= 0; i
< limit
; i
++)
66 pframe_count
-= bsize
;
69 if(input
&& current_rfreq
) {
70 const int16_t* _input
= reinterpret_cast<const int16_t*>(input
);
71 unsigned long rframe_count
= frame_count
;
72 float voicebuf2
[voice_blocksize
];
74 while(rframe_count
> 0) {
75 unsigned bsize
= min(voice_blocksize
/ 2, static_cast<unsigned>(rframe_count
));
77 for(size_t i
= 0; i
< bsize
; i
++) {
78 float l
= _input
[iptr
++];
79 float r
= _input
[iptr
++];
80 voicebuf2
[i
] = (l
+ r
) / 2;
83 for(size_t i
= 0; i
< bsize
; i
++)
84 voicebuf2
[i
] = _input
[iptr
++];
85 lsnes_instance
.audio
->put_voice(voicebuf2
, bsize
);
86 rframe_count
-= bsize
;
92 PaStreamParameters
* get_output_parameters(PaDeviceIndex idx_in
, PaDeviceIndex idx_out
)
94 static PaStreamParameters output
;
95 if(idx_out
== paNoDevice
)
97 const PaDeviceInfo
* inf
= Pa_GetDeviceInfo(idx_out
);
98 if(!inf
|| !inf
->maxOutputChannels
)
100 memset(&output
, 0, sizeof(output
));
101 output
.device
= idx_out
;
102 output
.channelCount
= (inf
->maxOutputChannels
> 1) ? 2 : 1;
103 output
.sampleFormat
= paInt16
;
104 output
.suggestedLatency
= inf
->defaultLowOutputLatency
;
105 output
.hostApiSpecificStreamInfo
= NULL
;
109 PaStreamParameters
* get_input_parameters(PaDeviceIndex idx_in
, PaDeviceIndex idx_out
)
111 static PaStreamParameters input
;
112 if(idx_in
== paNoDevice
)
114 const PaDeviceInfo
* inf
= Pa_GetDeviceInfo(idx_in
);
115 if(!inf
|| !inf
->maxInputChannels
)
117 memset(&input
, 0, sizeof(input
));
118 input
.device
= idx_in
;
119 input
.channelCount
= (inf
->maxInputChannels
> 1) ? 2 : 1;
120 input
.sampleFormat
= paInt16
;
121 input
.suggestedLatency
= inf
->defaultLowInputLatency
;
122 input
.hostApiSpecificStreamInfo
= NULL
;
126 bool dispose_stream(PaStream
* s
)
131 err
= Pa_StopStream(s
);
132 if(err
!= paNoError
) {
133 messages
<< "Portaudio error (stop): " << Pa_GetErrorText(err
)
137 err
= Pa_CloseStream(s
);
138 if(err
!= paNoError
) {
139 messages
<< "Portaudio error (close): " << Pa_GetErrorText(err
) << std::endl
;
145 bool check_indev(PaDeviceIndex dev
)
147 if(getenv("LSNES_NO_SOUND_IN"))
149 const PaDeviceInfo
* inf
= NULL
;
150 if(dev
!= paNoDevice
)
151 inf
= Pa_GetDeviceInfo(dev
);
152 if(dev
!= paNoDevice
&& (!inf
|| !inf
->maxInputChannels
)) {
153 messages
<< "Portaudio: Trying to switch to invalid input device" << std::endl
;
159 bool check_outdev(PaDeviceIndex dev
)
161 const PaDeviceInfo
* inf
= NULL
;
162 if(dev
!= paNoDevice
)
163 inf
= Pa_GetDeviceInfo(dev
);
164 if(dev
!= paNoDevice
&& (!inf
|| !inf
->maxOutputChannels
)) {
165 messages
<< "Portaudio: Trying to switch to invalid output device" << std::endl
;
173 dispose_stream(stream_p
);
175 current_pdev
= paNoDevice
;
177 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
182 dispose_stream(stream_r
);
184 current_rdev
= paNoDevice
;
186 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
195 void print_status(unsigned flags
)
199 messages
<< "Portaudio: Output: " << current_pfreq
<< "Hz "
200 << (flag_pstereo
? "Stereo" : "Mono") << " on '"
201 << Pa_GetDeviceInfo(current_pdev
)->name
<< std::endl
;
203 messages
<< "Portaudio: No sound output" << std::endl
;
207 messages
<< "Portaudio: Input: " << current_rfreq
<< "Hz "
208 << (flag_rstereo
? "Stereo" : "Mono") << " on '"
209 << Pa_GetDeviceInfo(current_rdev
)->name
<< std::endl
;
211 messages
<< "Portaudio: No sound input" << std::endl
;
215 //Switch output device only in split-duplex configuration.
216 unsigned switch_devices_outonly(PaDeviceIndex dev
)
218 if(!check_outdev(dev
))
221 if(dev
== paNoDevice
) {
222 messages
<< "Sound output closed." << std::endl
;
225 PaStreamParameters
* output
= get_output_parameters(current_rdev
, dev
);
226 auto inf
= Pa_GetDeviceInfo(dev
);
227 PaError err
= Pa_OpenStream(&stream_p
, NULL
, output
, inf
->defaultSampleRate
, 0, 0, audiocb
, NULL
);
228 if(err
!= paNoError
) {
229 messages
<< "Portaudio: error (open): " << Pa_GetErrorText(err
) << std::endl
;
230 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
233 flag_pstereo
= (output
&& output
->channelCount
== 2);
234 err
= Pa_StartStream(stream_p
);
235 if(err
!= paNoError
) {
236 messages
<< "Portaudio error (start): " << Pa_GetErrorText(err
) << std::endl
;
237 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
240 const PaStreamInfo
* si
= Pa_GetStreamInfo(stream_p
);
241 current_pfreq
= output
? si
->sampleRate
: 0;
243 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
248 //Switch input device only in split-duplex configuration.
249 unsigned switch_devices_inonly(PaDeviceIndex dev
)
251 if(!check_indev(dev
))
254 if(dev
== paNoDevice
) {
255 messages
<< "Sound input closed." << std::endl
;
258 PaStreamParameters
* input
= get_input_parameters(dev
, current_pdev
);
259 auto inf
= Pa_GetDeviceInfo(dev
);
260 PaError err
= Pa_OpenStream(&stream_r
, input
, NULL
, inf
->defaultSampleRate
, 0, 0, audiocb
, NULL
);
261 if(err
!= paNoError
) {
262 messages
<< "Portaudio: error (open): " << Pa_GetErrorText(err
) << std::endl
;
263 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
266 flag_rstereo
= (input
&& input
->channelCount
== 2);
267 err
= Pa_StartStream(stream_r
);
268 if(err
!= paNoError
) {
269 messages
<< "Portaudio error (start): " << Pa_GetErrorText(err
) << std::endl
;
270 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
273 const PaStreamInfo
* si
= Pa_GetStreamInfo(stream_r
);
274 current_rfreq
= input
? si
->sampleRate
: 0;
276 lsnes_instance
.audio
->voice_rate(current_rfreq
, current_pfreq
);
281 //Switch to split-duplex configuration.
282 unsigned switch_devices_split(PaDeviceIndex rdev
, PaDeviceIndex pdev
)
285 if(!check_indev(rdev
) || !check_outdev(pdev
))
288 status
&= switch_devices_outonly(pdev
);
289 status
&= switch_devices_inonly(rdev
);
293 unsigned switch_devices(PaDeviceIndex new_in
, PaDeviceIndex new_out
)
295 bool switch_in
= (new_in
!= current_rdev
);
296 bool switch_out
= (new_out
!= current_pdev
);
297 if(!switch_in
&& !switch_out
)
300 return switch_devices_outonly(new_out
);
302 return switch_devices_inonly(new_in
);
304 return switch_devices_split(new_in
, new_out
);
307 bool initial_switch_devices(PaDeviceIndex force_in
, PaDeviceIndex force_out
)
309 if(force_in
== paNoDevice
)
310 force_in
= Pa_GetDefaultInputDevice();
311 if(force_out
== paNoDevice
)
312 force_out
= Pa_GetDefaultOutputDevice();
313 unsigned r
= switch_devices(force_in
, force_out
);
317 PaDeviceIndex
output_to_index(const std::string
& dev
)
324 idx
= boost::lexical_cast
<PaDeviceIndex
>(dev
);
325 if(idx
< 0 || !Pa_GetDeviceInfo(idx
))
326 throw std::runtime_error("foo");
327 } catch(std::exception
& e
) {
328 throw std::runtime_error("Invalid device '" + dev
+ "'");
334 struct _audioapi_driver drv
= {
335 .init
= []() -> void {
336 PaError err
= Pa_Initialize();
337 if(err
!= paNoError
) {
338 messages
<< "Portaudio error (init): " << Pa_GetErrorText(err
) << std::endl
;
339 messages
<< "Audio can't be initialized, audio playback disabled" << std::endl
;
344 PaDeviceIndex forcedevice_in
= paNoDevice
;
345 PaDeviceIndex forcedevice_out
= paNoDevice
;
346 if(getenv("LSNES_FORCE_AUDIO_OUT")) {
347 forcedevice_out
= atoi(getenv("LSNES_FORCE_AUDIO_OUT"));
348 messages
<< "Attempting to force sound output " << forcedevice_out
<< std::endl
;
350 if(getenv("LSNES_FORCE_AUDIO_IN")) {
351 forcedevice_in
= atoi(getenv("LSNES_FORCE_AUDIO_IN"));
352 messages
<< "Attempting to force sound input " << forcedevice_in
<< std::endl
;
354 messages
<< "Detected " << Pa_GetDeviceCount() << " sound devices." << std::endl
;
355 for(PaDeviceIndex j
= 0; j
< Pa_GetDeviceCount(); j
++)
356 messages
<< "Audio device " << j
<< ": " << Pa_GetDeviceInfo(j
)->name
<< std::endl
;
358 bool any_success
= initial_switch_devices(forcedevice_in
, forcedevice_out
);
360 messages
<< "Portaudio: Can't open any sound device, audio disabled" << std::endl
;
362 .quit
= []() -> void {
365 switch_devices(paNoDevice
, paNoDevice
);
369 .enable
= [](bool _enable
) -> void { was_enabled
= _enable
; },
370 .initialized
= []() -> bool { return init_flag
; },
371 .set_device
= [](const std::string
& pdev
, const std::string
& rdev
) -> void {
372 PaDeviceIndex pidx
= output_to_index(pdev
);
373 PaDeviceIndex ridx
= output_to_index(rdev
);
374 auto ret
= switch_devices(ridx
, pidx
);
376 throw std::runtime_error("Can't change output device");
378 throw std::runtime_error("Can't change input device");
380 throw std::runtime_error("Can't change audio devices");
382 .get_device
= [](bool rec
) -> std::string
{
384 if(current_rdev
== paNoDevice
)
387 return (stringfmt() << current_rdev
).str();
389 if(current_pdev
== paNoDevice
)
392 return (stringfmt() << current_pdev
).str();
394 .get_devices
= [](bool rec
) -> std::map
<std::string
, std::string
> {
395 std::map
<std::string
, std::string
> ret
;
396 ret
["null"] = rec
? "null sound input" : "null sound output";
397 for(PaDeviceIndex j
= 0; j
< Pa_GetDeviceCount(); j
++) {
398 std::ostringstream str
;
400 auto devinfo
= Pa_GetDeviceInfo(j
);
401 if(rec
&& !getenv("LSNES_NO_SOUND_IN") && devinfo
->maxInputChannels
)
402 ret
[str
.str()] = devinfo
->name
;
403 if(!rec
&& devinfo
->maxOutputChannels
)
404 ret
[str
.str()] = devinfo
->name
;
408 .name
= []() -> const char* { return "Portaudio sound plugin"; }
410 struct audioapi_driver
_drv(drv
);