In this tutorial we will focus on full duplex streaming using jackd. Full duplex is a term given to both audio output and audio input (half duplex means either output or input). This tutorial will also introduce how to start jackd, the JackPortMonitorGui application and show you the playback and capture mixers settings required for the Audio Injector stereo cards to get input and output audio working so that you can hear something on the input at the output of your hardware.
Before we start, you have to have compiled and installed gtkIOStream. For certain components of gtkIOStream its compilation and installation isn't necessary, but for certain parts of audio processing it is necessary. Tutorial 0 and contents guides you through how to install and test the installation of gtkIOStream. Before you start this tutorial, you must have it installed first.
If you haven't already done so, install jackd2 like so :
Code: Select all
sudo apt-get install jackd2
Code: Select all
geany JackClient.C
Code: Select all
ssh -X pi@raspberrypiIP
Code: Select all
#include <JackClient.H>
#include <iostream>
using namespace std;
#include <unistd.h> // for sleep
/** This jack client will play back what it captures
*/
class JackFullDuplex : public JackClient {
/// The Jack client callback - copy input to output
int processAudio(jack_nframes_t nframes) {
if (outputPorts.size()!=inputPorts.size()){
cout<<"Different input and output port count, don't know how to copy. In port cnt="<<inputPorts.size()<<" output port count="<<outputPorts.size()<<endl;
return -1;
}
for (uint i=0; i<outputPorts.size(); i++) { // for each channel of audio, copy input to output
jack_default_audio_sample_t *out = ( jack_default_audio_sample_t* ) jack_port_get_buffer ( outputPorts[i], nframes ); // the output sample buffer
jack_default_audio_sample_t *in = ( jack_default_audio_sample_t* ) jack_port_get_buffer ( inputPorts[i], nframes ); // the inputs sample buffer
for (uint j=0; j<nframes; j++) // rather then copying, you could do some signal processing here!
out[j]=in[j];
}
return 0;
}
};
int main(int argc, char *argv[]) {
JackFullDuplex jackClient; // init the full duplex jack client
// connect to the jack server
int res=jackClient.connect("jack full duplex client");
if (res!=0)
return JackDebug().evaluateError(res);
cout<<"Jack : sample rate set to : "<<jackClient.getSampleRate()<<" Hz"<<endl;
cout<<"Jack : block size set to : "<<jackClient.getBlockSize()<<" samples"<<endl;
// create the ports
res=jackClient.createPorts("in ", 2, "out ", 2);
if (res!=0)
return JackDebug().evaluateError(res);
// start the client connecting ports to system ports
res=jackClient.startClient(2, 2, true);
if (res!=0)
return JackDebug().evaluateError(res);
while (1) sleep(10); // sleep forever
return 0;
}
We start with good old main, which has the standard (unused) input variables argc and argv. The first thing main does is instantiate our jackd client JackFullDuplex :
Code: Select all
int main(int argc, char *argv[]) {
JackFullDuplex jackClient;
Code: Select all
#include <JackClient.H>
#include <iostream>
using namespace std;
#include <unistd.h>
Next we define our JackFullDuplex class which inherits from the JackClient class :
Code: Select all
class JackFullDuplex : public JackClient {
Code: Select all
int processAudio(jack_nframes_t nframes) {
if (outputPorts.size()!=inputPorts.size()){
cout<<"Different input and output port count, don't know how to copy. In port cnt="<<inputPorts.size()<<" output port count="<<outputPorts.size()<<endl;
return -1;
}
Next, as we are a full duplex "copy" client, we take the input audio and copy it directly to the output. We loop over all of our ports (channels) :
Code: Select all
for (uint i=0; i<outputPorts.size(); i++) { // for each channel of audio, copy input to output
jack_default_audio_sample_t *out = ( jack_default_audio_sample_t* ) jack_port_get_buffer ( outputPorts[i], nframes ); // the output sample buffer
jack_default_audio_sample_t *in = ( jack_default_audio_sample_t* ) jack_port_get_buffer ( inputPorts[i], nframes ); // the inputs sample buffer
for (uint j=0; j<nframes; j++) // rather then copying, you could do some signal processing here!
out[j]=in[j];
}
Finally, we return 0 indicating that everything is good and we want to keep processing next time there are nframes of audio samples available :
Code: Select all
return 0;
}
OK - what about the rest of main ? In the rest of the main function we tell our JackFullDuplex client (jackClient in the code) to connect to the jackd server :
Code: Select all
int res=jackClient.connect("jack full duplex client");
if (res!=0)
return JackDebug().evaluateError(res);
We print information about the sample rate in use and the number of samples per channel per frame (nframes) :
Code: Select all
cout<<"Jack : sample rate set to : "<<jackClient.getSampleRate()<<" Hz"<<endl;
cout<<"Jack : block size set to : "<<jackClient.getBlockSize()<<" samples"<<endl;
Code: Select all
res=jackClient.createPorts("in ", 2, "out ", 2);
if (res!=0)
return JackDebug().evaluateError(res);
We want to start our client and auto-connect our ports to the system input and output ports :
Code: Select all
res=jackClient.startClient(2, 2, true);
if (res!=0)
return JackDebug().evaluateError(res);
Finally we sleep forever and if for whatever reason we stop sleeping, we return 0 :
Code: Select all
while (1) sleep(10); // sleep forever
return 0;
We compile it with the following command :
Code: Select all
g++ `pkg-config --cflags --libs gtkIOStream` -o JackClient JackClient.C
We have to start jackd before we start our client, if you are using a Pi, you will have to have a sound card installed with audio input. You start jackd with two input and output ports (channels) like so :
Code: Select all
jackd -d alsa -r 48000 -i 2 -o 2
If you don't have a DISPLAY env. variable, jackd will complain and not stare, so either log back in with "ssh -X" (assuming you used ssh to access your term) or declare one first :
Code: Select all
export DISPLAY=:0.0
jackd -d alsa -r 48000 -i 2 -o 2
Code: Select all
JackPortMonitorGui
Finally we start our jackd client :
Code: Select all
$ ./JackClient
Jack : sample rate set to : 48000 Hz
Jack : block size set to : 1024 samples
input port 0 latency = 0
input port 1 latency = 0
output port 0 latency = 0
output port 1 latency = 0
JackClient::bufferSizeChangeStatic : New buffer size = 1024
You will now observe that the JackPortMonitorGui app shows that the system and our client are fully connected and looks like so : You will be able to drag and drop the buttons from the left side to the right side dropping on any buttons on the right to connect ports together. You can disconnect ports by dragging buttons from the right side to the left side dropping them on the ports you want to disconnect.
At this point you can verify that your client is working by plugging in an audio source to the input lines and playing the output lines through an amplifier or pair of headphones.
If you don't hear any audio then either your player isn't playing or your mixer isn't set up correctly. In the case of the audio injector stereo cards, your playback alsamixer should look like so : Your capture alsamixer setting should look like so :