diff --git a/src/osd/modules/sound/pa_sound.cpp b/src/osd/modules/sound/pa_sound.cpp index ee7f6420fe4..fb92689504f 100644 --- a/src/osd/modules/sound/pa_sound.cpp +++ b/src/osd/modules/sound/pa_sound.cpp @@ -2,9 +2,9 @@ // copyright-holders:intealls, R.Belmont /*************************************************************************** - pa_sound.c + pa_sound.c - PortAudio interface. + PortAudio interface. *******************************************************************c********/ @@ -34,404 +34,404 @@ class sound_pa : public osd_module, public sound_module { public: - sound_pa() - : osd_module(OSD_SOUND_PROVIDER, "portaudio"), sound_module() - { - } - virtual ~sound_pa() { } + sound_pa() + : osd_module(OSD_SOUND_PROVIDER, "portaudio"), sound_module() + { + } + virtual ~sound_pa() { } - virtual int init(osd_options const &options); - virtual void exit(); + virtual int init(osd_options const &options); + virtual void exit(); - // sound_module + // sound_module - virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame); - virtual void set_mastervolume(int attenuation); + virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame); + virtual void set_mastervolume(int attenuation); private: - /* Lock free SPSC ring buffer */ - template - struct audio_buffer { - T* buf; - int size; - int reserve; - std::atomic rd_pos, wr_pos; + /* Lock free SPSC ring buffer */ + template + struct audio_buffer { + T* buf; + int size; + int reserve; + std::atomic rd_pos, wr_pos; - audio_buffer(int size, int reserve) : size(size), reserve(reserve) { - rd_pos = wr_pos = 0; - buf = new T[size](); - } + audio_buffer(int size, int reserve) : size(size), reserve(reserve) { + rd_pos = wr_pos = 0; + buf = new T[size](); + } - ~audio_buffer() { delete[] buf; } + ~audio_buffer() { delete[] buf; } - int count() { - int diff; - diff = wr_pos - rd_pos; - diff = diff < 0 ? size + diff : diff; - diff -= reserve; - return diff < 0 ? 0 : diff; - } + int count() { + int diff; + diff = wr_pos - rd_pos; + diff = diff < 0 ? size + diff : diff; + diff -= reserve; + return diff < 0 ? 0 : diff; + } - void increment_wrpos(int n) { - wr_pos = (wr_pos + n) % size; - } + void increment_wrpos(int n) { + wr_pos = (wr_pos + n) % size; + } - int write(const T* src, int n, int attenuation) { - n = std::min(n, size - count()); + int write(const T* src, int n, int attenuation) { + n = std::min(n, size - count()); - if (wr_pos + n > size) { - att_memcpy(buf + wr_pos, src, sizeof(T) * (size - wr_pos), attenuation); - att_memcpy(buf, src + (size - wr_pos), sizeof(T) * (n - (size - wr_pos)), attenuation); - } else { - att_memcpy(buf + wr_pos, src, sizeof(T) * n, attenuation); - } + if (wr_pos + n > size) { + att_memcpy(buf + wr_pos, src, sizeof(T) * (size - wr_pos), attenuation); + att_memcpy(buf, src + (size - wr_pos), sizeof(T) * (n - (size - wr_pos)), attenuation); + } else { + att_memcpy(buf + wr_pos, src, sizeof(T) * n, attenuation); + } - increment_wrpos(n); + increment_wrpos(n); - return n; - } + return n; + } - void increment_rdpos(int n) { - rd_pos = (rd_pos + n) % size; - } + void increment_rdpos(int n) { + rd_pos = (rd_pos + n) % size; + } - int read(T* dst, int n) { - n = std::min(n, count()); + int read(T* dst, int n) { + n = std::min(n, count()); - if (rd_pos + n > size) { - std::memcpy(dst, buf + rd_pos, sizeof(T) * (size - rd_pos)); - std::memcpy(dst + (size - rd_pos), buf, sizeof(T) * (n - (size - rd_pos))); - } else { - std::memcpy(dst, buf + rd_pos, sizeof(T) * n); - } + if (rd_pos + n > size) { + std::memcpy(dst, buf + rd_pos, sizeof(T) * (size - rd_pos)); + std::memcpy(dst + (size - rd_pos), buf, sizeof(T) * (n - (size - rd_pos))); + } else { + std::memcpy(dst, buf + rd_pos, sizeof(T) * n); + } - increment_rdpos(n); + increment_rdpos(n); - return n; - } + return n; + } - int clear(int n) { - n = std::min(n, size - count()); + int clear(int n) { + n = std::min(n, size - count()); - if (wr_pos + n > size) { - std::memset(buf + wr_pos, 0, sizeof(T) * (size - wr_pos)); - std::memset(buf, 0, sizeof(T) * (n - (size - wr_pos))); - } else { - std::memset(buf + wr_pos, 0, sizeof(T) * n); - } + if (wr_pos + n > size) { + std::memset(buf + wr_pos, 0, sizeof(T) * (size - wr_pos)); + std::memset(buf, 0, sizeof(T) * (n - (size - wr_pos))); + } else { + std::memset(buf + wr_pos, 0, sizeof(T) * n); + } - increment_wrpos(n); + increment_wrpos(n); - return n; - } + return n; + } - void att_memcpy(T* dest, const T* data, int n, int attenuation) { - int level = powf(10.0, attenuation / 20.0) * 32768; - n /= sizeof(T); - while (n--) - *dest++ = (*data++ * level) >> 15; - } - }; + void att_memcpy(T* dest, const T* data, int n, int attenuation) { + int level = powf(10.0, attenuation / 20.0) * 32768; + n /= sizeof(T); + while (n--) + *dest++ = (*data++ * level) >> 15; + } + }; - int callback(s16* output_buffer, size_t number_of_frames); - static int _callback(const void*, - void *output_buffer, - unsigned long number_of_frames, - const PaStreamCallbackTimeInfo*, - PaStreamCallbackFlags, - void *arg) { return static_cast (arg)-> - callback((s16*) output_buffer, number_of_frames * 2); } + int callback(s16* output_buffer, size_t number_of_frames); + static int _callback(const void*, + void *output_buffer, + unsigned long number_of_frames, + const PaStreamCallbackTimeInfo*, + PaStreamCallbackFlags, + void *arg) { return static_cast (arg)-> + callback((s16*) output_buffer, number_of_frames * 2); } - PaDeviceIndex list_get_devidx(const char* api_str, const char* device_str); + PaDeviceIndex list_get_devidx(const char* api_str, const char* device_str); - PaStream* m_pa_stream; - PaError err; + PaStream* m_pa_stream; + PaError err; - int m_attenuation; + int m_attenuation; - audio_buffer* m_ab; + audio_buffer* m_ab; - std::atomic m_has_underflowed; - std::atomic m_has_overflowed; - unsigned m_underflows; - unsigned m_overflows; + std::atomic m_has_underflowed; + std::atomic m_has_overflowed; + unsigned m_underflows; + unsigned m_overflows; - int m_skip_threshold; // this many samples in the buffer ~1 second in a row count as an overflow - osd_ticks_t m_osd_ticks; - osd_ticks_t m_skip_threshold_ticks; - osd_ticks_t m_osd_tps; - int m_buffer_min_ct; + int m_skip_threshold; // this many samples in the buffer ~1 second in a row count as an overflow + osd_ticks_t m_osd_ticks; + osd_ticks_t m_skip_threshold_ticks; + osd_ticks_t m_osd_tps; + int m_buffer_min_ct; #if LOG_BUFCNT - std::stringstream m_log; + std::stringstream m_log; #endif }; int sound_pa::init(osd_options const &options) { - PaStreamParameters stream_params; - const PaStreamInfo* stream_info; - const PaHostApiInfo* api_info; - const PaDeviceInfo* device_info; + PaStreamParameters stream_params; + const PaStreamInfo* stream_info; + const PaHostApiInfo* api_info; + const PaDeviceInfo* device_info; - unsigned long frames_per_callback = paFramesPerBufferUnspecified; - double callback_interval; + unsigned long frames_per_callback = paFramesPerBufferUnspecified; + double callback_interval; - if (!sample_rate()) - return 0; + if (!sample_rate()) + return 0; - m_attenuation = options.volume(); - m_underflows = 0; - m_overflows = 0; - m_has_overflowed = false; - m_has_underflowed = false; - m_skip_threshold_ticks = 0; + m_attenuation = options.volume(); + m_underflows = 0; + m_overflows = 0; + m_has_overflowed = false; + m_has_underflowed = false; + m_skip_threshold_ticks = 0; - try { - m_ab = new audio_buffer(m_sample_rate, 2); - } catch (std::bad_alloc&) { - osd_printf_error("PortAudio: Unable to allocate audio buffer, sound is disabled\n"); - goto error; - } + try { + m_ab = new audio_buffer(m_sample_rate, 2); + } catch (std::bad_alloc&) { + osd_printf_error("PortAudio: Unable to allocate audio buffer, sound is disabled\n"); + goto error; + } - m_osd_tps = osd_ticks_per_second(); + m_osd_tps = osd_ticks_per_second(); - err = Pa_Initialize(); + err = Pa_Initialize(); - if (err != paNoError) goto pa_error; + if (err != paNoError) goto pa_error; - stream_params.device = list_get_devidx(options.pa_api(), options.pa_device()); + stream_params.device = list_get_devidx(options.pa_api(), options.pa_device()); - stream_params.channelCount = 2; - stream_params.sampleFormat = paInt16; - stream_params.hostApiSpecificStreamInfo = NULL; + stream_params.channelCount = 2; + stream_params.sampleFormat = paInt16; + stream_params.hostApiSpecificStreamInfo = NULL; - device_info = Pa_GetDeviceInfo(stream_params.device); + device_info = Pa_GetDeviceInfo(stream_params.device); - // 0 = use default - stream_params.suggestedLatency = options.pa_latency() ? options.pa_latency() : device_info->defaultLowOutputLatency; + // 0 = use default + stream_params.suggestedLatency = options.pa_latency() ? options.pa_latency() : device_info->defaultLowOutputLatency; #ifdef WIN32 - PaWasapiStreamInfo wasapi_stream_info; + PaWasapiStreamInfo wasapi_stream_info; - // if requested latency is less than 20 ms, we need to use exclusive mode - if (Pa_GetHostApiInfo(device_info->hostApi)->type == paWASAPI && stream_params.suggestedLatency < 0.020) - { - wasapi_stream_info.size = sizeof(PaWasapiStreamInfo); - wasapi_stream_info.hostApiType = paWASAPI; - wasapi_stream_info.flags = paWinWasapiExclusive; - wasapi_stream_info.version = 1; + // if requested latency is less than 20 ms, we need to use exclusive mode + if (Pa_GetHostApiInfo(device_info->hostApi)->type == paWASAPI && stream_params.suggestedLatency < 0.020) + { + wasapi_stream_info.size = sizeof(PaWasapiStreamInfo); + wasapi_stream_info.hostApiType = paWASAPI; + wasapi_stream_info.flags = paWinWasapiExclusive; + wasapi_stream_info.version = 1; - stream_params.hostApiSpecificStreamInfo = &wasapi_stream_info; + stream_params.hostApiSpecificStreamInfo = &wasapi_stream_info; - // for latencies lower than ~16 ms, we need to use event mode - if (stream_params.suggestedLatency < 0.016) - { - // only way to control output latency with event mode - frames_per_callback = stream_params.suggestedLatency * m_sample_rate; + // for latencies lower than ~16 ms, we need to use event mode + if (stream_params.suggestedLatency < 0.016) + { + // only way to control output latency with event mode + frames_per_callback = stream_params.suggestedLatency * m_sample_rate; - // needed for event mode to work - stream_params.suggestedLatency = 0; - } - } + // needed for event mode to work + stream_params.suggestedLatency = 0; + } + } #endif - err = Pa_OpenStream(&m_pa_stream, - NULL, - &stream_params, - m_sample_rate, - frames_per_callback, - paClipOff, - _callback, - this); + err = Pa_OpenStream(&m_pa_stream, + NULL, + &stream_params, + m_sample_rate, + frames_per_callback, + paClipOff, + _callback, + this); - if (err != paNoError) goto pa_error; + if (err != paNoError) goto pa_error; - stream_info = Pa_GetStreamInfo(m_pa_stream); - api_info = Pa_GetHostApiInfo(device_info->hostApi); + stream_info = Pa_GetStreamInfo(m_pa_stream); + api_info = Pa_GetHostApiInfo(device_info->hostApi); - // in milliseconds - callback_interval = static_cast(stream_info->outputLatency) * 1000.0; + // in milliseconds + callback_interval = static_cast(stream_info->outputLatency) * 1000.0; - // clamp to a probable figure - callback_interval = std::min(callback_interval, 20.0); + // clamp to a probable figure + callback_interval = std::min(callback_interval, 20.0); - // set the best guess callback interval to allowed count, each audio_latency step > 1 adds 20 ms - m_skip_threshold = ((std::max(callback_interval, 10.0) + (m_audio_latency - 1) * 20.0) / 1000.0) * m_sample_rate * 2 + 0.5f; + // set the best guess callback interval to allowed count, each audio_latency step > 1 adds 20 ms + m_skip_threshold = ((std::max(callback_interval, 10.0) + (m_audio_latency - 1) * 20.0) / 1000.0) * m_sample_rate * 2 + 0.5f; - osd_printf_verbose("PortAudio: Using device \"%s\" on API \"%s\"\n", device_info->name, api_info->name); - osd_printf_verbose("PortAudio: Sample rate is %0.0f Hz, device output latency is %0.2f ms\n", - stream_info->sampleRate, stream_info->outputLatency * 1000.0); - osd_printf_verbose("PortAudio: Allowed additional buffering latency is %0.2f ms/%d frames\n", - (m_skip_threshold / 2.0) / (m_sample_rate / 1000.0), m_skip_threshold / 2); + osd_printf_verbose("PortAudio: Using device \"%s\" on API \"%s\"\n", device_info->name, api_info->name); + osd_printf_verbose("PortAudio: Sample rate is %0.0f Hz, device output latency is %0.2f ms\n", + stream_info->sampleRate, stream_info->outputLatency * 1000.0); + osd_printf_verbose("PortAudio: Allowed additional buffering latency is %0.2f ms/%d frames\n", + (m_skip_threshold / 2.0) / (m_sample_rate / 1000.0), m_skip_threshold / 2); - err = Pa_StartStream(m_pa_stream); + err = Pa_StartStream(m_pa_stream); - if (err != paNoError) goto pa_error; + if (err != paNoError) goto pa_error; - return 0; + return 0; pa_error: - delete m_ab; - osd_printf_error("PortAudio error: %s\n", Pa_GetErrorText(err)); - Pa_Terminate(); + delete m_ab; + osd_printf_error("PortAudio error: %s\n", Pa_GetErrorText(err)); + Pa_Terminate(); error: - m_sample_rate = 0; - return -1; + m_sample_rate = 0; + return -1; } PaDeviceIndex sound_pa::list_get_devidx(const char* api_str, const char* device_str) { - PaDeviceIndex selected_devidx = -1; + PaDeviceIndex selected_devidx = -1; - for (PaHostApiIndex api_idx = 0; api_idx < Pa_GetHostApiCount(); api_idx++) - { - const PaHostApiInfo *api_info = Pa_GetHostApiInfo(api_idx); + for (PaHostApiIndex api_idx = 0; api_idx < Pa_GetHostApiCount(); api_idx++) + { + const PaHostApiInfo *api_info = Pa_GetHostApiInfo(api_idx); - osd_printf_verbose("PortAudio: API %s has %d devices\n", api_info->name, api_info->deviceCount); + osd_printf_verbose("PortAudio: API %s has %d devices\n", api_info->name, api_info->deviceCount); - for (int api_devidx = 0; api_devidx < api_info->deviceCount; api_devidx++) - { - PaDeviceIndex devidx = Pa_HostApiDeviceIndexToDeviceIndex(api_idx, api_devidx); - const PaDeviceInfo *device_info = Pa_GetDeviceInfo(devidx); + for (int api_devidx = 0; api_devidx < api_info->deviceCount; api_devidx++) + { + PaDeviceIndex devidx = Pa_HostApiDeviceIndexToDeviceIndex(api_idx, api_devidx); + const PaDeviceInfo *device_info = Pa_GetDeviceInfo(devidx); - // specified API and device is found - if (!strcmp(api_str, api_info->name) && !strcmp(device_str, device_info->name)) - selected_devidx = devidx; + // specified API and device is found + if (!strcmp(api_str, api_info->name) && !strcmp(device_str, device_info->name)) + selected_devidx = devidx; - // if specified device cannot be found, use the default device of the specified API - if (!strcmp(api_str, api_info->name) && api_devidx == api_info->deviceCount - 1 && selected_devidx == -1) - selected_devidx = api_info->defaultOutputDevice; + // if specified device cannot be found, use the default device of the specified API + if (!strcmp(api_str, api_info->name) && api_devidx == api_info->deviceCount - 1 && selected_devidx == -1) + selected_devidx = api_info->defaultOutputDevice; - osd_printf_verbose("PortAudio: %s: \"%s\"%s\n", - api_info->name, device_info->name, api_info->defaultOutputDevice == devidx ? " (default)" : ""); - } - } + osd_printf_verbose("PortAudio: %s: \"%s\"%s\n", + api_info->name, device_info->name, api_info->defaultOutputDevice == devidx ? " (default)" : ""); + } + } - if (selected_devidx < 0) - { - osd_printf_verbose("PortAudio: Unable to find specified API or device or none set, reverting to default\n"); - return Pa_GetDefaultOutputDevice(); - } + if (selected_devidx < 0) + { + osd_printf_verbose("PortAudio: Unable to find specified API or device or none set, reverting to default\n"); + return Pa_GetDefaultOutputDevice(); + } - return selected_devidx; + return selected_devidx; } int sound_pa::callback(s16* output_buffer, size_t number_of_samples) { - int buf_ct = m_ab->count(); + int buf_ct = m_ab->count(); - if (buf_ct >= number_of_samples) - { - m_ab->read(output_buffer, number_of_samples); + if (buf_ct >= number_of_samples) + { + m_ab->read(output_buffer, number_of_samples); - // keep track of the minimum buffer count, skip samples adaptively to respect the audio_latency setting - buf_ct -= number_of_samples; + // keep track of the minimum buffer count, skip samples adaptively to respect the audio_latency setting + buf_ct -= number_of_samples; - if (buf_ct < m_buffer_min_ct) - m_buffer_min_ct = buf_ct; + if (buf_ct < m_buffer_min_ct) + m_buffer_min_ct = buf_ct; - // if we are below the threshold, reset the counter - if (buf_ct < m_skip_threshold) - m_skip_threshold_ticks = m_osd_ticks; + // if we are below the threshold, reset the counter + if (buf_ct < m_skip_threshold) + m_skip_threshold_ticks = m_osd_ticks; - // if we have been above the set threshold for ~1 second, skip forward - if (m_osd_ticks - m_skip_threshold_ticks > m_osd_tps) - { - int adjust = m_buffer_min_ct - m_skip_threshold / 2; + // if we have been above the set threshold for ~1 second, skip forward + if (m_osd_ticks - m_skip_threshold_ticks > m_osd_tps) + { + int adjust = m_buffer_min_ct - m_skip_threshold / 2; - // if adjustment is less than two milliseconds, don't bother - if (adjust / 2 > sample_rate() / 500) { - m_ab->increment_rdpos(adjust); - m_has_overflowed = true; - } + // if adjustment is less than two milliseconds, don't bother + if (adjust / 2 > sample_rate() / 500) { + m_ab->increment_rdpos(adjust); + m_has_overflowed = true; + } - m_skip_threshold_ticks = m_osd_ticks; - m_buffer_min_ct = INT_MAX; - } - } - else - { - m_ab->read(output_buffer, buf_ct); - std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16)); + m_skip_threshold_ticks = m_osd_ticks; + m_buffer_min_ct = INT_MAX; + } + } + else + { + m_ab->read(output_buffer, buf_ct); + std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16)); - // rd_pos == wr_pos only happens when buffer hasn't received any samples, - // i.e. before update_audio_stream has been called - if (m_ab->rd_pos != m_ab->wr_pos) - m_has_underflowed = true; + // rd_pos == wr_pos only happens when buffer hasn't received any samples, + // i.e. before update_audio_stream has been called + if (m_ab->rd_pos != m_ab->wr_pos) + m_has_underflowed = true; - m_skip_threshold_ticks = m_osd_ticks; - } + m_skip_threshold_ticks = m_osd_ticks; + } - return paContinue; + return paContinue; } void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) { - if (!sample_rate()) - return; + if (!sample_rate()) + return; - // for determining buffer overflows, take the sample here instead of in the callback - m_osd_ticks = osd_ticks(); + // for determining buffer overflows, take the sample here instead of in the callback + m_osd_ticks = osd_ticks(); #if LOG_BUFCNT - if (m_log.good()) - m_log << m_ab->count() << std::endl; + if (m_log.good()) + m_log << m_ab->count() << std::endl; #endif - if (m_has_underflowed) - { - m_underflows++; - // add some silence to prevent immediate underflows - m_ab->clear(m_skip_threshold / 4); - m_has_underflowed = false; - } + if (m_has_underflowed) + { + m_underflows++; + // add some silence to prevent immediate underflows + m_ab->clear(m_skip_threshold / 4); + m_has_underflowed = false; + } - if (m_has_overflowed) - { - m_overflows++; - m_has_overflowed = false; - } + if (m_has_overflowed) + { + m_overflows++; + m_has_overflowed = false; + } - m_ab->write(buffer, samples_this_frame * 2, m_attenuation); + m_ab->write(buffer, samples_this_frame * 2, m_attenuation); } void sound_pa::set_mastervolume(int attenuation) { - m_attenuation = attenuation; + m_attenuation = attenuation; } void sound_pa::exit() { - if (!sample_rate()) - return; + if (!sample_rate()) + return; #if LOG_BUFCNT - std::ofstream m_logfile(LOG_FILE); + std::ofstream m_logfile(LOG_FILE); - if (m_log.good() && m_logfile.is_open()) { - m_logfile << m_log.str(); - m_logfile.close(); - } + if (m_log.good() && m_logfile.is_open()) { + m_logfile << m_log.str(); + m_logfile.close(); + } - if (!m_log.good() || m_logfile.fail()) - osd_printf_error("PortAudio: Error writing log.\n"); + if (!m_log.good() || m_logfile.fail()) + osd_printf_error("PortAudio: Error writing log.\n"); #endif - Pa_StopStream(m_pa_stream); - err = Pa_Terminate(); + Pa_StopStream(m_pa_stream); + err = Pa_Terminate(); - if (err != paNoError) - osd_printf_error("PortAudio error: %s\n", Pa_GetErrorText(err)); + if (err != paNoError) + osd_printf_error("PortAudio error: %s\n", Pa_GetErrorText(err)); - delete m_ab; + delete m_ab; - if (m_overflows || m_underflows) - osd_printf_verbose("Sound: overflows=%d underflows=%d\n", m_overflows, m_underflows); + if (m_overflows || m_underflows) + osd_printf_verbose("Sound: overflows=%d underflows=%d\n", m_overflows, m_underflows); } #else - MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") + MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") #endif MODULE_DEFINITION(SOUND_PORTAUDIO, sound_pa)