Skip to content

Instantly share code, notes, and snippets.

@mike7c2
Created January 16, 2015 15:12
Show Gist options
  • Save mike7c2/3c4825278c0217d7f8a0 to your computer and use it in GitHub Desktop.
Save mike7c2/3c4825278c0217d7f8a0 to your computer and use it in GitHub Desktop.
/**
* Copyright Michael Shaw 2013
* Created with inspiration from alsa_in.c
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This is a small tool intended to replace alsa_in specifically for bluetooth channels
* It carries out the hard work, reading from alsa and converting sample rates in the main
* thread, this means the only work carried out in the jack callback is to read from the
* intermediate buffer. This is an operation which can never block thus preventing
* any conditions from being able to cause xruns in the jack server.
*
* Program main thread:
* Initialise Alsa, Jack and libsamplerate
* while running:
* Read data from alsa
* Convert alsa data from SI16 to float
* Resample float data using libsamplerate
* Write data into ringbuffer
*
* Jack callback:
* Read data from ringbuffer
*
* Note that the ringbuffer being written to whilst full or read from whilst empty
* will cause overrun/underrun messages to be printed but will not attempt to correct
* any problems - this approach seems to be effective from testing carried out so far
*
* Note increasing the ringbuffer size too large will cause a large delay between alsa
* data being read and the resampled data being written to jack, this will manifest as
* a large audio latency. Decreasing the buffer size too far will cause frequent under
* and overruns, this will render the audio data unusable. The current values have been
* arrived at after some experimentation, touching them is not reccomended! This situation
* may be avoided by starting alsa after starting jack however this would still allow drift
* to accumulate if the alsa data is provided marginally faster than jack consumes it.
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <poll.h>
#include <alsa/asoundlib.h>
#include <jack/jack.h>
#include <limits.h>
#include <samplerate.h>
#define INPUT_SAMPLE_RATE 8000 //! Sample rate to read from alsa
#define CHANNELS 1 //! Number of channels for alsa source
#define INTERNAL_BUF_SIZE 32 //! Buffer size to request from alsa
static jack_port_t * s_port; //! Handle for jack port
static unsigned int s_jack_sample_rate; //! Sample rate for jack
#define CONVERT_BUFFER_SIZE 2048 //! Size of Ringbuffer between sampler and jack
static unsigned int s_convert_buffer_head; //! Head pointer for ringbuffer
static unsigned int s_convert_buffer_tail; //! Tail pointer for ringbuffer
float s_convert_buffer[CONVERT_BUFFER_SIZE]; //! Ringbuffer storage array
static int s_running = 1; //! Flag remains true until stop requested
/**
* Read read_length samples from ringbuffer. Store them in buf
* @param read_length Number of samples to read
* @param buf Buffer to read samples to
*/
void read_convert_buffer(int read_length, float * buf)
{
int length = 0;
while ((s_convert_buffer_head != s_convert_buffer_tail) && (read_length != length))
{
buf[length++] = s_convert_buffer[s_convert_buffer_tail];
s_convert_buffer_tail = (s_convert_buffer_tail+1) & (CONVERT_BUFFER_SIZE-1);
}
if ( length < read_length )
{
printf (" Underrun of %d\n", read_length - length);
}
while(length < read_length)
{
buf[length++] = 0;
}
}
/**
* Read read_length samples from ringbuffer. Store them in buf
* @param read_length Number of samples to read
* @param buf Buffer to read samples to
*/
void write_convert_buffer(int write_length,float *buf)
{
int length = 0;
while ((write_length != length) && (((s_convert_buffer_head + 1) & (CONVERT_BUFFER_SIZE-1)) != s_convert_buffer_tail))
{
s_convert_buffer[s_convert_buffer_head] = buf[length++];
s_convert_buffer_head = (s_convert_buffer_head+1) & (CONVERT_BUFFER_SIZE-1);
}
if ( length < write_length )
{
printf (" Overrun of %d\n", write_length - length);
}
while(length < write_length)
{
buf[length++] = 0;
}
}
/**
* Initialise the conversion buffer empty
*/
void init_convert_buffer()
{
s_convert_buffer_head = 0;
s_convert_buffer_tail = 0;
}
/**
* Called by jack, read samples from convert buffer into jack buffer
*/
int jack_process_callback (jack_nframes_t nframes, void *arg)
{
float *buf = jack_port_get_buffer (s_port, nframes);
read_convert_buffer(nframes, buf);
return 0;
}
/**
* Handler to shutdown gracefully
*/
void sigint_handler(int signo)
{
if (signo == SIGINT)
{
s_running = 0;
}
}
int main (int argc, char *argv[])
{
jack_client_t *client; // Pointer to jack client instance
jack_port_t *port; // Pointer to jack port instance
snd_pcm_hw_params_t *hw_params; // Alsa struct pointer for hardware parameters
snd_pcm_sw_params_t *sw_params; // Alsa struct pointer for software parameters
snd_pcm_sframes_t frames_to_deliver; // Count of frames alsa will deliver
snd_pcm_t *capture_handle; // Handle for alsa instance
unsigned int sample_rate = INPUT_SAMPLE_RATE; // Sample rate for Alsa
SRC_DATA src_data; // Control data structure for libsamplerate
SRC_STATE *src_state; // State structure for resampling
int sample_err; // Error value for libsamplerate
float resample_buf[INTERNAL_BUF_SIZE*10]; // Buffer to store samples at new sample rate
short alsa_rd_buf[INTERNAL_BUF_SIZE]; // Buffer to read alsa samples into
float float_buf[INTERNAL_BUF_SIZE]; // Buffer to hold alsa samples after conversion to float
int nfds;
int err;
int i;
struct pollfd *pfds;
//Initialise signal handling
if (signal(SIGINT, sigint_handler) == SIG_ERR)
{
fprintf (stderr, "cannot handle sigint\n");
exit(1);
}
//Initialise the conversion ringbuffer
init_convert_buffer();
//Initialise alsa
if ((err = snd_pcm_open (&capture_handle, argv[1], SND_PCM_STREAM_CAPTURE, 0)) < 0)
{
fprintf (stderr, "cannot open audio device %s (%s)\n",
argv[1],
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0)
{
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0)
{
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
fprintf (stderr, "cannot set access type (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0)
{
fprintf (stderr, "cannot set sample format (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, &sample_rate, 0)) < 0)
{
fprintf (stderr, "cannot set sample rate (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, CHANNELS)) < 0)
{
fprintf (stderr, "cannot set channel count (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0)
{
fprintf (stderr, "cannot set parameters (%s)\n",
snd_strerror (err));
exit (1);
}
snd_pcm_hw_params_free (hw_params);
//Alsa HW setup complete
//Tell Alsa to notify us each time INTERNAL_BUF_SIZE samples is available
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0)
{
fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_current (capture_handle, sw_params)) < 0)
{
fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_set_avail_min (capture_handle, sw_params, INTERNAL_BUF_SIZE)) < 0)
{
fprintf (stderr, "cannot set minimum available count (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_set_start_threshold (capture_handle, sw_params, 0U)) < 0)
{
fprintf (stderr, "cannot set start mode (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params (capture_handle, sw_params)) < 0)
{
fprintf (stderr, "cannot set software parameters (%s)\n",
snd_strerror (err));
exit (1);
}
//Tell alsa to goooooooo!
if ((err = snd_pcm_prepare (capture_handle)) < 0)
{
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
//Initialise jack
if ((client = jack_client_open ("btheadset_in", 0, NULL)) == 0)
{
fprintf (stderr, "jack server not running?\n");
return 1;
}
s_port = jack_port_register (client, "capture_1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
s_jack_sample_rate = jack_get_sample_rate( client );
jack_set_process_callback (client, jack_process_callback, 0);
if (jack_activate (client))
{
fprintf (stderr, "cannot activate client");
return 1;
}
//Setup control for libsamplerate
src_state = src_new (SRC_SINC_FASTEST, CHANNELS, &sample_err);
if (src_state == NULL)
{
fprintf (stderr, "Opening libsamplerate error %d\n", sample_err);
return 1;
}
src_data.data_in = float_buf;
src_data.output_frames = INTERNAL_BUF_SIZE*10;
src_data.data_out = resample_buf;
src_data.src_ratio = (double)s_jack_sample_rate / (double)INPUT_SAMPLE_RATE;
src_data.end_of_input = 0;
//Now goooo!
while (s_running)
{
//Wait until we have the data we need
if ((err = snd_pcm_wait (capture_handle, 1000)) < 0)
{
fprintf (stderr, "poll failed (%s)\n", strerror (errno));
break;
}
//Check enough data is available
if ((frames_to_deliver = snd_pcm_avail_update (capture_handle)) < 0)
{
if (frames_to_deliver == -EPIPE)
{
fprintf (stderr, "an xrun occured\n");
break;
}
else
{
fprintf (stderr, "unknown ALSA avail update return value (%d)\n",
(int)frames_to_deliver);
break;
}
}
//Deliver up to an entire buffer of data
frames_to_deliver = frames_to_deliver > INTERNAL_BUF_SIZE ? INTERNAL_BUF_SIZE : frames_to_deliver;
if ((err = snd_pcm_readi (capture_handle, alsa_rd_buf, frames_to_deliver)) < 0)
{
fprintf (stderr, "read failed (%s)\n", snd_strerror (err));
}
else
{
//Do SI16->float conversion
for ( i = 0; i < err; i++ )
{
float_buf[i] = alsa_rd_buf[i] * (1.0/SHRT_MAX);
}
//Do resampling
src_data.input_frames = err;
src_process (src_state, &src_data);
//Write to the output buffer
write_convert_buffer(src_data.output_frames_gen, resample_buf);
}
}
jack_port_unregister (client, port);
jack_client_close (client);
src_delete(src_state);
snd_pcm_close (capture_handle);
exit (0);
}
all:
gcc bt_alsa_in.c -o bt_alsa_in -ljack -lasound -lsamplerate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment