Created
January 16, 2015 15:12
-
-
Save mike7c2/3c4825278c0217d7f8a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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