Skip to content

Instantly share code, notes, and snippets.

@asonge
Last active August 29, 2015 14:13
Show Gist options
  • Save asonge/cc1d528efd2407b8dfb3 to your computer and use it in GitHub Desktop.
Save asonge/cc1d528efd2407b8dfb3 to your computer and use it in GitHub Desktop.
#include "erl_nif.h"
#include "inttypes.h"
#include "string.h"
#include "stdint.h"
#include "stdio.h"
#include "portaudio.h"
#include "erlaudio_drv.h"
#include "assert.h"
static void erlaudio_ringbuf_init(struct erlaudio_ringbuf *buf, size_t length) {
// printf("Initing for %zu\n", length);
buf->length = length;
buf->head = 0;
buf->tail = 0;
buf->data = enif_alloc(sizeof(char) * buf->length);
}
static void erlaudio_ringbuf_destroy(struct erlaudio_ringbuf *buf) {
if(buf!=NULL && buf->length && buf->data != NULL) {
enif_free(buf->data);
buf->length = 0;
buf->data = NULL;
}
}
static size_t erlaudio_ringbuf_avail_space(struct erlaudio_ringbuf *buf) {
if(buf->head > buf->tail) return buf->head - buf->tail - 1;
if(buf->head == buf->tail) return buf->length - 1;
return buf->length - 1 - (buf->head - buf->tail);
}
static size_t erlaudio_ringbuf_avail_data(struct erlaudio_ringbuf *buf) {
if(buf->tail > buf->head) return buf->tail - buf->head;
if(buf->head == buf->tail) return 0;
return buf->length - (buf->head - buf->tail);
}
static void erlaudio_ringbuf_write(struct erlaudio_ringbuf *buf, unsigned char *data, int length) {
assert(length <= erlaudio_ringbuf_avail_space(buf));
size_t tail_write = 0;
size_t tail = buf->tail;
if(buf->tail + length > buf->length) {
tail_write = buf->length - buf->tail;
memcpy((void *) &buf->data[buf->tail], data, tail_write);
tail = 0;
}
memcpy((void *) &buf->data[tail], &data[tail_write], length - tail_write);
buf->tail = (buf->tail + length) % buf->length;
}
static struct erlaudio_stream_handle* erlaudio_stream_handle_alloc() {
struct erlaudio_stream_handle* handle = enif_alloc_resource(
ERLAUDIO_STREAM_RESOURCE,
sizeof(struct erlaudio_stream_handle));
handle->owner_pid = (ErlNifPid *) enif_alloc(sizeof(ErlNifPid));
handle->reader_pid = (ErlNifPid *) enif_alloc(sizeof(ErlNifPid));
handle->input = NULL;
handle->output = NULL;
handle->thread_opts = NULL;
handle->pa = NULL;
handle->output_buf = enif_alloc(sizeof(struct erlaudio_ringbuf));
return handle;
}
static void erlaudio_stream_resource_cleanup(ErlNifEnv* env, void* arg) {
printf("Resource cleanup...\n");
struct erlaudio_stream_handle* handle = (struct erlaudio_stream_handle*) arg;
if(handle->pa) Pa_CloseStream(handle->pa);
if(handle->owner_pid) enif_free(handle->owner_pid);
if(handle->reader_pid) enif_free(handle->reader_pid);
if(handle->thread_opts) enif_thread_opts_destroy(handle->thread_opts);
if(handle->output_buf) {
printf("DESTROY ZEE RING BUFFUR DATA!\n");
erlaudio_ringbuf_destroy(handle->output_buf);
printf("DESTROY ZEE RING BUFFUR!\n");
enif_free(handle->output_buf);
}
}
static int get_stream_handle(ErlNifEnv* env,
ERL_NIF_TERM item,
struct erlaudio_stream_handle** handle)
{
return enif_get_resource(env, item, ERLAUDIO_STREAM_RESOURCE, (void **) handle);
}
/**
* Return an error message from the list
*/
const char* pa_error_to_char(int err) {
struct err_to_str* cur = &pa_errors[0];
while(cur->str != 0) {
if(cur->num == err) {
return cur->str;
}
cur++;
}
return "invalid_error";
}
/**
* Turn a PA call into an error if there's an error.
*/
static ERL_NIF_TERM pa_error_to_error_tuple(ErlNifEnv *env, int err) {
return enif_make_tuple2(env,
enif_make_atom(env, "error"),
enif_make_atom(env, pa_error_to_char(err))
);
}
/**
* Possibly send a PA call to a mailbox as an error, return 0 on fatal error, 1 if good
*/
static int pa_error_message(void *resource, int err, ErlNifPid* pid) {
if(err != paNoError) {
ErlNifEnv* env = enif_alloc_env();
enif_send(NULL, pid, env, enif_make_tuple3(env,
enif_make_atom(env, "erlaudio_error"),
enif_make_resource(env, resource),
enif_make_atom(env, pa_error_to_char(err))
));
enif_free_env(env);
}
return err == paNoError || err == paInputOverflowed || err == paOutputUnderflowed;
}
/**
* Get sample size from atom
*/
static PaSampleFormat atom_to_sample_format(ErlNifEnv *env, ERL_NIF_TERM sample_atom) {
struct int_to_str* cur = &pa_types[0];
while(cur->num != 0) {
if(enif_compare(sample_atom, enif_make_atom(env, cur->str))==0) {
return cur->num;
}
cur++;
}
return -1;
}
/**
* Turn a stream flag atom into its value
*/
static int atom_to_stream_flags(ErlNifEnv *env, ERL_NIF_TERM atom) {
struct sflags_to_str* cur = &pa_flags[0];
while(cur->num != 0) {
if(enif_compare(atom, enif_make_atom(env, cur->str))==0) {
return cur->num;
}
cur++;
}
return -1;
}
/**
* Get stream flags from a list of atoms
*/
static int list_to_stream_flags(ErlNifEnv *env, ERL_NIF_TERM list, PaStreamFlags *flags) {
*flags = paNoFlag;
int flag = paNoFlag;
unsigned length = 0;
ERL_NIF_TERM atom;
if(!enif_get_list_length(env, list, &length)) {
return 0;
}
if(length==0) {
return 1;
}
while(enif_get_list_cell(env, list, &atom, &list)) {
flag = atom_to_stream_flags(env, atom);
if(flag >= 0) {
*flags |= flag;
} else {
return 0;
}
}
return 1;
}
/**
* Convert the stream params record (tuple) to PaStreamParameters
*/
static int
convert_tuple_to_stream_params(ErlNifEnv* env, ERL_NIF_TERM term, PaStreamParameters **stream_params) {
int arity, device, channelCount;
double suggestedLatency;
const ERL_NIF_TERM *tuple;
PaStreamParameters *params;
if(enif_compare(term, enif_make_atom(env, "undefined"))==0
|| enif_compare(term, enif_make_atom(env, "none"))==0
|| enif_compare(term, enif_make_atom(env, "null"))==0)
{
return 1;
}
if(!enif_get_tuple(env, term, &arity, &tuple)
|| arity!=5
|| enif_compare(tuple[0], enif_make_atom(env, "erlaudio_device_params"))!=0
|| !enif_get_int(env, tuple[1], &device)
|| !enif_get_int(env, tuple[2], &channelCount)
|| !enif_is_atom(env, tuple[3])
|| !enif_get_double(env, tuple[4], &suggestedLatency)
|| device < 0
|| device >= Pa_GetDeviceCount()
|| channelCount < 0
) {
return 0;
}
params = enif_alloc(sizeof(PaStreamParameters));
params->device = device;
params->channelCount = channelCount;
params->sampleFormat = atom_to_sample_format(env, tuple[3]);
params->suggestedLatency = suggestedLatency;
params->hostApiSpecificStreamInfo = NULL;
*stream_params = params;
return 1;
}
static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
ErlNifResourceType* rt = enif_open_resource_type(env, NULL,
"ERLAUDIO_STREAM_RESOURCE",
&erlaudio_stream_resource_cleanup,
flags, NULL);
if (rt == NULL) return -1;
ERLAUDIO_STREAM_RESOURCE = rt;
return Pa_Initialize();
}
static void on_unload(ErlNifEnv* env, void* priv_data) {
Pa_Terminate();
}
ERL_NIF_INIT(erlaudio_drv, nif_funcs, &on_load, NULL, NULL, &on_unload);
/**
* The callback of the main loop of the processing thread
*/
static int erlaudio_thread_stream_exec(struct erlaudio_stream_handle* h) {
signed long frames_available;
signed long bytes_available;
size_t bytes_to_write_available;
struct erlaudio_ringbuf* buf = h->output_buf;
size_t head_read = 0;
size_t head = buf->head;
ErlNifEnv* env;
int err;
ErlNifBinary input_bin;
if(h->input) {
// printf("I");
if ((frames_available = Pa_GetStreamReadAvailable(h->pa)) > 0) {
// printf("i");
// If input is enabled, check for new input capture
ErlNifBinary input_bin;
bytes_available = frames_available * h->input_frame_size;
if(!enif_alloc_binary(bytes_available, &input_bin)) {
// We have to alloc a new thing for the thing.
return -1;
}
err = Pa_ReadStream(h->pa, input_bin.data, frames_available);
if(err==paNoError) {
env = enif_alloc_env();
enif_send(NULL, h->reader_pid, env, enif_make_tuple3(env,
enif_make_atom(env, "erlaudio_pcmdata"),
enif_make_resource(env, h),
enif_make_binary(env, &input_bin)
));
enif_free_env(env);
enif_release_binary(&input_bin);
} else {
enif_release_binary(&input_bin);
// printf("\nRead Error %d\n", err);
// Tell the reader we've had an error
pa_error_message(h, err, h->reader_pid);
// Tell the owner if the owner is someone else
if(h->reader_pid != h->owner_pid) {
pa_error_message(h, err, h->owner_pid);
}
return -1;
}
}
if(frames_available < 0) {
// printf("\nRead Error2 %d\n", err);
pa_error_message(h, frames_available, h->owner_pid);
}
}
if(h->output) {
// printf("O");
// Make sure we have data to write as well)
frames_available = Pa_GetStreamWriteAvailable(h->pa);
if(frames_available < 0) {
pa_error_message(h, frames_available, h->owner_pid);
return -1;
}
bytes_to_write_available = erlaudio_ringbuf_avail_data(buf);
while(bytes_to_write_available > 0 && frames_available > 0) {
bytes_available = frames_available * h->output_frame_size;
// printf("o");
// printf("o: %zu %ld", bytes_to_write_available, bytes_available);
bytes_to_write_available = bytes_to_write_available > bytes_available ? bytes_available : bytes_to_write_available;
// printf(" %zu\n", bytes_to_write_available);
// printf("Settings: %zu %zu\n", buf->head, buf->tail);
// assert(bytes_to_write_available <= erlaudio_ringbuf_avail_data(buf));
head_read = 0;
head = buf->head;
if(buf->head + bytes_to_write_available > buf->length) {
head_read = buf->length - buf->head;
err = Pa_WriteStream(h->pa, &buf->data[head], head_read / h->output_frame_size);
assert(head_read % h->output_frame_size == 0);
if(err!=paNoError) {
pa_error_message(h, err, h->owner_pid);
return -1;
}
head = 0;
}
err = Pa_WriteStream(h->pa, &buf->data[head], (bytes_to_write_available - head_read) / h->output_frame_size);
if(err!=paNoError) {
pa_error_message(h, err, h->owner_pid);
return -1;
}
buf->head = (buf->head + bytes_to_write_available) % buf->length;
// printf("Settings: %zu %zu\n", buf->head, buf->tail);
bytes_to_write_available = erlaudio_ringbuf_avail_data(buf);
frames_available = Pa_GetStreamWriteAvailable(h->pa);
}
}
if(h->input) {
// printf("I");
if ((frames_available = Pa_GetStreamReadAvailable(h->pa)) > 0) {
// printf("i");
// If input is enabled, check for new input capture
bytes_available = frames_available * h->input_frame_size;
if(!enif_alloc_binary(bytes_available, &input_bin)) {
// We have to alloc a new thing for the thing.
return -1;
}
err = Pa_ReadStream(h->pa, input_bin.data, frames_available);
if(err==paNoError) {
env = enif_alloc_env();
enif_send(NULL, h->reader_pid, env, enif_make_tuple3(env,
enif_make_atom(env, "erlaudio_pcmdata"),
enif_make_resource(env, h),
enif_make_binary(env, &input_bin)
));
enif_free_env(env);
enif_release_binary(&input_bin);
} else {
enif_release_binary(&input_bin);
// printf("\nRead Error %d\n", err);
// Tell the reader we've had an error
pa_error_message(h, err, h->reader_pid);
// Tell the owner if the owner is someone else
if(h->reader_pid != h->owner_pid) {
pa_error_message(h, err, h->owner_pid);
}
return -1;
}
}
if(frames_available < 0) {
// printf("\nRead Error2 %d\n", err);
pa_error_message(h, frames_available, h->owner_pid);
}
}
return 1;
}
/**
* The main thread loop
*/
static void* erlaudio_thread_stream_main(void* data) {
struct erlaudio_stream_handle* h = (struct erlaudio_stream_handle*) data;
// const struct PaStreamInfo *info = Pa_GetStreamInfo(h->pa);
PaTime to_sleep;
int loop = 1;
int err;
PaTime time1, time2;
// if(h->output && h->input) {
// to_sleep = (info->inputLatency < info->outputLatency ? info->inputLatency : info->outputLatency) * 1000;
// } else if(h->input) {
// to_sleep = info->inputLatency*100;
// } else if(h->output) {
// to_sleep = info->outputLatency*100;
// } else {
// to_sleep = 100;
// }
to_sleep = 10;
err = Pa_StartStream(h->pa);
if(err!=paNoError) {
return NULL;
}
while(loop && h->pa!=NULL && Pa_IsStreamActive(h->pa)) {
time1 = Pa_GetStreamTime(h->pa);
loop = erlaudio_thread_stream_exec(h) > 0;
time2 = Pa_GetStreamTime(h->pa);
// Pa_Sleep((long) (to_sleep - (time2 - time1) * 100));
// if(to_sleep > (time2 - time1) * 1000) {
// Pa_Sleep((long) (to_sleep - (time2 - time1) * 100));
// }
Pa_Sleep(10);
}
return NULL;
}
/**
* Get the portaudio version
*/
static ERL_NIF_TERM erlaudio_get_pa_version(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
const char* version = Pa_GetVersionText();
ERL_NIF_TERM binary_term;
char* name_raw = (char *) enif_make_new_binary(env, strlen(version), &binary_term);
strcpy(name_raw, version);
return enif_make_tuple2(env,
enif_make_int(env, Pa_GetVersion()),
binary_term
);
}
/**
* Get the hostapi info record/tuple
*/
static ERL_NIF_TERM erlaudio_get_hostapi_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
const PaHostApiInfo* info;
int index;
if(argc!=1 || !enif_get_int(env, argv[0], &index)) {
return enif_make_badarg(env);
}
info = Pa_GetHostApiInfo(index);
ERL_NIF_TERM name;
char *name_raw = (char *) enif_make_new_binary(env, strlen(info->name), &name);
strcpy(name_raw, info->name);
return enif_make_tuple6(env,
enif_make_atom(env, "erlaudio_hostapi_info"),
enif_make_int(env, info->type),
name,
enif_make_int(env, info->deviceCount),
enif_make_int(env, info->defaultInputDevice),
enif_make_int(env, info->defaultOutputDevice)
);
}
/**
* Get the default hostapi index
*/
static ERL_NIF_TERM erlaudio_get_default_hostapi_index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
return enif_make_int(env, Pa_GetDefaultHostApi());
}
/**
* Get number of hostapi's available
*/
static ERL_NIF_TERM erlaudio_get_hostapi_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
return enif_make_int(env, Pa_GetHostApiCount());
}
/**
* Get hostapi index from the type
*/
static ERL_NIF_TERM erlaudio_get_hostapi_index_from_type(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int type = -1;
if(argc!=1) {
return enif_make_badarg(env);
}
if(enif_is_atom(env, argv[0])) {
struct api_to_str cur = pa_drivers[0];
while(cur.str != 0) {
if(enif_compare(enif_make_atom(env, cur.str), argv[0])==0)
return enif_make_int(env, Pa_HostApiTypeIdToHostApiIndex(cur.num));
}
return enif_make_badarg(env);
}
if(enif_get_int(env, argv[0], &type)) {
return enif_make_int(env, Pa_HostApiTypeIdToHostApiIndex(type));
}
return enif_make_badarg(env);
}
/**
* Get device index from the hostapi index and hostapi device index
*/
static ERL_NIF_TERM erlaudio_get_device_index_from_hostapi(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int hostapi_device_index;
int hostapi_index;
if(argc!=2
|| enif_get_int(env, argv[0], &hostapi_index)
|| enif_get_int(env, argv[1], &hostapi_device_index))
{
return enif_make_badarg(env);
}
return enif_make_int(env, Pa_HostApiDeviceIndexToDeviceIndex(hostapi_index, hostapi_device_index));
}
/**
* Get default input device index
*/
static ERL_NIF_TERM erlaudio_get_default_input_device_index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
return enif_make_int(env, Pa_GetDefaultInputDevice());
}
/**
* Get default output device index
*/
static ERL_NIF_TERM erlaudio_get_default_output_device_index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
return enif_make_int(env, Pa_GetDefaultOutputDevice());
}
/**
* Get a record of the device information
*/
static ERL_NIF_TERM erlaudio_get_device(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
unsigned int i;
const PaDeviceInfo *deviceInfo;
if(argc != 1
|| !enif_get_uint(env, argv[0], &i)
|| i >= Pa_GetDeviceCount()) {
return enif_make_badarg(env);
}
deviceInfo = Pa_GetDeviceInfo(i);
ERL_NIF_TERM name;
char *name_raw = (char *) enif_make_new_binary(env, strlen(deviceInfo->name), &name);
strcpy(name_raw, deviceInfo->name);
const ERL_NIF_TERM fields[11] = {
enif_make_atom(env, "erlaudio_device"),
enif_make_int(env, i),
name,
enif_make_int(env, deviceInfo->hostApi),
enif_make_int(env, deviceInfo->maxInputChannels),
enif_make_int(env, deviceInfo->maxOutputChannels),
enif_make_double(env, deviceInfo->defaultLowInputLatency),
enif_make_double(env, deviceInfo->defaultLowOutputLatency),
enif_make_double(env, deviceInfo->defaultHighInputLatency),
enif_make_double(env, deviceInfo->defaultHighOutputLatency),
enif_make_double(env, deviceInfo->defaultSampleRate)
};
return enif_make_tuple_from_array(env, fields, 11);
}
/**
* Get the total number of devices
*/
static ERL_NIF_TERM erlaudio_get_device_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int numDevices;
numDevices = Pa_GetDeviceCount();
if(numDevices < 0) {
return pa_error_to_error_tuple(env, numDevices);
}
return enif_make_int(env, numDevices);
}
/**
* Check if the format settings are supported
*/
static ERL_NIF_TERM erlaudio_stream_format_supported(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int err;
PaStreamParameters *input = NULL;
PaStreamParameters *output = NULL;
double sample_rate;
if(argc!=3
|| !convert_tuple_to_stream_params(env, argv[0], &input)
|| !convert_tuple_to_stream_params(env, argv[1], &output)
|| !enif_get_double(env, argv[2], &sample_rate)
// I know it's confusing, but inside the !, we must assert things we
// wish to be true to not fall into the error case
// || !(input ==NULL && input->channelCount < Pa_GetDeviceInfo(input->device) ->maxInputChannels )
// || !(output!=NULL && output->channelCount < Pa_GetDeviceInfo(output->device)->maxOutputChannels)
) {
return enif_make_badarg(env);
}
err = Pa_IsFormatSupported(input, output, sample_rate);
if(err!=paFormatIsSupported) {
return pa_error_to_error_tuple(env, err);
}
return enif_make_atom(env, "ok");
}
/**
* Open a stream resource
*/
static ERL_NIF_TERM erlaudio_stream_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
unsigned long flags;
int err;
int ret;
struct erlaudio_stream_handle* handle = erlaudio_stream_handle_alloc();
// Gives ownership back to Erlang-land.
enif_release_resource(handle);
if(argc!=5
|| !convert_tuple_to_stream_params(env, argv[0], &handle->input)
|| !convert_tuple_to_stream_params(env, argv[1], &handle->output)
|| !enif_get_double(env, argv[2], &handle->sample_rate)
|| !enif_get_ulong (env, argv[3], &handle->frames_per_buffer)
|| !list_to_stream_flags(env, argv[4], &flags)
) {
return enif_make_badarg(env);
}
if(handle->input!=NULL) {
handle->input_sample_size = Pa_GetSampleSize(handle->input->sampleFormat);
handle->input_frame_size = handle->input_sample_size * handle->input->channelCount;
} else {
handle->input_sample_size = 0;
handle->input_frame_size = 0;
}
if(handle->output!=NULL) {
handle->output_sample_size = Pa_GetSampleSize(handle->output->sampleFormat);
handle->output_frame_size = handle->output_sample_size * handle->output->channelCount;
// 3 seconds seems like a reasonable time for the size of the buffer, no?
erlaudio_ringbuf_init(handle->output_buf, handle->output_frame_size * handle->sample_rate * 3);
memset(handle->output_buf->data, 0, handle->frames_per_buffer*handle->output_frame_size);
handle->output_buf->tail = handle->frames_per_buffer*handle->output_frame_size;
} else {
handle->output_sample_size = 0;
handle->output_frame_size = 0;
}
err = Pa_OpenStream(&handle->pa, handle->input, handle->output,
handle->sample_rate, handle->frames_per_buffer, flags,
NULL, NULL);
if(err!=paNoError) {
return pa_error_to_error_tuple(env, err);
}
enif_self(env, handle->owner_pid);
enif_self(env, handle->reader_pid);
ret = enif_make_tuple2(
env,
enif_make_atom(env, "ok"),
enif_make_resource(env, handle)
);
return ret;
}
/**
* Start a stream
*/
static ERL_NIF_TERM erlaudio_stream_start(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int err;
struct erlaudio_stream_handle* handle;
if(argc != 1 || !get_stream_handle(env, argv[0], &handle))
{
return enif_make_badarg(env);
}
handle->thread_opts = enif_thread_opts_create("erlaudio_thread_opts");
err = enif_thread_create("erlaudio_thread", &handle->thread,
erlaudio_thread_stream_main, handle, handle->thread_opts);
if(err!=0) {
return enif_make_tuple2(env,
enif_make_atom(env, "error"),
enif_make_tuple2(env,
enif_make_atom(env, "threadfailed"),
enif_make_int(env, err)
)
);
}
return enif_make_atom(env, "ok");
}
/**
* Get/Set the stream owner's pid
*/
static ERL_NIF_TERM erlaudio_stream_owner(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* handle;
if(argc < 1 || argc > 2
|| !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle))
{
return enif_make_badarg(env);
}
if(argc==1) {
return enif_make_pid(env, handle->owner_pid);
}
if(!enif_get_local_pid(env, argv[1], handle->owner_pid)) {
return enif_make_badarg(env);
}
return enif_make_atom(env, "ok");
}
/**
* Set the reader's pid
*/
static ERL_NIF_TERM erlaudio_stream_reader(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* handle;
if(argc < 1 || argc > 2
|| !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle))
{
return enif_make_badarg(env);
}
if(argc==1) {
return enif_make_pid(env, handle->reader_pid);
}
if(!enif_get_local_pid(env, argv[1], handle->reader_pid)) {
return enif_make_badarg(env);
}
return enif_make_atom(env, "ok");
}
/**
* Stop the stream, semi-immediate, doesn't wait for buffers to flush
*/
static ERL_NIF_TERM erlaudio_stream_stop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int err;
struct erlaudio_stream_handle* handle;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle)) {
return enif_make_badarg(env);
}
err = Pa_StopStream(handle->pa);
if(err!=paNoError) {
return pa_error_to_error_tuple(env, err);
}
return enif_make_atom(env, "ok");
}
/**
* Close the stream, returns immediately and doesn't wait for buffers to flush.
*/
static ERL_NIF_TERM erlaudio_stream_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int err;
struct erlaudio_stream_handle* handle;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle)) {
return enif_make_badarg(env);
}
//err = Pa_CloseStream(handle->pa);
err = Pa_AbortStream(handle->pa);
if(err!=paNoError) {
return pa_error_to_error_tuple(env, err);
}
return enif_make_atom(env, "ok");
}
/**
* Practically an emergency stop kind of thing. Only exposed because the API exposes it.
*/
static ERL_NIF_TERM erlaudio_stream_abort(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
int err;
struct erlaudio_stream_handle* handle;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle)) {
return enif_make_badarg(env);
}
err = Pa_AbortStream(handle->pa);
if(err!=paNoError) {
return pa_error_to_error_tuple(env, err);
}
return enif_make_atom(env, "ok");
}
/**
* Write stream
*/
static ERL_NIF_TERM erlaudio_stream_write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* h;
struct erlaudio_ringbuf* buf;
ErlNifBinary bin;
// printf("WRITE DATA!\n");
if(argc != 2
|| !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &h)
|| !enif_inspect_iolist_as_binary(env, argv[1], &bin)) {
return enif_make_badarg(env);
}
buf = h->output_buf;
// Must be evenly divisible by the frame size (sample format size * output channels)
if(bin.size % h->output_frame_size != 0) {
return enif_make_tuple2(env,
enif_make_atom(env, "error"),
enif_make_atom(env, "badbinsize")
);
}
// printf("Space avail? %zu %zu\n", erlaudio_ringbuf_avail_space(buf), bin.size);
if(erlaudio_ringbuf_avail_space(buf) < bin.size) {
return enif_make_tuple2(env,
enif_make_atom(env, "error"),
enif_make_atom(env, "toobig")
);
}
erlaudio_ringbuf_write(buf, bin.data, bin.size);
printf("Settings: %zu %zu\n", buf->head, buf->tail);
return enif_make_atom(env, "ok");
}
/**
* Get stream info
*/
static ERL_NIF_TERM erlaudio_stream_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* handle;
const PaStreamInfo *info;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle)) {
return enif_make_badarg(env);
}
info = Pa_GetStreamInfo(handle->pa);
return enif_make_tuple4(env,
enif_make_atom(env, "erlaudio_stream_info"),
enif_make_double(env, info->inputLatency),
enif_make_double(env, info->outputLatency),
enif_make_double(env, info->sampleRate)
);
}
/**
* Is stopped
*/
static ERL_NIF_TERM erlaudio_stream_is_stopped(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* handle;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void**) &handle)) {
return enif_make_badarg(env);
}
PaError ret = Pa_IsStreamStopped(handle->pa);
if(ret==1) {
return enif_make_atom(env, "false");
} else if(ret==paNoError) {
return enif_make_atom(env, "true");
} else {
return pa_error_to_error_tuple(env, ret);
}
}
/**
* Stream is active
*/
static ERL_NIF_TERM erlaudio_stream_is_active(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
struct erlaudio_stream_handle* handle;
if(argc != 1 || !enif_get_resource(env, argv[0], ERLAUDIO_STREAM_RESOURCE, (void **) &handle)) {
return enif_make_badarg(env);
}
PaError ret = Pa_IsStreamActive(handle->pa);
if(ret==1) {
return enif_make_atom(env, "false");
} else if(ret==paNoError) {
return enif_make_atom(env, "true");
} else {
return pa_error_to_error_tuple(env, ret);
}
}
Thread 16 Crashed:
0 beam.smp 0x0000000008f866a3 erts_alcu_free_thr_pref + 70
1 erlaudio_drv.so 0x00000000098a2bc9 erlaudio_ringbuf_destroy + 73 (erlaudio_drv.c:21)
2 erlaudio_drv.so 0x00000000098a2b51 erlaudio_stream_resource_cleanup + 225 (erlaudio_drv.c:74)
3 beam.smp 0x000000000904ce16 nif_resource_dtor + 92
4 beam.smp 0x000000000904ce98 enif_release_resource + 47
5 erlaudio_drv.so 0x00000000098a34a8 erlaudio_stream_open + 40 (erlaudio_drv.c:591)
6 beam.smp 0x0000000009062b32 process_main + 41240
7 beam.smp 0x0000000008fd6ddc sched_thread_func + 472
8 beam.smp 0x00000000090e505f thr_wrapper + 146
9 libsystem_pthread.dylib 0x00007fff8fbd02fc _pthread_body + 131
10 libsystem_pthread.dylib 0x00007fff8fbd0279 _pthread_start + 176
11 libsystem_pthread.dylib 0x00007fff8fbce4b1 thread_start + 13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment