// // Created by wolverindev on 07.02.20. // #include #include #include "SoundIO.h" #include "../../logger.h" using namespace tc::audio; SoundIOPlayback::SoundIOPlayback(struct ::SoundIoDevice *device) : device_handle{device} { soundio_device_ref(device); if(device->probe_error || !device->sample_rate_count) this->_sample_rate = kDefaultSampleRate; else { for(const auto& sample_rate : kSampleRateOrder) { for(size_t index{0}; index < device->sample_rate_count; index++) { auto supported_rate = device->sample_rates[index]; if(supported_rate.min <= sample_rate && supported_rate.max >= sample_rate) { this->_sample_rate = sample_rate; goto _found; } } } this->_sample_rate = kDefaultSampleRate; _found:; } } SoundIOPlayback::~SoundIOPlayback() { soundio_device_unref(this->device_handle); } size_t SoundIOPlayback::sample_rate() const { return this->_sample_rate; } bool SoundIOPlayback::impl_start(std::string &error) { assert(this->device_handle); //TODO: Figure out how many channels! this->buffer = soundio_ring_buffer_create(nullptr, (int) (kChunkTime * this->_sample_rate * sizeof(float) * 2)); /* 2 channels */ if(!buffer) { error = "failed to allocate the buffer"; return false; } this->stream = soundio_outstream_create(this->device_handle); if(!this->stream) { error = "out of memory"; return false; } this->stream->userdata = this; this->stream->sample_rate = this->_sample_rate; this->stream->format = SoundIoFormatFloat32LE; this->stream->software_latency = 0.02; 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); }; this->stream->error_callback = [](auto str, int err) { auto handle = reinterpret_cast(str->userdata); log_info(category::audio, tr("Having an error on {}: {}. Aborting playback."), handle->device_handle->id, soundio_strerror(err)); handle->stream_invalid = true; }; this->stream->write_callback = [](struct SoundIoOutStream *str, int frame_count_min, int frame_count_max) { auto handle = reinterpret_cast(str->userdata); handle->write_callback(frame_count_min, frame_count_max); }; if(auto err = soundio_outstream_open(this->stream); err) { error = soundio_strerror(err) + std::string{" (open)"}; goto error_cleanup; } if(this->_sample_rate != this->stream->sample_rate) { error = "sample rate mismatch (" + std::to_string(this->_sample_rate) + " <> " + std::to_string(this->stream->sample_rate) + ")"; goto error_cleanup; } if(false && this->stream->layout_error) { error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error); goto error_cleanup; } if(auto err = soundio_outstream_start(this->stream); err) { error = soundio_strerror(err) + std::string{" (start)"}; 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 //TODO: Test for interleaved channel layout! return true; error_cleanup: if(this->stream) soundio_outstream_destroy(this->stream); this->stream = nullptr; if(this->buffer) soundio_ring_buffer_destroy(this->buffer); this->buffer = nullptr; return false; } void SoundIOPlayback::impl_stop() { if(!this->stream) return; soundio_outstream_destroy(this->stream); this->stream = nullptr; soundio_ring_buffer_destroy(this->buffer); this->buffer = nullptr; } 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; } { 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(latency > max_latency) return; } 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 length = sizeof(float) * frame_count * layout->channel_count; this->fill_buffer(soundio_ring_buffer_write_ptr(this->buffer), frame_count, layout->channel_count); soundio_ring_buffer_advance_write_ptr(this->buffer, length); } 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; } }