sounddevice is a Python module that provides bindings for the PortAudio library [1].
Let’s see some examples of what sounddevice can do:
This module implements the algorithm:
where (1) captures 1024 frames from the ADC, and (2) plays the chunk of frames. In sounddevice a frame is a collection of one or more samples (typically, a frame is a single sample if the number of channels is 1, or two samples if the number of channels is 2).
If you want to run this module right now and you are not using a headset, check first that the output volumen of your speakers is not too high, otherwise you could involuntary “couple” the speaker and the mic(rophone) of your computer, producing a loud and annoying tonal sound. In order to mitigate this effect, you can also control the gain of your mic (if the gain is 0, no feedback between the speaker and the mic will be possible). In Xubuntu, these controls are available through clicking in the speaker icon (situated in the top-right corner of your screen) of the Xfce window manager.
To run the module:
Stop (killing) the module by clicking the CTRL- and c-keys (CTRL+c), simultaneously.
The chunk size introduces some latency. If we want to measure it:
First, we need the tools: SoX, Audacity, and plot_input.py:
Run:
and check that the gain of the mic does not produce clipping during the sound recording.
In a terminal, run:
while you control the output volume of the speakers to produce a decaying coupling noisy effect between both devices (the speaker(s) and the mic). If your desktop has not these transducers, we can use a male-to-male jack audio cable and connect the line-output of your soundcard to the input of your sound card.
In a different terminal (keep python wire3.py running), run:
to save the ADC’s output to the file test.wav.
Load the sound file into Audacity:
Modify the constant \(\mathtt {CHUNK\_SIZE}\) in the module and repeat this process, starting at the Step 3. Create an ASCII file (named latency_vs_chunk_size.txt) with the content (use TAB-ulators to space the columns):
# CHUNK_SIZE real 32 ... 64 ... 128 ... 256 ... 512 ... 1024 ... 2048 ... 4096 ... 8192 ...
with the real (practical) latency.
At this point, we know the real latency of wire3.py as a function of \(\mathtt {CHUNK\_SIZE}\). Plot the file latency_vs_chunk_size.txt with:
Let’s compute now the buffering latency of a chunk (the chunk-time). If \(\mathtt {sampling\_rate}\) is the number of frames per second during the recording process, it holds that:
\begin {equation} \mathtt {minimal\_buffering\_latency} = \mathtt {CHUNK\_SIZE} / \mathtt {sampling\_rate} \end {equation}
Add these calculations to latency_vs_chunk_size.txt using a third column (remember to use TABs).
# CHUNK_SIZE real minimal : : :
Plot both latencies:
Which seems to be the minimal practical (real) latency (the latency obtained ideallly when \(\mathtt {CHUNK\_SIZE}=1\) ... however, notice that depending on your computer, this chunk size can be too small, overwhelming the CPU) in your computer? Justify your answers.
[1] M. Walker. python-sounddevice.