Current section
Files
Jump to
Current section
Files
c_src/xalsa_nif.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <erl_nif.h>
#include <alsa/asoundlib.h>
#define FRAME_TYPE float
#define FRAME_SIZE sizeof(float)
#if FRAME_TYPE == float
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define NATIVE_FORMAT SND_PCM_FORMAT_FLOAT_LE
#else
#define NATIVE_FORMAT SND_PCM_FORMAT_FLOAT_BE
#endif
#else
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define NATIVE_FORMAT SND_PCM_FORMAT_FLOAT64_LE
#else
#define NATIVE_FORMAT SND_PCM_FORMAT_FLOAT64_BE
#endif
#endif
static ErlNifResourceType* res_type;
static int set_hwparams(snd_pcm_t *handle,
unsigned int *rate, unsigned int *channels,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t *period_size){
int err; //, dir;
// unsigned int buffer_time = 10000; /* ring buffer length in us */
// unsigned int period_time = 5000; /* period time in us */
/*
buffer_size = 2 * period_size
rate period_size
44100 224
48000 240
96000 480
192000 960
*/
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_malloc(¶ms);
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 */
err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_NONINTERLEAVED);
if (err < 0) {
printf("Access type not available for playback: %s\n", snd_strerror(err));
return err;
}
/* set the sample format */
err = snd_pcm_hw_params_set_format(handle, params, NATIVE_FORMAT);
if (err < 0) {
printf("Sample format not available for playback: %s\n", snd_strerror(err));
return err;
}
/* 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;
}
/* 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;
}
/* set the buffer time */
/* err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir); */
/* if (err < 0) { */
/* printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err)); */
/* return err; */
/* } */
err = snd_pcm_hw_params_set_buffer_size(handle, params, *buffer_size);
if (err < 0) {
printf("Unable to set buffer size for playback: %s\n", snd_strerror(err));
return err;
}
/* set the period time */
/* err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir); */
/* if (err < 0) { */
/* printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err)); */
/* return err; */
/* } */
err = snd_pcm_hw_params_set_period_size(handle, params, *period_size, 0);
if (err < 0) {
printf("Unable to get period size for playback: %s\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;
}
return 0;
}
static ERL_NIF_TERM pcm_open_handle(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
char device[128]; /* playback device */
int err = 0;
snd_pcm_t** handle_p = enif_alloc_resource(res_type, sizeof(snd_pcm_t*));
// 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, handle_p);
enif_release_resource(handle_p);
return term;
}
static ERL_NIF_TERM pcm_close_handle(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
return enif_make_badarg(env);
}
ret = snd_pcm_close(*handle_p);
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;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
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(*handle_p, 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 ret, channels, rate;
snd_pcm_uframes_t buffer_size, period_size; // ulong
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
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);
}
buffer_size = 2 * period_size;
ret = set_hwparams(*handle_p, &rate, &channels, &buffer_size, &period_size);
if (ret < 0) {
return enif_make_uint(env, ret);
}
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_malloc(&sw_params);
snd_pcm_sw_params_current(*handle_p, sw_params);
snd_pcm_sw_params_set_start_threshold(*handle_p, sw_params,
(buffer_size / period_size) * period_size - 1);
snd_pcm_sw_params_set_avail_min(*handle_p, sw_params, period_size);
snd_pcm_sw_params(*handle_p, 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));
}
static ERL_NIF_TERM pcm_writei(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
unsigned int size; // No of frames (per channel)
int err;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
return enif_make_badarg(env);
}
if (!enif_inspect_binary(env, argv[1], &bin)) {
return enif_make_badarg(env);
}
if (!enif_get_uint(env, argv[2], &size)) {
return enif_make_badarg(env);
}
err = snd_pcm_writei(*handle_p, bin.data, size);
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_writen(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
ERL_NIF_TERM buf_list;
unsigned int size; // No of frames (per channel)
unsigned int channels;
int err;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
return enif_make_badarg(env);
}
buf_list = argv[1];
if(!enif_get_list_length(env, buf_list, &channels)){
return enif_make_badarg(env);
}
void** bufs = enif_alloc(sizeof(void*) * channels);
unsigned int channel = 0;
ERL_NIF_TERM head, tail;
while(channel < channels){
if(!enif_get_list_cell(env, buf_list, &head, &tail)){
return enif_make_badarg(env);
}
if (!enif_inspect_binary(env, head, &bin)) {
return enif_make_badarg(env);
}
bufs[channel] = bin.data;
channel++;
buf_list = tail;
}
if (!enif_get_uint(env, argv[2], &size)) {
return enif_make_badarg(env);
}
err = snd_pcm_writen(*handle_p, bufs, size);
enif_free(bufs);
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_prepare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
return enif_make_badarg(env);
}
return enif_make_int(env, snd_pcm_prepare(*handle_p));
}
static ERL_NIF_TERM pcm_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int err = 0;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void **) &handle_p)){
return enif_make_badarg(env);
}
if (snd_pcm_state(*handle_p) == SND_PCM_STATE_PREPARED) {
err = snd_pcm_start(*handle_p);
}
return enif_make_int(env, err);
}
static ERL_NIF_TERM pcm_recover(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int errno;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void **) &handle_p)){
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(*handle_p, errno, 1));
}
static ERL_NIF_TERM pcm_avail_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
return enif_make_badarg(env);
}
return enif_make_int(env, snd_pcm_avail_update(*handle_p));
}
/* 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;
snd_pcm_t** handle_p;
if (!enif_get_resource(env, argv[0], res_type, (void**) &handle_p)){
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, *handle_p, pcm_async_callback, pid);
return enif_make_int(env, err);
}
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;
ErlNifMapIterator iter;
ErlNifBinary bin;
if (!enif_map_iterator_create(env, argv[0], &iter, ERL_NIF_MAP_ITERATOR_FIRST)) {
return enif_make_badarg(env);
}
if (!enif_get_uint(env, argv[1], &size)) {
return enif_make_badarg(env);
}
unsigned int bytesize = size * FRAME_SIZE;
ERL_NIF_TERM buf_term;
FRAME_TYPE *buf = (FRAME_TYPE *) enif_make_new_binary(env, bytesize, &buf_term);
for(i = 0; i < size; i++)
buf[i] = 0.0;
map_out = enif_make_new_map(env);
FRAME_TYPE* framesp;
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], ¬ify_flag) &&
enif_inspect_binary(env, tuple_array[1], &bin))) {
return enif_make_badarg(env);
}
framesp = (FRAME_TYPE *) bin.data;
noframes = (bin.size / FRAME_SIZE);
for(i = 0; (i < size) && (i < noframes); i++){
buf[i] += framesp[i];
}
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);
return enif_make_tuple2(env, buf_term, map_out);
}
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", 4, pcm_set_params, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"writei", 3, pcm_writei},
{"writen", 3, pcm_writen},
{"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},
{"sum_map", 2, sum_map},
{"float_list_to_binary", 1, float_list_to_binary}
};
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,
NULL, 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);