195 lines
6.7 KiB
C++
Raw Normal View History

2020-02-08 16:50:48 +01:00
//
// Created by wolverindev on 07.02.20.
//
#include <algorithm>
2020-02-09 13:15:21 +01:00
#include <cmath>
#include "SoundIO.h"
2020-02-08 16:50:48 +01:00
#include "../../logger.h"
using namespace tc::audio;
SoundIOPlayback::SoundIOPlayback(struct ::SoundIoDevice *device) : device_handle{device} {
soundio_device_ref(device);
2020-02-09 13:15:21 +01:00
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:;
}
2020-02-08 16:50:48 +01:00
}
SoundIOPlayback::~SoundIOPlayback() {
soundio_device_unref(this->device_handle);
}
2020-02-09 13:15:21 +01:00
size_t SoundIOPlayback::sample_rate() const {
return this->_sample_rate;
}
2020-02-08 16:50:48 +01:00
bool SoundIOPlayback::impl_start(std::string &error) {
assert(this->device_handle);
2020-02-09 13:15:21 +01:00
//TODO: Figure out how many channels!
this->buffer = soundio_ring_buffer_create(nullptr, (int) (kChunkTime * this->_sample_rate * sizeof(float) * 2)); /* 2 channels */
2020-02-08 16:50:48 +01:00
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;
2020-02-09 13:15:21 +01:00
this->stream->sample_rate = this->_sample_rate;
2020-02-08 16:50:48 +01:00
this->stream->format = SoundIoFormatFloat32LE;
this->stream->software_latency = 0.02;
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);
};
this->stream->error_callback = [](auto str, int err) {
auto handle = reinterpret_cast<SoundIOPlayback*>(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<SoundIOPlayback*>(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)"};
2020-02-09 13:15:21 +01:00
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;
2020-02-08 16:50:48 +01:00
}
if(false && this->stream->layout_error) {
error = std::string{"failed to set audio layout: "} + soundio_strerror(this->stream->layout_error);
2020-02-09 13:15:21 +01:00
goto error_cleanup;
2020-02-08 16:50:48 +01:00
}
if(auto err = soundio_outstream_start(this->stream); err) {
error = soundio_strerror(err) + std::string{" (start)"};
2020-02-09 13:15:21 +01:00
goto error_cleanup;
2020-02-08 16:50:48 +01:00
}
2020-02-09 13:15:21 +01:00
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
2020-02-08 16:50:48 +01:00
//TODO: Test for interleaved channel layout!
return true;
2020-02-09 13:15:21 +01:00
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;
2020-02-08 16:50:48 +01:00
}
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;
2020-02-09 13:15:21 +01:00
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;
}
2020-02-08 16:50:48 +01:00
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;
}
}