Created
November 21, 2016 21:42
-
-
Save jpcima/96f842a1d78d643e70d4531ac2ef3acc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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