Current section

Files

Jump to
xalsa c_src xalsa_nif.c
Raw

c_src/xalsa_nif.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <erl_nif.h>
#include <alsa/asoundlib.h>
#ifdef __AVX__
#include <immintrin.h>
#endif
#define INT24_MAX_I 8388607
#define INT24_MIN_I -8388607
#define INT24_MAX_F 8388607.0f
#define INT24_MIN_F -8388607.0f
#define INT16_MAX_I 32767
#define INT16_MIN_I -32767
#define INT16_MAX_F 32767.0f
#define INT16_MIN_F -32767.0f
#define FRAME_TYPE float
#define FRAME_SIZE sizeof(FRAME_TYPE)
static const snd_pcm_format_t formats[] = {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
SND_PCM_FORMAT_FLOAT_LE,
SND_PCM_FORMAT_S32_LE,
SND_PCM_FORMAT_S24_LE,
SND_PCM_FORMAT_S16_LE
#else
SND_PCM_FORMAT_FLOAT_BE,
SND_PCM_FORMAT_S32_BE,
SND_PCM_FORMAT_S24_BE,
SND_PCM_FORMAT_S16_BE
#endif
};
#define NUMFORMATS (sizeof(formats) / sizeof(formats[0]))
typedef struct
{
snd_pcm_t * handle;
unsigned int format_index;
snd_pcm_access_t access;
unsigned int period_size;
unsigned int channels;
FRAME_TYPE** channel_bufs;
int fd;
} xalsa_t;
static ErlNifResourceType* res_type;
static int set_hwparams(xalsa_t * unit,
unsigned int *rate, unsigned int *channels, unsigned int n,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t *period_size){
int err;
/*
rate period_size
44100 256
48000 256
96000 512
192000 1024
*/
snd_pcm_hw_params_t *params;
snd_pcm_t *handle = unit->handle;
snd_pcm_hw_params_malloc(&params);
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) {
printf("Broken configuration for playback: no configurations available: %s\n",
snd_strerror(err));
return err;
}
/* set hardware resampling */
err = snd_pcm_hw_params_set_rate_resample(handle, params, 1);
if (err < 0) {
printf("Resampling setup failed for playback: %s\n", snd_strerror(err));
return err;
}
/* set the interleaved read/write format */
unit->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
err = snd_pcm_hw_params_set_access(handle, params, unit->access);
if (err < 0) {
unit->access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
err = snd_pcm_hw_params_set_access(handle, params, unit->access);
if (err < 0) {
printf("Access type not available for playback: %s\n", snd_strerror(err));
return err;
}
printf("Interleaved access for playback\n");
}
/* set the sample format */
unsigned int format_index = 0;
while((err = snd_pcm_hw_params_set_format(handle, params, formats[format_index]) < 0) &&
format_index < NUMFORMATS) {
format_index++;
}
if (err < 0) {
printf("Sample format not available for playback: %s\n", snd_strerror(err));
return err;
}
printf("Sample format selected for playback: %i\n", format_index);
unit->format_index = format_index;
/* set the count of channels */
err = snd_pcm_hw_params_set_channels_near(handle, params, channels);
if (err < 0) {
printf("Channels count (%i) not available for playbacks: %s\n", *channels, snd_strerror(err));
return err;
}
unit->channels = *channels;
/* set the stream rate */
err = snd_pcm_hw_params_set_rate_near(handle, params, rate, 0);
if (err < 0) {
printf("Rate %iHz not available for playback: %s\n", *rate, snd_strerror(err));
return err;
}
err = snd_pcm_hw_params_set_period_size_near(handle, params, period_size, 0);
if (err < 0) {
printf("Unable to set period size for playback: %s\n", snd_strerror(err));
return err;
}
unit->period_size = *period_size;
*buffer_size = n * (*period_size);
err = snd_pcm_hw_params_set_buffer_size_near(handle, params, buffer_size);
if (err < 0) {
printf("Unable to set buffer size for playback: %s\r\n", snd_strerror(err));
return err;
}
/* write the parameters to device */
err = snd_pcm_hw_params(handle, params);
snd_pcm_hw_params_free(params);
if (err < 0) {
printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
return err;
}
/* Alloc channel buffers */
unit->channel_bufs = enif_alloc(sizeof(FRAME_TYPE*) * unit->channels);
if(unit->channel_bufs == NULL) return -1;
for(unsigned int channel = 0; channel < unit->channels; channel++){
unit->channel_bufs[channel] = enif_alloc(FRAME_SIZE * unit->period_size);
if(unit->channel_bufs[channel] == NULL) return -1;
}
return format_index;
}
static ERL_NIF_TERM pcm_open_handle(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
char device[128]; /* playback device */
int err = 0;
xalsa_t * unit = enif_alloc_resource(res_type, sizeof(xalsa_t));
snd_pcm_t** handle_p = &unit->handle;
// we read the erlang string (erlang list)
if (!enif_get_string(env, argv[0], device, 128, ERL_NIF_LATIN1)) {
return enif_make_badarg(env); // blame user if address isn't a string
}
if ((err = snd_pcm_open(handle_p, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
printf("Playback open error: %s\n", snd_strerror(err));
return enif_make_badarg(env);
}
ERL_NIF_TERM term = enif_make_resource(env, unit);
enif_release_resource(unit);
return term;
}
static ERL_NIF_TERM pcm_close_handle(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
ret = snd_pcm_close(unit->handle);
return enif_make_int(env, ret);
}
static ERL_NIF_TERM pcm_dump(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret = 0;
snd_output_t *output = NULL;
int err;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
err = snd_output_stdio_attach(&output, stdout, 0);
if (err < 0) {
printf("Output failed: %s\n", snd_strerror(err));
return -1;
}
ret = snd_pcm_dump(unit->handle, output);
return enif_make_int(env, ret);
}
static ERL_NIF_TERM pcm_set_params(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
unsigned int channels, rate, buffer_period_size_ratio;
int ret;
snd_pcm_uframes_t buffer_size, period_size; // ulong
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
if (!enif_get_uint(env, argv[1], &channels)) {
return enif_make_badarg(env);
}
if (!enif_get_uint(env, argv[2], &rate)) {
return enif_make_badarg(env);
}
if (!enif_get_ulong(env, argv[3], &period_size)) {
return enif_make_badarg(env);
}
if (!enif_get_uint(env, argv[4], &buffer_period_size_ratio)) {
return enif_make_badarg(env);
}
buffer_size = buffer_period_size_ratio * period_size;
ret = set_hwparams(unit, &rate, &channels,
buffer_period_size_ratio,
&buffer_size,
&period_size);
if (ret < 0) {
return enif_make_uint(env, ret);
}else{
unit->format_index = ret;
}
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_malloc(&sw_params);
snd_pcm_sw_params_current(unit->handle, sw_params);
snd_pcm_sw_params_set_start_threshold(unit->handle, sw_params,
(buffer_size / period_size) * period_size - 1);
// snd_pcm_sw_params_set_start_threshold(unit->handle, sw_params, 0U);
snd_pcm_sw_params_set_avail_min(unit->handle, sw_params, period_size);
snd_pcm_sw_params(unit->handle, sw_params);
snd_pcm_sw_params_free(sw_params);
return enif_make_tuple4(env,
enif_make_uint(env, rate),
enif_make_uint(env, channels),
enif_make_ulong(env, buffer_size),
enif_make_ulong(env, period_size));
}
#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x)<(y)) ? (x) : (y))
/* This function covers both MMAP_INTERLEAVED and MMAP_NONINTERLEAVED cases */
static ERL_NIF_TERM pcm_write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
unsigned int format_index, size; // No of frames (per channel)
unsigned int i, channel;
int err = 0;
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t offset, frames;
snd_pcm_sframes_t avail;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
size = unit->period_size;
format_index = unit->format_index;
avail = snd_pcm_avail_update(unit->handle);
if (avail < size) {
return enif_make_int(env, avail);
}
frames = size;
err = snd_pcm_mmap_begin(unit->handle, &areas, &offset, &frames);
if(err < 0){
// printf("mmap begin error for playback:: %s\n", snd_strerror(err));
return enif_make_int(env, err);
}
if(frames != size) {
return enif_make_int(env, frames);
}
FRAME_TYPE max;
unsigned int step;
unsigned char * mmapp;
FRAME_TYPE * f;
int32_t tmp32;
int16_t tmp16;
FRAME_TYPE tmpf;
for(channel = 0; channel < unit->channels; channel++){
f = unit->channel_bufs[channel];
step = areas[channel].step / 8;
mmapp = (((unsigned char *)areas[channel].addr) + (areas[channel].first / 8));
mmapp += offset * step;
switch(format_index) {
case 0:
for(i = 0; i < frames; i++){
*((FRAME_TYPE *) mmapp) = MAX(MIN(f[i], 1.0), -1.0);
mmapp += step;
}
break;
case 1:
case 2:
max = INT24_MAX_F;
for(i = 0; i < frames; i++){
if(f[i] <= -1.0f)
tmp32 = INT24_MIN_I;
else if(f[i] >= 1.0f)
tmp32 = INT24_MAX_I;
else
tmp32 = (int32_t) (f[i] * max);
if(format_index == 1)
tmp32 = tmp32 << 8;
*((int32_t *) mmapp) = tmp32;
mmapp += step;
}
break;
case 3:
max = INT16_MAX_F;
for(i = 0; i < frames; i++){
tmpf = f[i];
if(tmpf <= -1.0f)
tmp16 = INT16_MIN_I;
else if(tmpf >= 1.0f)
tmp16 = INT16_MAX_I;
else
tmp16 = (int16_t) (tmpf * max);
*((int16_t *) mmapp) = tmp16;
mmapp += step;
}
break;
}
}
err = snd_pcm_mmap_commit(unit->handle, offset, frames);
if(err < 0){
printf("mmap commit error for playback:: %s\n", snd_strerror(err));
}
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
return enif_make_int(env, snd_pcm_prepare(unit->handle));
}
static ERL_NIF_TERM pcm_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int err = 0;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
if (snd_pcm_state(unit->handle) == SND_PCM_STATE_PREPARED) {
err = snd_pcm_start(unit->handle);
}
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_recover(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int errno;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
if (!enif_get_int(env, argv[1], &errno)) {
return enif_make_badarg(env);
}
return enif_make_int(env, snd_pcm_recover(unit->handle, errno, 1));
}
static ERL_NIF_TERM pcm_avail_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
return enif_make_int(env, snd_pcm_avail_update(unit->handle));
}
/* Callback handling - asynchronous notification */
static void pcm_async_callback(snd_async_handler_t *ahandler)
{
ErlNifPid* pid = snd_async_handler_get_callback_private(ahandler);
ErlNifEnv* msg_env = enif_alloc_env();
if(!enif_send(NULL, pid, msg_env, enif_make_atom(msg_env, "pcm_ready4write"))){
printf("xalsa_nif pcm_async_callback: enif_send failed\n");
}
enif_free_env(msg_env);
}
static ERL_NIF_TERM pcm_add_async_handler(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
snd_async_handler_t *ahandler;
int err;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
ErlNifPid* pid = (ErlNifPid*) enif_alloc(sizeof(ErlNifPid));
if (!enif_get_local_pid(env, argv[1], pid)) {
return enif_make_badarg(env);
}
err = snd_async_add_pcm_handler(&ahandler, unit->handle, pcm_async_callback, pid);
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_add_fd(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
struct pollfd *ufds;
int count, err;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
count = snd_pcm_poll_descriptors_count(unit->handle);
if (count != 1) {
printf("Invalid poll descriptors count %i\n", count);
return enif_make_int(env, count);
}
ufds = enif_alloc(sizeof(struct pollfd) * (count + 2));
if ((err = snd_pcm_poll_descriptors(unit->handle, ufds, count)) < 0) {
printf("Unable to obtain poll descriptors for playback: %s\n", snd_strerror(err));
return enif_make_int(env, err);
}
unit->fd = ufds[0].fd;
enif_free(ufds);
return enif_make_int(env, unit->fd);
}
static ERL_NIF_TERM pcm_enable_ready4write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int rv;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
rv = enif_select_write(env,
(ErlNifEvent) unit->fd,
unit, // void* obj,
NULL,
enif_make_atom(env, "pcm_ready4write"),
NULL);
if( rv < 0 ) {
return enif_make_badarg(env);
}
return enif_make_int(env, rv);
}
static ERL_NIF_TERM sum_map(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){
unsigned int i, size; // No of frames
ERL_NIF_TERM key, value, map_out;
const ERL_NIF_TERM * tuple_array, * channel_array;
ERL_NIF_TERM out_array[16]; // Max 16 channels per card
unsigned int channel_arity;
ErlNifMapIterator iter;
ErlNifBinary bin;
xalsa_t * unit;
if (!enif_get_resource(env, argv[0], res_type, (void**) &unit)){
return enif_make_badarg(env);
}
if (
!enif_get_tuple(env, argv[1], (int *) &channel_arity, &channel_array) ||
channel_arity != unit->channels) {
return enif_make_badarg(env);
}
size = unit->period_size;
unsigned int bytesize = size * FRAME_SIZE;
for (unsigned int channel = 0; channel < unit->channels; channel++) {
if (!enif_map_iterator_create(env, channel_array[channel],
&iter, ERL_NIF_MAP_ITERATOR_FIRST))
{
return enif_make_badarg(env);
}
FRAME_TYPE *buf = (FRAME_TYPE *) unit->channel_bufs[channel];
for(i = 0; i < size; i++)
buf[i] = 0.0;
map_out = enif_make_new_map(env);
FRAME_TYPE * framesp, * z;
unsigned int noframes;
int arity, notify_flag;
ERL_NIF_TERM new_binary;
while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
if(!(enif_get_tuple(env, value, &arity, &tuple_array) &&
enif_get_int(env, tuple_array[0], &notify_flag) &&
enif_inspect_binary(env, tuple_array[1], &bin))) {
return enif_make_badarg(env);
}
framesp = (FRAME_TYPE *) bin.data;
z = buf;
noframes = (bin.size / FRAME_SIZE);
#ifdef __AVX__
#define AVXSTEP sizeof(__m256) / FRAME_SIZE // 8
__m256 ax, ay, az;
for(i = 0; (i < size) && (i < noframes); i += AVXSTEP){
ax = _mm256_loadu_ps(framesp);
ay = _mm256_loadu_ps(z);
az = _mm256_add_ps(ax,ay);
_mm256_storeu_ps(z, az);
framesp += AVXSTEP; z += AVXSTEP;
}
#else
for(i = 0; (i < size) && (i < noframes); i++){
buf[i] += framesp[i];
}
#endif
if(i < noframes) { // bin.data larger than size
unsigned int newsize = bin.size - bytesize;
if(notify_flag && newsize <= bytesize){
ErlNifPid pid;
if(enif_get_local_pid(env, key, &pid)) {
ErlNifEnv* msg_env = enif_alloc_env();
enif_send(env, &pid, msg_env, enif_make_atom(msg_env, "ready4more"));
enif_free_env(msg_env);
}
notify_flag = 0;
}
new_binary =
enif_make_sub_binary(env,
tuple_array[1],
bytesize,
newsize);
// insert it into the map along with its label converted as atom
enif_make_map_put(env, map_out,
key,
enif_make_tuple2(env, enif_make_int(env, notify_flag), new_binary),
&map_out);
} else if(notify_flag){
ErlNifPid pid;
if(enif_get_local_pid(env, key, &pid)) {
ErlNifEnv* msg_env = enif_alloc_env();
enif_send(env, &pid, msg_env, enif_make_atom(msg_env, "ready4more"));
enif_free_env(msg_env);
}
}
enif_map_iterator_next(env, &iter);
}
enif_map_iterator_destroy(env, &iter);
out_array[channel] = map_out;
}
return enif_make_tuple_from_array(env, out_array, unit->channels);
}
static ERL_NIF_TERM float_list_to_binary(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){
ERL_NIF_TERM float_list, new_binary, head, tail;
unsigned int list_length, bin_size;
double frame;
FRAME_TYPE * float_data;
float_list = argv[0];
if(!enif_get_list_length(env, float_list, &list_length)){
return enif_make_badarg(env);
}
bin_size = list_length * FRAME_SIZE;
float_data = (FRAME_TYPE *) enif_make_new_binary(env, bin_size, &new_binary);
while(enif_get_list_cell(env, float_list, &head, &tail)){
if(!enif_get_double(env, head, &frame)) {
return enif_make_badarg(env);
}
*float_data++ = (FRAME_TYPE) frame;
float_list = tail;
}
return new_binary;
}
static ErlNifFunc nif_funcs[] = {
{"open_handle", 1, pcm_open_handle, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"close_handle", 1, pcm_close_handle},
{"dump", 1, pcm_dump, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"set_params", 5, pcm_set_params, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"write", 1, pcm_write},
{"prepare", 1, pcm_prepare},
{"start", 1, pcm_start},
{"recover", 2, pcm_recover},
{"avail_update", 1, pcm_avail_update},
{"add_async_handler", 2, pcm_add_async_handler},
{"add_fd", 1, pcm_add_fd},
{"enable_ready4write", 1, pcm_enable_ready4write},
{"sum_map", 2, sum_map},
{"float_list_to_binary", 1, float_list_to_binary}
};
// ErlNifResourceDtor
static void pcm_resource_dtor(ErlNifEnv* env, void * obj){
xalsa_t * unit = (xalsa_t*) obj;
for(unsigned int channel = 0; channel < unit->channels; channel++){
enif_free(unit->channel_bufs[channel]);
}
enif_free(unit->channel_bufs);
}
static int open_pcm_resource_type(ErlNifEnv* env)
{
const char* mod = "xalsa_nif";
const char* resource_type = "pcm";
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
res_type = enif_open_resource_type(env, mod, resource_type,
pcm_resource_dtor, flags, NULL);
return ((res_type == NULL) ? -1:0);
}
static int load(ErlNifEnv* caller_env, void** priv_data, ERL_NIF_TERM load_info)
{
return open_pcm_resource_type(caller_env);
}
static int upgrade(ErlNifEnv* caller_env, void** priv_data, void** old_priv_data,
ERL_NIF_TERM load_info)
{
return open_pcm_resource_type(caller_env);
}
ERL_NIF_INIT(xalsa_pcm, nif_funcs, load, NULL, upgrade, NULL);