serval-dna/pa_phone.c
Andrew Bettison 71ed78e058 Make all 'config' tests pass
Change a test case: configuration options are now case sensitive.

Fix config file load and parse logic in conf.c, always copy 'debug' flags
from config.debug.

The config schema 'interfaces' option is no longer MANDATORY.

Introduce new CLIFLAG_PERMISSIVE_CONFIG to supress bad-config ERROR messages
from the 'config set' and 'config get' commands.

Refactor cli_execute() into cli_parse() and cli_invoke().  Use *const* struct
command_line_option everywhere.
2012-12-04 16:52:49 +10:30

260 lines
5.9 KiB
C

#include <codec2.h>
#include <spandsp.h>
#include "fifo.h"
#include <portaudio.h>
#include <pthread.h>
#include <samplerate.h>
#include "serval.h"
/* Defines */
#define IN_FRAMES 128
#define NUM_BUFS (8)
#define ECHO_LEN (128)
#define ADAPT_MODE (ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP | ECHO_CAN_USE_CNG)
#define CODEC2_BYTES_PER_FRAME ((CODEC2_BITS_PER_FRAME + 7) / 8)
/* Prototypes */
typedef struct {
PaStream *stream;
SRC_STATE *src;
pthread_mutex_t mtx; /* Mutex for frobbing queues */
/* Incoming samples after decompression
* Written with result of recvfrom + codec2_decode
* Read by sample rate converter
*/
struct fifo *incoming;
int incoverflow;
/* Samples after rate conversion
* Written by sample rate converter
* Read by PA callback
*/
struct fifo *incrate;
int underrun;
/* Outgoing samples
* Written by PA callback
* Read by codec2_encode + sendto
*/
struct fifo *outgoing;
int overrun;
echo_can_state_t *echocan; /* Echo canceller state */
void *codec2; /* Codec2 state */
} PaCtx;
static void freectx(PaCtx *ctx);
static int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
void *userData);
static PaCtx *pa_phone_setup(void);
/* Declarations */
int
app_pa_phone(int argc, const char *const *argv, const struct command_line_option *o)
{
PaCtx *ctx;
if ((ctx = pa_phone_setup()) == NULL)
return -1;
freectx(ctx);
return 0;
}
/* This routine will be called by the PortAudio engine when audio is needed.
** It may called at interrupt level on some machines so don't do anything
** that could mess up the system like calling malloc() or free().
*/
static int
patestCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData) {
PaCtx *ctx;
int16_t *in, *out;
int avail, amt;
ctx = (PaCtx *)userData;
out = (int16_t *)outputBuffer;
in = (int16_t *)inputBuffer;
pthread_mutex_lock(&ctx->mtx);
amt = framesPerBuffer * sizeof(out[0]);
/* Copy out samples to be played */
if ((avail = fifo_get(ctx->incrate, (uint8_t *)out, amt)) < amt) {
/* Zero out samples there are no data for */
bzero(out + (avail / sizeof(out[0])), amt - avail);
ctx->underrun += (amt - avail) / sizeof(out[0]);
}
/* Copy in samples to be recorded */
if ((avail = fifo_put(ctx->outgoing, (uint8_t *)in, amt)) < amt) {
/* Zero out samples there are no data for */
bzero(in + (avail / sizeof(out[0])), amt - avail);
ctx->overrun += (amt - avail) / sizeof(out[0]);
}
#if 1
/* Run the echo canceller */
for (int ofs = 0; ofs < framesPerBuffer; ofs++)
out[ofs] = echo_can_update(ctx->echocan, in[ofs], out[ofs]);
#endif
pthread_mutex_unlock(&ctx->mtx);
return paContinue;
}
static PaCtx *
pa_phone_setup(void) {
PaCtx *ctx;
int err, i, srcerr;
PaError err2;
err = paNoError;
err2 = 0;
if ((ctx = calloc(1, sizeof(PaCtx))) == NULL) {
WHY("Unable to allocate PA context");
err2 = 1;
goto error;
}
/* Init mutex */
if (pthread_mutex_init(&ctx->mtx, NULL) != 0) {
WHYF("Unable to init mutex: %s\n", strerror(errno));
err2 = 1;
goto error;
}
/* Allocate FIFOs */
i = IN_FRAMES * 10 * sizeof(int16_t);
printf("Allocating %d byte FIFOs\n", i);
if ((ctx->incoming = fifo_alloc(i)) == NULL) {
WHY("Unable to allocate incoming FIFO\n");
err2 = 1;
goto error;
}
if ((ctx->incrate = fifo_alloc(i)) == NULL) {
WHY("Unable to allocate incoming SRC FIFO\n");
err2 = 1;
goto error;
}
if ((ctx->outgoing = fifo_alloc(i)) == NULL) {
WHY("Unable to allocate outgoing FIFO\n");
err2 = 1;
goto error;
}
/* Init sample rate converter */
if ((ctx->src = src_new(SRC_SINC_BEST_QUALITY, 1, &srcerr)) == NULL) {
WHYF("Unable to init sample rate converter: %d\n", srcerr);
err2 = 1;
goto error;
}
/* Init echo canceller */
if ((ctx->echocan = echo_can_init(ECHO_LEN, ADAPT_MODE)) == NULL) {
WHY("Unable to init echo canceller\n");
err2 = 1;
goto error;
}
/* Init codec2 */
if ((ctx->codec2 = codec2_create()) == NULL) {
WHY("Unable to init codec2\n");
err2 = 1;
goto error;
}
/* Initialize Port Audio library */
if ((err = Pa_Initialize()) != paNoError)
goto error;
/* Open an audio I/O stream. */
if ((err = Pa_OpenDefaultStream(&ctx->stream,
1, /* input channels */
1, /* output channels */
paInt16,
SAMPLE_RATE,
IN_FRAMES, /* frames per buffer */
patestCallback,
&ctx)) != paNoError)
goto error;
/* Start stream */
if ((err = Pa_StartStream(ctx->stream)) != paNoError)
goto error;
/* Close down stream, PA, etc */
/* XXX: hangs in pthread_join on Ubuntu 10.04 */
#ifndef linux
if ((err = Pa_StopStream(ctx->stream)) != paNoError)
goto error;
#endif
/* Do stuff */
if ((err = Pa_CloseStream(ctx->stream)) != paNoError)
goto error;
error:
Pa_Terminate();
/* Free things */
freectx(ctx);
if (err != paNoError)
WHYF("Port audio error: %s\n", Pa_GetErrorText(err));
return NULL;
}
static void
freectx(PaCtx *ctx) {
/* Destroy mutex */
pthread_mutex_destroy(&ctx->mtx);
/* Free SRC resources */
if (ctx->src != NULL)
src_delete(ctx->src);
/* Free echo caneller */
if (ctx->echocan != NULL)
echo_can_free(ctx->echocan);
/* Free codec2 */
if (ctx->codec2 != NULL)
codec2_destroy(ctx->codec2);
/* Free FIFOs */
if (ctx->incoming != NULL)
fifo_free(ctx->incoming);
if (ctx->incrate != NULL)
fifo_free(ctx->incrate);
if (ctx->outgoing != NULL)
fifo_free(ctx->outgoing);
}
/*
* Local variables:
* c-basic-offset: 2
* End:
*/