Mercurial > audio-send
changeset 18:1e201037f666
separating out the sections of send.c
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 03 Nov 2011 13:32:27 -0700 |
parents | fdf84bc57e67 |
children | 22ac5a0367cd |
files | org/ear.org |
diffstat | 1 files changed, 99 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
1.1 --- a/org/ear.org Thu Nov 03 12:12:05 2011 -0700 1.2 +++ b/org/ear.org Thu Nov 03 13:32:27 2011 -0700 1.3 @@ -30,8 +30,8 @@ 1.4 pick no AudioRender at all, or the =LwjglAudioRenderer= 1.5 - jMonkeyEngine tries to figure out what sort of system you're 1.6 running and extracts the appropiate native libraries. 1.7 - - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (lightweight java game 1.8 - library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] 1.9 + - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game 1.10 + Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] 1.11 - =OpenAL= calculates the 3D sound localization and feeds a stream of 1.12 sound to any of various sound output devices with which it knows 1.13 how to communicate. 1.14 @@ -81,7 +81,19 @@ 1.15 files in the =OpenAL= source tree that must be modified to do so. I've 1.16 documented this process [[./add-new-device.org][here]] for anyone who is interested. 1.17 1.18 -#+srcname: send 1.19 + 1.20 +Onward to that actual Device! 1.21 + 1.22 +again, my objectives are: 1.23 + 1.24 + - Support Multiple Listeners from jMonkeyEngine3 1.25 + - Get access to the rendered sound data for further processing from 1.26 + clojure. 1.27 + 1.28 +** =send.c= 1.29 + 1.30 +** Header 1.31 +#+srcname: send-header 1.32 #+begin_src C 1.33 #include "config.h" 1.34 #include <stdlib.h> 1.35 @@ -117,7 +129,31 @@ 1.36 static void renderData(ALCdevice *, int samples); 1.37 1.38 #define UNUSED(x) (void)(x) 1.39 +#+end_src 1.40 1.41 +The main idea behing the Send device is to take advantage of the fact 1.42 +that LWJGL only manages one /context/ when using OpenAL. A /context/ 1.43 +is like a container that holds samples and keeps track of where the 1.44 +listener is. In order to support multiple listeners, the Send device 1.45 +identifies the LWJGL context as the master context, and creates any 1.46 +number of slave contexts to represent additional listeners. Every 1.47 +time the device renders sound, it synchronizes every source from the 1.48 +master LWJGL context to the slave contexts. Then, it renders each 1.49 +context separately, using a different listener for each one. The 1.50 +rendered sound is made available via JNI to jMonkeyEngine. 1.51 + 1.52 +To recap, the process is: 1.53 + - Set the LWJGL context as "master" in the =init()= method. 1.54 + - Create any number of additional contexts via =addContext()= 1.55 + - At every call to =renderData()= sync the master context with the 1.56 + slave contexts vit =syncContexts()= 1.57 + - =syncContexts()= calls =syncSources()= to sync all the sources 1.58 + which are in the master context. 1.59 + - =limitContext()= and =unLimitContext()= make it possible to render 1.60 + only one context at a time. 1.61 + 1.62 +** Necessary State 1.63 +#+begin_src C 1.64 //////////////////// State 1.65 1.66 typedef struct context_data { 1.67 @@ -133,9 +169,19 @@ 1.68 ALuint numContexts; 1.69 ALuint maxContexts; 1.70 } send_data; 1.71 +#+end_src 1.72 1.73 +Switching between contexts is not the normal operation of a Device, 1.74 +and one of the problems with doing so is that a Device normally keeps 1.75 +around a few pieces of state such as the =ClickRemoval= array above 1.76 +which will become corrupted if the contexts are not done in 1.77 +parallel. The solution is to create a copy of this normally global 1.78 +device state for each context, and copy it back and forth into and out 1.79 +of the actual device state whenever a context is rendered. 1.80 1.81 +** Synchronization Macros 1.82 1.83 +#+begin_src C 1.84 //////////////////// Context Creation / Synchronization 1.85 1.86 #define _MAKE_SYNC(NAME, INIT_EXPR, GET_EXPR, SET_EXPR) \ 1.87 @@ -168,6 +214,16 @@ 1.88 MAKE_SYNC3(syncSource3i, ALint, alGetSource3i, alSource3i); 1.89 MAKE_SYNC3(syncSource3f, ALfloat, alGetSource3f, alSource3f); 1.90 1.91 +#+end_src 1.92 + 1.93 +Setting the state of an =OpenAl= source is done with the =alSourcei=, 1.94 +=alSourcef=, =alSource3i=, and =alSource3f= functions. In order to 1.95 +complely synchronize two sources, it is necessary to use all of 1.96 +them. These macros help to condense the otherwise repetitive 1.97 +synchronization code involving these simillar low-level =OpenAL= functions. 1.98 + 1.99 +** Source Synchronization 1.100 +#+begin_src C 1.101 void syncSources(ALsource *masterSource, ALsource *slaveSource, 1.102 ALCcontext *masterCtx, ALCcontext *slaveCtx){ 1.103 ALuint master = masterSource->source; 1.104 @@ -231,8 +287,15 @@ 1.105 // Restore whatever context was previously active. 1.106 alcMakeContextCurrent(current); 1.107 } 1.108 +#+end_src 1.109 +This function is long because it has to exaustively go through all the 1.110 +possible state that a source can have and make sure that it is the 1.111 +same between the master and slave sources. I'd like to take this 1.112 +moment to salute the [[http://connect.creativelabs.com/openal/Documentation/Forms/AllItems.aspx][=OpenAL= Reference Manual]], which provides a very 1.113 +good description of =OpenAL='s internals. 1.114 1.115 - 1.116 +** Context Synchronization 1.117 +#+begin_src C 1.118 void syncContexts(ALCcontext *master, ALCcontext *slave){ 1.119 /* If there aren't sufficient sources in slave to mirror 1.120 the sources in master, create them. */ 1.121 @@ -261,7 +324,15 @@ 1.122 } 1.123 alcMakeContextCurrent(current); 1.124 } 1.125 +#+end_src 1.126 1.127 +Most of the hard work in Context Synchronization is done in 1.128 +=syncSources()=. The only thing that =syncContexts()= has to worry 1.129 +about is automoatically creating new sources whenever a slave context 1.130 +does not have the same number of sources as the master context. 1.131 + 1.132 +* Context Creation 1.133 +#+begin_src C 1.134 static void addContext(ALCdevice *Device, ALCcontext *context){ 1.135 send_data *data = (send_data*)Device->ExtraData; 1.136 // expand array if necessary 1.137 @@ -281,8 +352,15 @@ 1.138 data->contexts[data->numContexts] = ctxData; 1.139 data->numContexts++; 1.140 } 1.141 +#+end_src 1.142 1.143 +Here, the slave context is created, and it's data is stored in the 1.144 +device-wide =ExtraData= structure. The =renderBuffer= that is created 1.145 +here is where the rendered sound samples for this slave context will 1.146 +eventually go. 1.147 1.148 +* Context Switching 1.149 +#+begin_src C 1.150 //////////////////// Context Switching 1.151 1.152 /* A device brings along with it two pieces of state 1.153 @@ -316,11 +394,21 @@ 1.154 Device->Contexts = currentContext; 1.155 Device->NumContexts = currentNumContext; 1.156 } 1.157 +#+end_src 1.158 1.159 +=OpenAL= normally reneders all Contexts in parallel, outputting the 1.160 +whole result to the buffer. It does this by iterating over the 1.161 +Device->Contexts array and rendering each context to the buffer in 1.162 +turn. By temporarly setting Device->NumContexts to 1 and adjusting 1.163 +the Device's context list to put the desired context-to-be-rendered 1.164 +into position 0, we can get trick =OpenAL= into rendering each slave 1.165 +context separate from all the others. 1.166 1.167 +** Main Device Loop 1.168 +#+begin_src C 1.169 //////////////////// Main Device Loop 1.170 1.171 -/* Establish the LWJGL context as the main context, which will 1.172 +/* Establish the LWJGL context as the master context, which will 1.173 * be synchronized to all the slave contexts 1.174 */ 1.175 static void init(ALCdevice *Device){ 1.176 @@ -357,8 +445,14 @@ 1.177 } 1.178 alcMakeContextCurrent(current); 1.179 } 1.180 +#+end_src 1.181 1.182 +The main loop synchronizes the master LWJGL context with all the slave 1.183 +contexts, then walks each context, rendering just that context to it's 1.184 +audio-sample storage buffer. 1.185 1.186 +* JNI Methods 1.187 +#+begin_src C 1.188 //////////////////// JNI Methods 1.189 1.190 #include "com_aurellem_send_AudioSend.h" 1.191 @@ -389,17 +483,8 @@ 1.192 ALCdevice *recorder = (ALCdevice*) ((intptr_t)device); 1.193 send_data *data = (send_data*)recorder->ExtraData; 1.194 if ((ALuint)n > data->numContexts){return;} 1.195 - 1.196 - //printf("Want %d samples for listener %d\n", samples, n); 1.197 - //printf("Device's format type is %d bytes per sample,\n", 1.198 - // BytesFromDevFmt(recorder->FmtType)); 1.199 - //printf("and it has %d channels, making for %d requested bytes\n", 1.200 - // recorder->NumChan, 1.201 - // BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); 1.202 - 1.203 memcpy(buffer_address, data->contexts[n]->renderBuffer, 1.204 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); 1.205 - //samples*sizeof(ALfloat)); 1.206 } 1.207 1.208 /* 1.209 @@ -515,8 +600,6 @@ 1.210 return format; 1.211 } 1.212 1.213 - 1.214 - 1.215 //////////////////// Device Initilization / Management 1.216 1.217 static const ALCchar sendDevice[] = "Multiple Audio Send";