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…
Reference in New Issue
Block a user