From 1c9fb28cce8f6ceeb5a65f7886f2c4a1edf35bd1 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 9 Feb 2020 21:19:11 +0100 Subject: [PATCH] Fixed windows sounds :) --- .../src/audio/AudioOutput.cpp | 15 +- .../src/audio/driver/AudioDriver.cpp | 10 +- .../src/audio/driver/SoundIO.cpp | 1 + .../src/audio/driver/SoundIO.h | 15 +- .../src/audio/driver/SoundIOPlayback.cpp | 238 +++++++++++++----- .../src/connection/audio/VoiceClient.cpp | 4 +- 6 files changed, 214 insertions(+), 69 deletions(-) diff --git a/native/serverconnection/src/audio/AudioOutput.cpp b/native/serverconnection/src/audio/AudioOutput.cpp index 60ee5eb..fc492b3 100644 --- a/native/serverconnection/src/audio/AudioOutput.cpp +++ b/native/serverconnection/src/audio/AudioOutput.cpp @@ -29,12 +29,12 @@ ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { this->buffering = false; if(available_samples >= samples - written) { const auto byte_length = (samples - written) * sizeof(float) * this->channel_count; - memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); + if(buffer)memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); this->buffer.advance_read_ptr(byte_length); return samples; } else { const auto byte_length = available_samples * sizeof(float) * this->channel_count; - memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); + if(buffer)memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length); this->buffer.advance_read_ptr(byte_length); written += available_samples; written_bytes += byte_length; @@ -45,7 +45,7 @@ ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { if(fn(samples - written)) goto load_buffer; - memset(buffer, 0, (samples - written) * sizeof(float) * this->channel_count); + if(buffer)memset(buffer, 0, (samples - written) * sizeof(float) * this->channel_count); this->buffering = true; if(this->on_read) this->on_read(); @@ -179,12 +179,13 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann return; } const auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count; + void* const original_output{output}; if(this->resample_overhead_samples > 0) { const auto samples_to_write = this->resample_overhead_samples > out_frame_count ? out_frame_count : this->resample_overhead_samples; const auto byte_length = samples_to_write * sizeof(float) * channels; - memcpy(output, this->resample_overhead_buffer, byte_length); + if(output) memcpy(output, this->resample_overhead_buffer, byte_length); if(samples_to_write == out_frame_count) { this->resample_overhead_samples -= samples_to_write; memcpy(this->resample_overhead_buffer, (char*) this->resample_overhead_buffer + byte_length, this->resample_overhead_samples * this->_channel_count * sizeof(float)); @@ -196,7 +197,11 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t chann } } - if(this->_volume <= 0) { + if(!original_output) { + for(auto& source : this->_sources) + source->pop_samples(nullptr, local_frame_count); + return; + } else if(this->_volume <= 0) { for(auto& source : this->_sources) source->pop_samples(nullptr, local_frame_count); memset(output, 0, local_frame_count * channels * sizeof(float)); diff --git a/native/serverconnection/src/audio/driver/AudioDriver.cpp b/native/serverconnection/src/audio/driver/AudioDriver.cpp index 3a1499a..8430edf 100644 --- a/native/serverconnection/src/audio/driver/AudioDriver.cpp +++ b/native/serverconnection/src/audio/driver/AudioDriver.cpp @@ -27,7 +27,7 @@ namespace tc::audio { result.insert(result.end(), input_devices.begin(), input_devices.end()); result.insert(result.end(), output_devices.begin(), output_devices.end()); } -#ifdef WIN32 +#if defined(WIN32) && false //Remove all not raw devices. We do not support shared mode yet result.erase(std::remove_if(result.begin(), result.end(), [](const std::shared_ptr& device) { if(device->driver() != "WASAPI") return false; @@ -161,10 +161,16 @@ namespace tc::audio { this->_sources.erase(index); } -#define TMP_BUFFER_SIZE 8096 +#define TMP_BUFFER_SIZE (4096 * 16) /* 64k */ void AudioDevicePlayback::fill_buffer(void *buffer, size_t samples, size_t channels) { std::lock_guard lock{this->source_lock}; + if(!buffer) { + for(auto& source : this->_sources) + source->fill_buffer(nullptr, samples, channels); + return; + } + const auto size = this->_sources.size(); if(size == 1) { this->_sources.front()->fill_buffer(buffer, samples, channels); diff --git a/native/serverconnection/src/audio/driver/SoundIO.cpp b/native/serverconnection/src/audio/driver/SoundIO.cpp index d4ee536..9ab9bc9 100644 --- a/native/serverconnection/src/audio/driver/SoundIO.cpp +++ b/native/serverconnection/src/audio/driver/SoundIO.cpp @@ -211,6 +211,7 @@ SoundIODevice::SoundIODevice(struct ::SoundIoDevice *dev, std::string driver, bo if(this->device_handle->is_raw) { this->_device_id = std::string{dev->id} + "_raw"; + this->driver_name += " Raw"; } else { this->_device_id = dev->id; } diff --git a/native/serverconnection/src/audio/driver/SoundIO.h b/native/serverconnection/src/audio/driver/SoundIO.h index de2548b..432d3af 100644 --- a/native/serverconnection/src/audio/driver/SoundIO.h +++ b/native/serverconnection/src/audio/driver/SoundIO.h @@ -31,7 +31,7 @@ namespace tc::audio { class SoundIOPlayback : public AudioDevicePlayback { public: - constexpr static auto kChunkTime{0.01}; + constexpr static auto kChunkTime{0.005}; explicit SoundIOPlayback(struct ::SoundIoDevice* /* handle */); virtual ~SoundIOPlayback(); @@ -50,6 +50,19 @@ namespace tc::audio { struct ::SoundIoRingBuffer* buffer{nullptr}; +#ifdef WIN32 + std::mutex write_mutex{}; + std::condition_variable write_cv{}; + bool write_exit{false}; + + std::chrono::system_clock::time_point next_write{}; + + std::chrono::system_clock::time_point last_stats{}; + size_t samples{0}; + + bool priority_boost{false}; +#endif + void write_callback(int frame_count_min, int frame_count_max); }; diff --git a/native/serverconnection/src/audio/driver/SoundIOPlayback.cpp b/native/serverconnection/src/audio/driver/SoundIOPlayback.cpp index f717a16..b21b81d 100644 --- a/native/serverconnection/src/audio/driver/SoundIOPlayback.cpp +++ b/native/serverconnection/src/audio/driver/SoundIOPlayback.cpp @@ -59,6 +59,7 @@ bool SoundIOPlayback::impl_start(std::string &error) { this->stream->format = SoundIoFormatFloat32LE; this->stream->software_latency = 0.02; + log_info(category::audio, tr("Open device with: {}"), this->_sample_rate); this->stream->underflow_callback = [](auto str) { auto handle = reinterpret_cast(str->userdata); log_info(category::audio, tr("Having an underflow on {}"), handle->device_handle->id); @@ -75,6 +76,9 @@ bool SoundIOPlayback::impl_start(std::string &error) { handle->write_callback(frame_count_min, frame_count_max); }; +#ifdef WIN32 + this->next_write = std::chrono::system_clock::now(); +#endif if(auto err = soundio_outstream_open(this->stream); err) { error = soundio_strerror(err) + std::string{" (open)"}; goto error_cleanup; @@ -95,13 +99,16 @@ bool SoundIOPlayback::impl_start(std::string &error) { goto error_cleanup; } - soundio_outstream_wasapi_set_sleep_divider(this->stream,100); // Play one quarter and then request fill again so we've 3 quarters to fill up again +#ifdef WIN32 + this->priority_boost = false; + if(!this->device_handle->is_raw) + soundio_outstream_wasapi_set_sleep_divider(this->stream,0); /* basically while true loop */ +#endif //TODO: Test for interleaved channel layout! return true; error_cleanup: - if(this->stream) soundio_outstream_destroy(this->stream); this->stream = nullptr; @@ -113,6 +120,13 @@ bool SoundIOPlayback::impl_start(std::string &error) { void SoundIOPlayback::impl_stop() { if(!this->stream) return; +#ifdef WIN32 + { /* exit the endless write loop when we're not in raw mode */ + std::lock_guard write_lock{this->write_mutex}; + this->write_exit = true; + this->write_cv.notify_all(); + } +#endif soundio_outstream_destroy(this->stream); this->stream = nullptr; @@ -120,80 +134,186 @@ void SoundIOPlayback::impl_stop() { this->buffer = nullptr; } +typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); void SoundIOPlayback::write_callback(int frame_count_min, int frame_count_max) { const struct SoundIoChannelLayout *layout = &this->stream->layout; - struct SoundIoChannelArea *areas; - int frames_left{frame_count_min}, err; - - const auto min_interval = this->have_underflow ? 0.02 : 0.01; - const auto max_latency = 0.02; - - - { - const auto _min_interval_frames = (int) (min_interval * this->stream->sample_rate + .5); - - if(frames_left < _min_interval_frames) - frames_left = _min_interval_frames; - if(frames_left > frame_count_max) - frames_left = frame_count_max; - if(frame_count_max == 0) return; - } #ifdef WIN32 - //TODO: Test for WASAPI & Shared mode - { - double latency{}; - if(auto err = soundio_outstream_get_latency(this->stream, &latency); err) { - log_warn(category::audio, tr("Failed to get auto stream latency: {}"), err); - return; + if(!this->priority_boost) { + this->priority_boost = true; + + // Attempt to assign "Pro Audio" characteristic to thread + HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + if ( AvrtDll ) { + DWORD taskIndex = 0; + + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = + ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); + FreeLibrary( AvrtDll ); } - if(latency > max_latency) return; } -#endif - while(frames_left > 0) { - int frame_count{frames_left}; - auto buffered = soundio_ring_buffer_fill_count(this->buffer) / (sizeof(float) * layout->channel_count); - if(frame_count > buffered) { - if(buffered == 0) { - const auto fill_sample_count = (soundio_ring_buffer_free_count(this->buffer) / sizeof(float) / 2); - this->fill_buffer(soundio_ring_buffer_write_ptr(this->buffer), fill_sample_count, layout->channel_count); - soundio_ring_buffer_advance_write_ptr(this->buffer, fill_sample_count * sizeof(float) * 2); - buffered += fill_sample_count; - } else - frame_count = buffered; - } - if((err = soundio_outstream_begin_write(this->stream, &areas, &frame_count))) { - log_warn(category::audio, tr("Failed to begin a write to the soundio buffer: {}"), err); - return; - } + /* shared windows devices */ + if(!this->device_handle->is_raw) { + constexpr std::chrono::milliseconds jitter_ms{5}; + constexpr std::chrono::milliseconds kChunkMillis{(int64_t) (kChunkTime * 1000)}; - /* test for interleaved */ + /* wait until the last stuff has been written */ + double latency{}; { - char* begin = areas[0].ptr - sizeof(float); - for(size_t channel{0}; channel < layout->channel_count; channel++) { - if((begin += sizeof(float)) != areas[channel].ptr) { - log_error(category::audio, tr("Expected interleaved buffer, which it isn't")); - return; - } + if(auto err = soundio_outstream_get_latency(this->stream, &latency); err) { + log_warn(category::audio, tr("Failed to get auto stream latency: {}"), err); + return; + } - if(areas[channel].step != sizeof(float) * layout->channel_count) { - log_error(category::audio, tr("Invalid step size for channel {}"), channel); - return; + std::chrono::microseconds software_latency{(int64_t) (this->device_handle->software_latency_current * 1e6)}; // latency for shared audio device + std::chrono::microseconds buffered_duration{(int64_t) (latency * 1e6)}; + + std::unique_lock cv_lock{this->write_mutex}; + auto now = std::chrono::system_clock::now(); + if(buffered_duration - jitter_ms > software_latency) { + auto sleep_target = next_write - jitter_ms; + if(sleep_target > now) { + this->write_cv.wait_until(cv_lock, sleep_target); + if(auto err = soundio_outstream_get_latency(this->stream, &latency); err) { + log_warn(category::audio, tr("Failed to get auto stream latency: {}"), err); + return; + } } + } else { + this->next_write = now - kChunkMillis; /* insert that chunk */ + } + if(this->write_exit) + return; + } + + auto now = std::chrono::system_clock::now(); + auto overshoot = std::chrono::floor(now - next_write).count(); + next_write = now + kChunkMillis; + + if(last_stats + std::chrono::seconds{1} < now) { + last_stats = now; + log_info(category::audio, tr("Samples: {}, lat: {}"), samples, latency); + samples = 0; + } + + double time_to_write{overshoot / 1000.0 + kChunkTime}; + bool drop_buffer{false}; + { + const auto managed_latency = latency - this->device_handle->software_latency_current; + if(managed_latency > 0.08) { + drop_buffer = true; + time_to_write = managed_latency * 1000 - 10; } } - const auto length = sizeof(float) * frame_count * layout->channel_count; - memcpy(areas[0].ptr, soundio_ring_buffer_read_ptr(this->buffer), length); - soundio_ring_buffer_advance_read_ptr(this->buffer, length); + if(!drop_buffer) { + auto frames_to_write = (int) (this->_sample_rate * time_to_write); + if(frames_to_write <= 0) return; - if((err = soundio_outstream_end_write(this->stream))) { - log_warn(category::audio, tr("Failed to end a write to the soundio buffer: {}"), err); - return; + if(frames_to_write > frame_count_max) { + log_warn(category::audio, tr("Supposed write chunk size is larger that supported max frame count. Reducing write chunk size.")); + frames_to_write = frame_count_max; + } + + int frame_count{frames_to_write}; + if(auto err = soundio_outstream_begin_write(this->stream, &areas, &frame_count); err) { + log_warn(category::audio, tr("Failed to begin a write to the soundio buffer: {}"), err); + return; + } + + if(frame_count != frames_to_write) + log_warn(category::audio, tr("Allowed to write is not equal to the supposed value.")); + + /* test for interleaved */ + { + char* begin = areas[0].ptr - sizeof(float); + for(size_t channel{0}; channel < layout->channel_count; channel++) { + if((begin += sizeof(float)) != areas[channel].ptr) { + log_error(category::audio, tr("Expected interleaved buffer, which it isn't")); + return; + } + + if(areas[channel].step != sizeof(float) * layout->channel_count) { + log_error(category::audio, tr("Invalid step size for channel {}"), channel); + return; + } + } + } + + samples += frame_count; + this->fill_buffer(areas[0].ptr, frame_count, layout->channel_count); + if(auto err = soundio_outstream_end_write(this->stream); err) { + log_warn(category::audio, tr("Failed to end a write to the soundio buffer: {}"), err); + return; + } + } else { + this->fill_buffer(nullptr, (int) (this->_sample_rate * time_to_write), layout->channel_count); + } + } else +#endif + { + int frames_left{frame_count_min}, err; + + /* time in second how much we want to fill the buffer */ + const auto min_interval = this->have_underflow ? 0.02 : 0.01; + + + { + const auto _min_interval_frames = (int) (min_interval * this->stream->sample_rate + .5); + + if(frames_left < _min_interval_frames) + frames_left = _min_interval_frames; + if(frames_left > frame_count_max) + frames_left = frame_count_max; + if(frame_count_max == 0) return; } - frames_left -= frame_count; + while(frames_left > 0) { + int frame_count{frames_left}; + auto buffered = soundio_ring_buffer_fill_count(this->buffer) / (sizeof(float) * layout->channel_count); + if(frame_count > buffered) { + if(buffered == 0) { + const auto fill_sample_count = (soundio_ring_buffer_free_count(this->buffer) / sizeof(float) / 2); + this->fill_buffer(soundio_ring_buffer_write_ptr(this->buffer), fill_sample_count, layout->channel_count); + soundio_ring_buffer_advance_write_ptr(this->buffer, fill_sample_count * sizeof(float) * 2); + buffered += fill_sample_count; + } else + frame_count = buffered; + } + if((err = soundio_outstream_begin_write(this->stream, &areas, &frame_count))) { + log_warn(category::audio, tr("Failed to begin a write to the soundio buffer: {}"), err); + return; + } + + /* test for interleaved */ + { + char* begin = areas[0].ptr - sizeof(float); + for(size_t channel{0}; channel < layout->channel_count; channel++) { + if((begin += sizeof(float)) != areas[channel].ptr) { + log_error(category::audio, tr("Expected interleaved buffer, which it isn't")); + return; + } + + if(areas[channel].step != sizeof(float) * layout->channel_count) { + log_error(category::audio, tr("Invalid step size for channel {}"), channel); + return; + } + } + } + + const auto length = sizeof(float) * frame_count * layout->channel_count; + memcpy(areas[0].ptr, soundio_ring_buffer_read_ptr(this->buffer), length); + soundio_ring_buffer_advance_read_ptr(this->buffer, length); + + if((err = soundio_outstream_end_write(this->stream))) { + log_warn(category::audio, tr("Failed to end a write to the soundio buffer: {}"), err); + return; + } + + frames_left -= frame_count; + } } } \ No newline at end of file diff --git a/native/serverconnection/src/connection/audio/VoiceClient.cpp b/native/serverconnection/src/connection/audio/VoiceClient.cpp index 57dfffd..09dc4bc 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.cpp +++ b/native/serverconnection/src/connection/audio/VoiceClient.cpp @@ -259,8 +259,8 @@ void VoiceClient::initialize() { return false; }; - client->output_source->on_overflow = [&](size_t count){ - log_warn(category::audio, tr("Client {} has a audio buffer overflow of {}."), client->_client_id, count); + client->output_source->on_overflow = [client_ptr](size_t count){ + log_warn(category::audio, tr("Client {} has a audio buffer overflow of {}."), client_ptr->_client_id, count); }; }); }