Fixed windows sounds :)
This commit is contained in:
		
							parent
							
								
									bb1afc12d2
								
							
						
					
					
						commit
						1c9fb28cce
					
				| @ -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)); | ||||
|  | ||||
| @ -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<AudioDevice>& 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); | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|  | ||||
| @ -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); | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -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<SoundIOPlayback*>(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<std::chrono::milliseconds>(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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|         }; | ||||
|     }); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user