Skip to content

Instantly share code, notes, and snippets.

@jpcima
Created November 21, 2016 21:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpcima/96f842a1d78d643e70d4531ac2ef3acc to your computer and use it in GitHub Desktop.
Save jpcima/96f842a1d78d643e70d4531ac2ef3acc to your computer and use it in GitHub Desktop.
#include "main.h"
#include "main-soundio.h"
#include "core/math/utility.h"
#include "core/definitions.h"
#include "utils/logmsg.h"
#include "utils/concat.h"
#include "utils/scope_guard.h"
#include <speex/speex_resampler.h>
#include <algorithm>
#include <memory>
#include <cmath>
#include <cassert>
#undef LOG_TAG
#define LOG_TAG "Audio"
namespace {
SoundIo *soundio {};
SoundIoDevice *sounddev {};
SoundIoOutStream *soundstream {};
SpeexResamplerState *resampler {};
}
unsigned stream_sample_rate {};
unsigned stream_buffer_size {};
#warning XXX test only (to force floating point format)
// typedef float sample_t;
typedef int16_t sample_t;
template <class S> struct sample_traits;
template <> struct sample_traits<int16_t> {
static constexpr SoundIoFormat format = SoundIoFormatS16NE;
};
template <> struct sample_traits<float> {
static constexpr SoundIoFormat format = SoundIoFormatFloat32NE;
};
template <class S> struct resampler_traits;
template <> struct resampler_traits<int16_t> {
static inline int process(
SpeexResamplerState *st, uint32_t ch,
const int16_t *in, uint32_t *inlen, int16_t *out, uint32_t *outlen) {
return speex_resampler_process_int(st, ch, in, inlen, out, outlen);
}
};
template <> struct resampler_traits<float> {
static inline int process(
SpeexResamplerState *st, uint32_t ch,
const float *in, uint32_t *inlen, float *out, uint32_t *outlen) {
return speex_resampler_process_float(st, ch, in, inlen, out, outlen);
}
};
static std::unique_ptr<sample_t[]> rsinputs {};
static unsigned rsinnum {}, rsinidx {};
static void write_callback(SoundIoOutStream *, int, int nframes);
static void process_callback(SoundIoChannelArea *areas, unsigned nframes);
///
void setup_audio() {
bool success = false;
int err {};
scope(exit) {
if (!success)
shutdown_audio();
};
LOGI("Setting up audio output");
soundio = soundio_create();
if (!soundio)
throw std::bad_alloc();
err = soundio_connect(soundio);
if (err != 0)
throw SoundIoException(err);
soundio_flush_events(soundio);
LOGI("The audio backend is %s",
soundio_backend_name(soundio->current_backend));
int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0)
throw std::runtime_error("no output device found");
sounddev = soundio_get_output_device(soundio, default_out_device_index);
if (!sounddev)
throw std::bad_alloc();
soundstream = soundio_outstream_create(sounddev);
const SoundIoChannelLayout *channel_layout =
soundio_channel_layout_get_default(num_output_channels);
if (!channel_layout)
throw std::runtime_error(concat("no available audio layout for ",
num_output_channels, " channels"));
soundstream->layout = *channel_layout;
soundstream->format = sample_traits<sample_t>::format;
soundstream->write_callback = &write_callback;
LOGI("Audio output device: %s", sounddev->name);
// some devices have long default latencies, try to avoid this
const double latency_hint = 5e-3;
soundstream->software_latency = latency_hint;
err = soundio_outstream_open(soundstream);
if (err != 0)
throw SoundIoException(err);
::stream_sample_rate = sounddev->sample_rate_current;
LOGI("Audio sample rate: %u", ::stream_sample_rate);
LOGI("Audio stream format: %s", soundio_format_string(soundstream->format));
double latency = soundstream->software_latency;
assert(latency > 0.0);
// use this value to compute our buffer size
LOGI("Audio software latency: %f", latency);
::stream_buffer_size = nextpow2(
soundstream->software_latency * ::stream_sample_rate);
LOGI("Audio buffer size: %u", ::stream_buffer_size);
#ifndef FIXED_SAMPLE_RATE
::sample_rate = ::stream_sample_rate;
#endif
#ifndef FIXED_BUFFER_SIZE
::buffer_size = ::stream_buffer_size;
#endif
if (soundstream->layout_error)
throw SoundIoException(soundstream->layout_error);
// now buffer size and sample rate are both set
audio_out_init_callback();
rsinputs.reset(new sample_t[buffer_size * num_output_channels]());
rsinnum = 0;
rsinidx = 0;
const int resampler_quality = ::arg_resampler_quality;
resampler = speex_resampler_init(
num_output_channels, ::sample_rate, ::stream_sample_rate,
resampler_quality, &err);
if (!resampler)
throw std::runtime_error("resampler creation failed");
LOGI("Created resampler %u -> %u with quality %d",
::sample_rate, ::stream_sample_rate, resampler_quality);
err = soundio_outstream_start(soundstream);
if (err != 0)
throw SoundIoException(err);
success = true;
}
void shutdown_audio() {
LOGI("Shutting down Audio output");
if (soundstream) soundio_outstream_destroy(soundstream);
if (sounddev) soundio_device_unref(sounddev);
if (soundio) soundio_destroy(soundio);
if (resampler) speex_resampler_destroy(resampler);
}
void write_callback(SoundIoOutStream *, int, int nframes) {
while (nframes > 0) {
int err;
int frames_processed = nframes;
SoundIoChannelArea *areas;
err = soundio_outstream_begin_write(soundstream, &areas, &frames_processed);
if (err != 0) {
LOGE("Audio output error: %s", soundio_strerror(err));
break;
}
process_callback(areas, frames_processed);
err = soundio_outstream_end_write(soundstream);
if (err != 0) {
LOGE("Audio output error: %s", soundio_strerror(err));
break;
}
nframes -= frames_processed;
}
}
void process_callback(SoundIoChannelArea *areas, unsigned nframes) {
sample_t *rsinputs = ::rsinputs.get();
while (nframes > 0) {
if (rsinnum == 0) {
const int16_t *source = audio_out_process_callback();
std::copy(source, source + frame_count * num_output_channels, rsinputs);
#warning XXX test only (conversion int16 to float)
if (std::is_same<sample_t, float>::value)
for (unsigned i = 0; i < frame_count * num_output_channels; ++i)
rsinputs[i] /= INT16_MAX;
rsinnum = frame_count;
rsinidx = 0;
}
unsigned inp_count {};
unsigned out_count {};
for (unsigned c = 0; c < num_output_channels; ++c) {
SoundIoChannelArea &area = areas[c];
unsigned stride = unsigned(area.step) / sizeof(sample_t);
inp_count = rsinnum;
out_count = nframes;
const sample_t *rsin = rsinputs + rsinidx * num_output_channels + c;
sample_t *rsout = (sample_t *)area.ptr;
speex_resampler_set_input_stride(resampler, num_output_channels);
speex_resampler_set_output_stride(resampler, stride);
resampler_traits<sample_t>::process(
resampler, c, rsin, &inp_count, rsout, &out_count);
}
rsinnum -= inp_count;
rsinidx += inp_count;
nframes -= out_count;
for (unsigned c = 0; c < num_output_channels; ++c) {
SoundIoChannelArea &area = areas[c];
unsigned stride = unsigned(area.step) / sizeof(sample_t);
sample_t *ptr = (sample_t *)area.ptr + out_count * stride;
area.ptr = (char *)ptr;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment