From eaca8109f5da65b9edb1ed43b557a80ed8786fed Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 28 Aug 2014 16:05:51 -0400 Subject: [PATCH] now using a pure-julia portaudio wrapper and the read/write API --- deps/build.jl | 3 - deps/src/Makefile | 63 ------- deps/src/shim.c | 93 ----------- deps/usr/lib/libportaudio_shim.dylib | Bin 9836 -> 0 bytes deps/usr/lib/libportaudio_shim.so | Bin 11923 -> 0 bytes src/AudioIO.jl | 4 +- src/portaudio.jl | 235 +++++++++++++++++---------- 7 files changed, 154 insertions(+), 244 deletions(-) delete mode 100644 deps/src/Makefile delete mode 100644 deps/src/shim.c delete mode 100755 deps/usr/lib/libportaudio_shim.dylib delete mode 100755 deps/usr/lib/libportaudio_shim.so diff --git a/deps/build.jl b/deps/build.jl index 99304d2..9ef9f16 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -25,6 +25,3 @@ end @BinDeps.install [:libportaudio => :libportaudio, :libsndfile => :libsndfile] - -# cd(Pkg.dir("AudioIO", "deps", "src")) -# run(`make`) diff --git a/deps/src/Makefile b/deps/src/Makefile deleted file mode 100644 index 4d6602d..0000000 --- a/deps/src/Makefile +++ /dev/null @@ -1,63 +0,0 @@ -# Makefile lifted from Clang.jl - -all: default - -ifeq (exists, $(shell [ -e Make.user ] && echo exists )) -include Make.user -endif - -.PHONY: all clean check-env default - -#check-env: -#ifndef JULIA_INC -# $(error Environment variable JULIA_INC is not set.) -#endif - -#INC =-I"$(JULIA_INC)" -FLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC -CFLAGS =-g -LIBS =-L/usr/local/lib -lportaudio -LINUX_LIBS =-lrt -LINUX_LDFLAGS =-rdynamic -# add the Homebrew.jl tree to the include dirs in case we used it for -# portaudio and libsndfile -DARWIN_LDFLAGS =-L../../../Homebrew/deps/usr/lib -DARWIN_INC =-I../../../Homebrew/deps/usr/include -TARGETDIR=../usr/lib - -OBJS = shim.o - -# Figure out OS and architecture -OS = $(shell uname) -ifeq ($(OS), MINGW32_NT-6.1) - OS=WINNT -endif - -# file extensions and platform-specific libs -ifeq ($(OS), WINNT) - SHLIB_EXT = dll -else ifeq ($(OS), Darwin) - SHLIB_EXT = dylib - INC += $(DARWIN_INC) - LDFLAGS += $(DARWIN_LDFLAGS) -else - LIBS += $(LINUX_LIBS) - LDFLAGS += $(LINUX_LDFLAGS) - SHLIB_EXT = so - INC += $(LINUX_INC) -endif - -TARGET=$(TARGETDIR)/libportaudio_shim.$(SHLIB_EXT) - -default: $(TARGET) - -%.o: %.c Makefile - $(CC) $< -fPIC -c -o $@ $(INC) $(CFLAGS) $(FLAGS) - -$(TARGET): $(OBJS) Makefile - mkdir -p $(TARGETDIR) - $(CC) $(OBJS) -shared -o $@ $(LDFLAGS) $(LIBS) - -clean: - rm -f *.o - rm -f $(TARGET) diff --git a/deps/src/shim.c b/deps/src/shim.c deleted file mode 100644 index fa7df80..0000000 --- a/deps/src/shim.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include - -#define SHIM_REVISION 2 - -int paCallback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData); - -static PaStream *AudioStream; -static int JuliaPipeReadFD = 0; -static int JuliaPipeWriteFD = 0; -static sem_t CSemaphore; -static void *Buffer = NULL; - -int make_pipe(void) -{ - int pipefd[2]; - pipe(pipefd); - JuliaPipeReadFD = pipefd[0]; - JuliaPipeWriteFD = pipefd[1]; - sem_init(&CSemaphore, 0, 0); - return JuliaPipeReadFD; -} - -void synchronize_buffer(void *buffer) -{ - Buffer = buffer; - sem_post(&CSemaphore); -} - -int get_shim_revision(void) -{ - return SHIM_REVISION; -} - -PaError open_stream(unsigned int sampleRate, unsigned int bufSize) -{ - PaError err; - - err = Pa_OpenDefaultStream(&AudioStream, - 1, /* single input channel */ - 1, /* mono output */ - paFloat32, /* 32 bit floating point output */ - sampleRate, - bufSize, /* frames per buffer, i.e. the number of sample frames - that PortAudio will request from the callback. Many - apps may want to use paFramesPerBufferUnspecified, - which tells PortAudio to pick the best, possibly - changing, buffer size.*/ - paCallback, /* this is your callback function */ - NULL); /*This is a pointer that will be passed to your callback*/ - if(err != paNoError) - { - return err; - } - - err = Pa_StartStream(AudioStream); - if(err != paNoError) - { - return err; - } - - return paNoError; -} - -/* - * This routine will be called by the PortAudio engine when audio is needed. - * It may called at interrupt level on some machines so don't do anything that - * could mess up the system like calling malloc() or free(). - */ -int paCallback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) -{ - unsigned int i; - unsigned char fd_data = 0; - - sem_wait(&CSemaphore); - for(i=0; i)9ub4Ir+<<=LivL5kj1e6h*4G3Gsx8!4hIQ($Raw3o}PYGg7 z%(TKVgF2F-OPufH}@j^(7Y zE1xXnWU*YrJm=^0>A0+E>2lFM41W+87UOZLvrlwEY@jabLpV7L_jda(NZk;rS9W<&&ihjuw-JKwP> z=(q+xT+%MYjeGbSjn@NHu0vuyG519%VFbbmgb@fM5Jn)3z&}S|*mQq1_xv0;$9GSe zwYD4Z>}A%3=^E4ZtMD9Pj&FPrMQ@R)Haz7n+X7S@KH)Cg48#50{lqnXuD7sdZQ~SF zZP8jqakIH$_q1!&-Tohxz%!IXNlNj#r(OB!#UOm?7_vn&Y zG##v4_Ofd1Rkty2u5tMI%QG`G=6L1P*+|wlj-j%?0S(?q!eERy-Dyl@aO?h=)r(O# zvpS~o3sk;Tm^j@;SI(n)H1~kkBU&f5&T0LS)>W;a)_NYwuqcc`7=bVXVFbbm zgb@fM5Jn)3Kp25A0$~Kg2!s*%pCYhiW%~%bz91oa1#XRQm!p~O@_~v~N{Ksn#3mBB zBuuHxcA3O|HZI4jb-bfc5}6P?+Lt8cpd~jK?Xey^ZB=sRp>oN#@&a{nuWqPpl>((# zH}#davN^WyLU}P;v}v!0?bvzTo@dL%35|;dM`2Urot9TW2)whDEz{YPh{U%Ct$Ay^ z?UCN>>>3-*lnP_nM{Jp_q|=y5PXVLG+|+n`i`FexE|)|I#6XLfT#k1tlNTeau0Xa= zzlfP!UX66^i-?ooE#&77cmvk(8Gw3qgF24kY9wOb*z(oQ`M7NLuLJ?rD;rgjBGSV9 zzaZvoq;yL}oPFC@Lu^9A4rKoK5*F~P0A~Fvq$(1HcmP?I076XvL0%X`a!B;|G3)8y zg+w8ye-P8Zs{N~|r@xQcp8orgD8%&NM*tzFe`2wMVjPP8K4v}r4|vt5{eAob%Gcoi ztAEeFSL6F)p2IsDZ`1gY#u<$#H7;t*{!+}ed<(re!2hepK0gP)3W*#48YJGyb3a28tC9Uv+xVGo=ezmILhokpa^5M8b_(_R!sx$l zubLNxoIMWkO~{<%Kn9Nk88{B)?fgh!5NFd}ww1cIN6Z%7!$XH9w+z{NtC%U2Y#}#! z$BR_s*2m$j%YWbqjQ@;doo{0E9?(3tgGY129{_bIHB7_H0&`I+c~B&SQNN I&-ugg7joa_y8r+H diff --git a/deps/usr/lib/libportaudio_shim.so b/deps/usr/lib/libportaudio_shim.so deleted file mode 100755 index 241ee1680cab8fccd075589f16c52490c55e3fc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11923 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 portaudio_task(fd, stream))) - # TODO: test not yielding here - yield() - info("Audio Task Yielded, starting the stream...") - - err = ccall((:open_stream, libportaudio_shim), PaError, - (Cuint, Cuint), - stream.info.sample_rate, stream.info.buf_size) - handle_status(err) - info("Portaudio stream started.") -end - -function handle_status(err::PaError) - if err != PA_NO_ERROR - msg = ccall((:Pa_GetErrorText, libportaudio), - Ptr{Cchar}, (PaError,), err) - error("libportaudio: " * bytestring(msg)) - end -end - -function portaudio_task(jl_filedesc::Integer, stream::PortAudioStream) - buffer = zeros(AudioSample, stream.info.buf_size) - desc_bytes = Cchar[0] - jl_stream = fdio(jl_filedesc) - jl_rawfd = RawFD(jl_filedesc) +function portaudio_task(stream::PortAudioStream) + info("PortAudio Render Task Running...") + n = bufsize(stream) + buffer = zeros(AudioSample, n) try while true + while Pa_GetStreamReadAvailable(stream.stream) < n + sleep(0.005) + end + Pa_ReadStream(stream.stream, buffer, n) # assume the root is always active rendered = render(stream.root.renderer, buffer, stream.info)::AudioBuf for i in 1:length(rendered) buffer[i] = rendered[i] end - for i in (length(rendered)+1):length(buffer) + for i in (length(rendered)+1):n buffer[i] = 0.0 end - - # wake the C code so it knows we've given it some more data - synchronize_buffer(buffer) - # wait for new data to be available from the sound card (and for it - # to have processed our last frame of data). At some point we - # should do something with the data we get from the callback - wait(jl_rawfd, readable=true) - # read from the file descriptor so that it's empty. We're using - # ccall here because readbytes() was blocking the whole julia - # thread. This shouldn't block at all because we just waited on it - ccall(:read, Clong, (Cint, Ptr{Void}, Culong), - jl_filedesc, desc_bytes, 1) + while Pa_GetStreamWriteAvailable(stream.stream) < n + sleep(0.005) + end + Pa_WriteStream(stream.stream, buffer, n) end catch ex warn("Audio Task died with exception: $ex") Base.show_backtrace(STDOUT, catch_backtrace()) - finally - # TODO: we need to close the stream here. Otherwise the audio callback - # will segfault accessing the output array if there were exceptions - # thrown in the render loop end end @@ -156,32 +137,118 @@ type PortAudioInterface <: AudioInterface max_output_channels::Int end -# some thin wrappers to portaudio calls -get_device_info(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), - Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) -get_host_api_info(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), - Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) - function get_portaudio_devices() - init_portaudio() + require_portaudio_init() device_count = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) - pa_devices = [get_device_info(i) for i in 0:(device_count - 1)] + pa_devices = [Pa_GetDeviceInfo(i) for i in 0:(device_count - 1)] [PortAudioInterface(bytestring(d.name), - bytestring(get_host_api_info(d.host_api).name), + bytestring(Pa_GetHostApiInfo(d.host_api).name), d.max_input_channels, d.max_output_channels) for d in pa_devices] end -function init_portaudio() +function require_portaudio_init() # can be called multiple times with no effect global portaudio_inited if !portaudio_inited - @assert(libportaudio_shim != "", "Failed to find required library libportaudio_shim. Try re-running the package script using Pkg.build(\"AudioIO\"), then reloading with reload(\"AudioIO\")") - info("Initializing PortAudio. Expect errors as we scan devices") - err = ccall((:Pa_Initialize, libportaudio), PaError, ()) - handle_status(err) + Pa_Initialize() portaudio_inited = true end end + +# Low-level wrappers for Portaudio calls +Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), + Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) +Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), + Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) + +function Pa_Initialize() + err = ccall((:Pa_Initialize, libportaudio), PaError, ()) + handle_status(err) +end + +function Pa_Terminate() + err = ccall((:Pa_Terminate, libportaudio), PaError, ()) + handle_status(err) +end + +function Pa_StartStream(stream::PaStream) + err = ccall((:Pa_StartStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_StopStream(stream::PaStream) + err = ccall((:Pa_StopStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_CloseStream(stream::PaStream) + err = ccall((:Pa_CloseStream, libportaudio), PaError, + (PaStream,), stream) + handle_status(err) +end + +function Pa_GetStreamReadAvailable(stream::PaStream) + avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, + (PaStream,), stream) + avail >= 0 || handle_status(avail) + avail +end + +function Pa_GetStreamWriteAvailable(stream::PaStream) + avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, + (PaStream,), stream) + avail >= 0 || handle_status(avail) + avail +end + +function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf)) + frames <= length(buf) || error("Need a buffer at least $frames long") + err = ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream, buf, frames) + handle_status(err) + buf +end + +function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf)) + frames <= length(buf) || error("Need a buffer at least $frames long") + err = ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Void}, Culong), + stream, buf, frames) + handle_status(err) + nothing +end + +Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) + +function Pa_OpenDefaultStream(inChannels::Integer, outChannels::Integer, + sampleFormat::PaSampleFormat, + sampleRate::Real, framesPerBuffer::Integer) + streamPtr::Array{PaStream} = PaStream[0] + err = ccall((:Pa_OpenDefaultStream, libportaudio), + PaError, (Ptr{PaStream}, Cint, Cint, + PaSampleFormat, Cdouble, Culong, + Ptr{PaStreamCallback}, Ptr{Void}), + streamPtr, inChannels, outChannels, sampleFormat, sampleRate, + framesPerBuffer, 0, 0) + handle_status(err) + + streamPtr[1] +end + +function handle_status(err::PaError) + if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED + msg = ccall((:Pa_GetErrorText, libportaudio), + Ptr{Cchar}, (PaError,), err) + warn("libportaudio: " * bytestring(msg)) + elseif err != PA_NO_ERROR + msg = ccall((:Pa_GetErrorText, libportaudio), + Ptr{Cchar}, (PaError,), err) + error("libportaudio: " * bytestring(msg)) + end +end