-
Notifications
You must be signed in to change notification settings - Fork 0
/
doc-basc.txt
230 lines (194 loc) · 9.8 KB
/
doc-basc.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
**********************************************************************
******** BASC-GENOM3: A GENERIC BINAURAL AUDIO STREAM CLIENT *********
**********************************************************************
Copyright (c) 2014, LAAS/CNRS
All rights reserved.
CONTENTS
1. OVERVIEW
2. AN ALGORITHM FOR CLIENTS OF BASS
3. IMPLEMENTATION IN BASC
1. OVERVIEW ----------------------------------------------------------
The binaural audio stream client 'basc' is a GenoM3 module that acts
as a client of the binaural audio stream server ('bass', see related
documentation). It can be connected to the output port of bass, and
proposes a generic algorithm to retrieve binaural audio data from the
port.
basc does not perform any processing on the retrieved data. It is only
intended to give an example on how a client can get data from bass,
trying to deal with any possible situation a client can face.
5. AN ALGORITHM FOR CLIENTS OF BASS ----------------------------------
A client of bass can possibly face many different situations:
* It could need just a single block of data (e.g. 2 seconds of audio
data), and the block may be longer than the total length of the
port.
* It could indefinitely request new blocks of data, with the
requirement that the blocks follow each other (no dropped frames
between two consecutive blocks).
* It could request data faster than the port is actually updated. Or
it might not read it fast enough, leading to data loss. And in case
of data loss, it might need to know how many frames were lost.
Let's define FOP as the total amount of Frames On the Port. The data
structure published on the 'Audio' port is recalled below in a formal
definition:
structure portStruct {
members: integer sampleRate
integer nChunksOnPort
integer nFramesPerChunk
integer lastFrameIndex
integer left[FOP] //FOP = nFramesPerChunk * nChunksOnPort
integer right[FOP]
}
The left and right members are arrays of FOP samples each, updated as
FIFOs (c.f. bass documentation). The server also stores the index of
the last published frame in the lastFrameIndex member. What the client
need is to copy blocks of given size 'N' from the left and right
arrays. Let's define a function 'getAudioData', taking N as input and
returning one copied block.
In order not to drop any frames between two consecutive blocks
retrieved with two calls, the getAudioData function takes both as
input and output an index of the Next Frame to be Read, noted
'nfr'. For instance, the client will get a first block of N frames
starting from a given index nfr. The function will return, along with
the block of data, the new value of the index, corresponding to the
next frame right after the first retrieved block. Then, the client can
call the function again with this new value for nfr, so as to get the
second block starting from this point.
For the very first block, the client can choose nfr according to the
current value of the lastFrameIndex.
* If it wants to pick data from the existing frames on the port, nfr
is chosen to be less than lastFrameIndex.
* If it wants to get fresher data (frames that are not yet on the port
but will be soon), nfr is chosen to be greater than lastFrameIndex.
With a call to getAudioData, the client requests a block of given size
N, but the function might not be able to return a full block of N
frames. Indeed, the ending point for the desired block may be a frame
that is not yet published by the server. Thus, the function returns
the amount of frames 'n' it was able to get (n <= N). The client can
then call the function again and ask for the remaining frames in a
loop until the desired block is complete. This will occur in the
following situations:
* The client requests data faster than they are captured by the
microphones (which should be the regular case, because a slower
client will end up losing data).
* The client requests a block which is longer than the total amount of
frames on the port (N > FOP).
* At first call, if the client sets nfr to a greater value than
lastFrameIndex, getAudioData will not return any available frames (n
= 0). But the client can keep calling the function in a loop until
it gets the requested frames.
Finally, in case of data loss, getAudioData also returns the amount of
frames that were lost. The retrieved block then starts at the first
frame that is still available on the port (the oldest one).
Below is the getAudioData function written with formal syntax:
function: getAudioData
|
| inputs: integer N (amount of frames the client wants to get)
| portStruct Audio (data from the the output port of bass)
|
| outputs: integer n (amount of frames the function was able to get)
| integer loss (amount of lost frames, 0 if no loss)
| integer l[n] (the retrieved data block from left channel)
| integer r[n] (the retrieved data block from right channel)
|
| in&out: integer nfr (index of the Next Frame to Read)
|
| local: integer FOP (total amount of Frames On the Port)
| integer lfi (Index of the Last Frame on the port)
| integer ofi (Index of the Oldest Frame on the port)
| integer pos (current position in the left and right arrays)
|
| algorithm
| |
| | FOP <- Audio.nFramesPerChunk * Audio.nChunksOnPort
| | lfi <- Audio.lastFrameIndex
| | ofi <- max(0, lfi - FOP + 1) //if the acquisition just started and the
| | //port is not full yet, ofi equals 0
| | /* Detect a data loss */
| | loss <- 0
| | if (nfr < ofi)
| | | loss <- ofi - nfr
| | | nfr <- ofi
| | end if
| |
| | /* Compute the starting position in the left and right input arrays */
| | pos <- FOP - (lfi - nfr + 1)
| |
| | /* Fill the output arrays l and r */
| | n <- 0
| | while (n < N AND pos < FOP)
| | | l[n] <- Audio.left[pos]
| | | r[n] <- Audio.right[pos]
| | | n <- n + 1
| | | pos <- pos + 1
| | | nfr <- nfr + 1
| | end while
| |
| | return (l[], r[], n, nfr, loss)
| |
| end algorithm
|
end function
3. IMPLEMENTATION IN BASC --------------------------------------------
basc implements the algorithm described above in a service called
'GetBlocks', defined in description file 'basc-genom3/basc.gen'. It
expects the following input parameters:
(data_type name = default_value : "documentation")
inout unsigned long nBlocks = 1 : "Amount of blocks, 0 for unlimited",
in unsigned long nFramesPerBlock = 12000 : "Blocks size in frames",
in long startOffs = -12000 : "Starting offset ('past' < 0, 'future' > 0)
The first parameter nBlocks sets the amount of blocks that the service
will get, or can be set to 0 to run indefinitely. The second parameter
nFramesPerBlock sets the block size (N in the previous section). Last,
the startOffs parameter sets the index of the first frame to read,
relatively to the current value of the Last Frame Index on the
port. For instance, if startOffs is -1000 and lastFrameIndex is 43000
when the service is called, the first frame to read (nfr in previous
section) will have index 42000. With the given default values,
GetBlocks will get 1 block of 12000 frames, taking the last 12000
frames on the port.
At any moment, the GetBlocks service can be interrupted by calling a
service named 'Stop'.
The GetBlocks service executes at a period of 250ms, choosen
arbitrarily for the example (c.f. the dotgen file). So every 250ms, it
calls the getAudioData function, either to request a new block or to
complete the current block if the previous call could not return a
full one. Depending on the sample rate, if the requested block size
(parameter nFramesPerBlock) is too small so that one block is less
than 250ms, the client will eventually loose some frames. On the other
hand, if a block is more than 250ms, the client will request data
faster than they are streamed, which should be fine.
This can be tested with basc: let's assume that the sample rate is
44100Hz. 250ms @44100Hz is 11025 frames. So calling GetBlocks with
nFramesPerBlock < 11025 will lead to data loss. Here is an example
from the Tcl client:
# The middleware, genomix, bass and basc should be running
eltclsh > package require genomix
eltclsh > set g [::genomix::connect]
eltclsh > $g load bass; $g load basc ;#load the server and the client
eltclsh > ::basc::connect_port Audio bass/Audio ;#connect the port
# Start the acquisition (using default values)
eltclsh > ::bass::Acquire {device hw:1,0 sampleRate 44100 \
nFramesPerChunk 2205 nChunksOnPort 20} &
# Get unlimited amount of blocks, with nFramesPerBlock < 11025
eltclsh > ::basc::GetBlocks {nBlocks 0 nFramesPerBlock 10000 \
startOffs -10000} &
# After a few seconds, the client prints a message at each attempt
# to get the following block, indicating that some frames are lost
eltclsh > ::basc::Stop
# Get unlimited amount of blocks, with nFramesPerBlock > 11025
eltclsh > ::basc::GetBlocks {nBlocks 0 nFramesPerBlock 12000 \
startOffs -12000} &
# In that case, as the client reads slighlty faster than data are
# streamed, it can be seen from time to time that the retrieved
# block is not complete and the client makes a second request to
# get the remaining part.
eltclsh > ::basc::Stop
# Get one block of 2 seconds (88200 frames at 44100Hz)
eltclsh > ::basc::GetBlocks {nBlocks 1 nFramesPerBlock 88200 \
startOffs 0}
eltclsh > ::bass::kill; ::basc::kill;
eltclsh > exit
The getAudioData function in charge of getting the requested block is
implemented in C in file 'basc-genom3/codels/basc_read_codels.c'. The
codels of the GetBlocks service are also written in this file, with
comments to explain the overall process followed by basc.