
//
//    This file is part of Dire Wolf, an amateur radio packet TNC.
//
//    Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//    Copyright (C) 2015 Robert Stiles, KK5VD
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 2 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/>.
//

/*------------------------------------------------------------------
 *
 * Module:  audio_portaudio.c
 *
 * Purpose: Interface to audio device commonly called a "sound card" for
 *          historical reasons.
 *
 * This version is for Various OS' using Port Audio
 *
 * Major Revisions:
 *
 *		1.2 - Add ability to use more than one audio device.
 *		1.3 - New file added for Port Audio for Mac and possibly others.
 *
 *---------------------------------------------------------------*/

#if	defined(USE_PORTAUDIO)

#include "direwolf.h"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <assert.h>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>

#include "audio.h"
#include "audio_stats.h"
#include "textcolor.h"
#include "dtime_now.h"
#include "demod.h"		/* for alevel_t & demod_get_audio_level() */

#include "portaudio.h"

/* Audio configuration. */

static struct audio_s          *save_audio_config_p;

/* Current state for each of the audio devices. */

static struct adev_s {

	pthread_mutex_t input_mutex;
	pthread_cond_t  input_cond;

	PaStream *inStream;
	PaStreamParameters inputParameters;
	int pa_input_device_number;
	int no_of_input_channels;
	int input_finished;
	int input_pause;
	int input_flush;

	void *audio_in_handle;
	int inbuf_size_in_bytes;	  /* number of bytes allocated */
	unsigned char *inbuf_ptr;
	int inbuf_len;				  /* number byte of actual data available. */
	int inbuf_next;				  /* index of next to remove. */
	int inbuf_bytes_per_frame;	  /* number of bytes for a sample from all channels. */
	int inbuf_frames_per_buffer;  /* number of frames in a buffer. */

	pthread_mutex_t output_mutex;
	pthread_cond_t  output_cond;

	PaStream *outStream;
	PaStreamParameters outputParameters;
	int pa_output_device_number;
	int no_of_output_channels;
	int output_pause;
	int output_finished;
	int output_flush;
	int output_wait_flag;

	void *audio_out_handle;
	int outbuf_size_in_bytes;
	unsigned char *outbuf_ptr;
	int outbuf_len;
	int outbuf_next;			  /* index of next to remove. */
	int outbuf_bytes_per_frame;   /* number of bytes for a sample from all channels. */
	int outbuf_frames_per_buffer; /* number of frames in a buffer. */

	enum audio_in_type_e g_audio_in_type;

	int udp_sock;			      /* UDP socket for receiving data */

} adev[MAX_ADEVS];

// Originally 40.  Version 1.2, try 10 for lower latency.

#define ONE_BUF_TIME 10
#define SAMPLE_SILENCE 0

#define PA_INPUT  1
#define PA_OUTPUT 2

#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)

#undef FOR_FUTURE_USE

static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *devname, char *inout);
static void print_pa_devices(void);
static int check_pa_configure(struct adev_s *dev, int sample_rate);
static void list_supported_sample_rates(struct adev_s *dev);
static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo);
static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag);
static int calcbufsize(int rate, int chans, int bits);


static int calcbufsize(int rate, int chans, int bits)
{
	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
	int size2 = roundup1k(size1);
#if DEBUG
	text_color_set(DW_COLOR_DEBUG);
	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
			   rate, chans, bits, size1, size2);
#endif
	return (size2);
}

/*------------------------------------------------------------------
 * Search the portaudio device tree looking for the request device.
 * One of the issues with portaudio has to do with devices returning
 * the same device name for more then one connected device
 * (ie two SignaLinks). Appending a Portaudio device index to the
 * the device name ensure we can find the correct one. And if it's not
 * available return the first occurrence that matches the device name.
 *----------------------------------------------------------------*/
static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag)
{
	int numDevices = Pa_GetDeviceCount();
	const PaDeviceInfo * di = (PaDeviceInfo *)0;
	int i = 0;

	// First check to see if the requested index matches the device name.
	if(reqDeviceNo < numDevices) {
		di = Pa_GetDeviceInfo((PaDeviceIndex) reqDeviceNo);
		if(strncmp(di->name, _devName, 80) == 0) {
			if((io_flag == PA_INPUT) && di->maxInputChannels)
				return reqDeviceNo;
			if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
				return reqDeviceNo;
		}
	}

	// Requested device index doesn't match device name. Search for one.
	for(i = 0; i < numDevices; i++) {
		di = Pa_GetDeviceInfo((PaDeviceIndex) i);
		if(strncmp(di->name, _devName, 80) == 0) {
			if((io_flag == PA_INPUT) && di->maxInputChannels)
				return i;
			if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
				return i;
		}
	}

	// No Matches found
	return -1;
}

/*------------------------------------------------------------------
 * Extract device name and number.
 *----------------------------------------------------------------*/
static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo)
{
	char *cPtr = (char *)0;
	char cVal = 0;
	int count = 0;
	char numStr[8];

	if(!deviceStr || !_devName || !_devNo) {
		dw_printf( "Internal Error: Func %s passed null pointer.\n", __func__);
		return -1;
	}

	cPtr = deviceStr;

	memset(_devName, 0, length);
	memset(numStr, 0, sizeof(numStr));

	while(*cPtr) {
		cVal = *cPtr++;
		if(cVal == ':')  break;

		// See Issue 417.
		// Originally this copied only printable ASCII characters (space thru ~).
		// That is a problem for some locales that use UTF-8 characters in the device name.
		// original: if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) {

		// At first I was thinking we should keep the test for < ' ' but then I
		// remembered that char type can be signed or unsigned depending on implementation.
		// If characters are signed then a value above 0x7f would be considered negative.

		// It seems to me that the test for buffer full is off by one.
		// count could reach length, leaving no room for a nul terminator.
		// Compare has been changed so count is limited to length minus 1.

		if(count < length - 1) {
			_devName[count++] = cVal;
		}

	}

	count = 0;

	while(*cPtr) {
		cVal = *cPtr++;
		if(isdigit(cVal) && (count < (sizeof(numStr) - 1))) {
			numStr[count++] = cVal;
		}
	}

	if(numStr[0] == 0) {
		*_devNo = 0;
	} else {
		sscanf(numStr, "%d", _devNo);
	}

	return 0;
}

/*------------------------------------------------------------------
 * List the supported sample rates.
 *----------------------------------------------------------------*/
static void list_supported_sample_rates(struct adev_s *dev)
{
	static double standardSampleRates[] = {
		8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0,
		44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated  list */
	};
	int     i, printCount;
	PaError err;

	printCount = 0;
	for(i = 0; standardSampleRates[i] > 0; i++ ) {
		err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, standardSampleRates[i] );
		if( err == paFormatIsSupported ) {
			if( printCount == 0 ) {
				dw_printf( "\t%8.2f", standardSampleRates[i] );
				printCount = 1;
			}
			else if( printCount == 4 ) {
				dw_printf( ",\n\t%8.2f", standardSampleRates[i] );
				printCount = 1;
			}
			else {
				dw_printf( ", %8.2f", standardSampleRates[i] );
				++printCount;
			}
		}
	}

	if( !printCount )
		dw_printf( "None\n" );
	else
		dw_printf( "\n" );
}

/*------------------------------------------------------------------
 * Check PA Configure parameters.
 *----------------------------------------------------------------*/
static int check_pa_configure(struct adev_s *dev, int sample_rate)
{
	if(!dev) {
		dw_printf( "Internal Error: Func %s struct adev_s *dev null pointer.\n", __func__);
		return -1;
	}

	PaError err = 0;
	err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, sample_rate);
	if(err == paFormatIsSupported) return 0;
	dw_printf( "PortAudio Config Error: %s\n", Pa_GetErrorText(err));
	return err;
}

/*------------------------------------------------------------------
 * Print a list of device names and parameters
 *----------------------------------------------------------------*/
static void print_pa_devices(void)
{
	int     i, numDevices, defaultDisplayed;
	const   PaDeviceInfo *deviceInfo;

	numDevices = Pa_GetDeviceCount();

	if( numDevices < 0 ) {
		dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
		return;
	}

	dw_printf( "Number of devices = %d\n", numDevices );

	for(i = 0; i < numDevices; i++ ) {
		deviceInfo = Pa_GetDeviceInfo( i );
		dw_printf( "--------------------------------------- device #%d\n", i );

		/* Mark global and API specific default devices */
		defaultDisplayed = 0;
		if( i == Pa_GetDefaultInputDevice() ) {
			dw_printf( "[ Default Input" );
			defaultDisplayed = 1;
		}
		else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) {
			const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
			dw_printf( "[ Default %s Input", hostInfo->name );
			defaultDisplayed = 1;
		}

		if( i == Pa_GetDefaultOutputDevice() ) {
			dw_printf( (defaultDisplayed ? "," : "[") );
			dw_printf( " Default Output" );
			defaultDisplayed = 1;
		}
		else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) {
			const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
			dw_printf( (defaultDisplayed ? "," : "[") );
			dw_printf( " Default %s Output", hostInfo->name );
			defaultDisplayed = 1;
		}

		if( defaultDisplayed )
			dw_printf( " ]\n" );

		/* print device info fields */
		dw_printf( "Name        = \"%s\"\n", deviceInfo->name );
		dw_printf( "Host API    = %s\n",     Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
		dw_printf( "Max inputs  = %d\n",     deviceInfo->maxInputChannels  );
		dw_printf( "Max outputs = %d\n",     deviceInfo->maxOutputChannels  );
	}
}

/*------------------------------------------------------------------
 * Port Audio Input Callback
 *----------------------------------------------------------------*/
static int paInput16CB( const void *inputBuffer, void *outputBuffer,
					   unsigned long framesPerBuffer,
					   const PaStreamCallbackTimeInfo* timeInfo,
					   PaStreamCallbackFlags statusFlags,
					   void *userData )
{
	struct adev_s *data = (struct adev_s *) userData;
	const int16_t *rptr = (const int16_t *) inputBuffer;
	size_t framesToCalc = 0;
	size_t i = 0;
	int finished = 0;
	int word = 0;
	size_t bytes_left = data->inbuf_size_in_bytes - data->inbuf_len;
	size_t framesLeft = bytes_left / data->inbuf_bytes_per_frame;

	(void) outputBuffer; /* Prevent unused variable warnings. */
	(void) timeInfo;
	(void) statusFlags;
	(void) userData;

	if( framesLeft < framesPerBuffer ) {
		framesToCalc = framesLeft;
		finished = paComplete;
	} else {
		framesToCalc = framesPerBuffer;
		finished = paContinue;
	}

	if( inputBuffer == NULL || data->input_flush) {
		for(i = 0; i < framesToCalc; i++) {
			data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
			data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
			if(data->no_of_input_channels == 2) {
				data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
				data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
			}
		}
	} else {
		for(i = 0; i < framesToCalc; i++) {
			word = *rptr++;  /* left */
			data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
			data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;

			if(data->no_of_input_channels == 2) {
				word = *rptr++;  /* right */
				data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
				data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;
			}
		}
	}

	if((finished == paComplete) ||
	   (data->inbuf_len >= data->inbuf_size_in_bytes)) {
		pthread_cond_signal(&data->input_cond);
		finished = data->input_finished;
	}

	return finished;
}

#if FOR_FUTURE_USE
/*------------------------------------------------------------------
 * Port Audio Output Callback
 *----------------------------------------------------------------*/
static int paOutput16CB( const void *inputBuffer, void *outputBuffer,
						unsigned long framesPerBuffer,
						const PaStreamCallbackTimeInfo* timeInfo,
						PaStreamCallbackFlags statusFlags,
						void *userData)
{
	struct adev_s *data = (struct adev_s *) userData;
	int16_t *wptr = (int16_t *) outputBuffer;
	size_t i = 0;
	int finished = 0;
	size_t bytes_left = data->outbuf_size_in_bytes - data->outbuf_len;
	size_t framesLeft = bytes_left / data->outbuf_bytes_per_frame;
	int word = 0;

	(void) inputBuffer; /* Prevent unused variable warnings. */
	(void) timeInfo;
	(void) statusFlags;
	(void) userData;

	if(framesLeft && (framesLeft < framesPerBuffer)) {
		/* final buffer... */
		for(i = 0; i < framesLeft; i++ ) {
			word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
			word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
			*wptr++ = word;  /* left */
			if(data->no_of_output_channels == 2 ) {
				word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
				word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
				*wptr++ = word;  /* right */
			}
		}
		for( ; i < framesPerBuffer; i++ ) {
			*wptr++ = 0;  	/* left */
			if(data->no_of_output_channels == 2 )
				*wptr++ = 0;  /* right */
		}
		finished = paContinue;
	} else {
		for(i = 0; i < framesPerBuffer; i++ ) {
			word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
			word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
			*wptr++ = word;  /* left */
			if(data->no_of_output_channels == 2) {
				word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
				word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
				*wptr++ = word;  /* right */
			}
		}
		finished = paComplete;
	}

	if(data->output_flush) {
		data->output_flush = 0;
		finished = paComplete;
	}

	pthread_cond_signal(&data->output_cond);
	finished = data->output_finished;

	return finished;
}
#endif

/*------------------------------------------------------------------
 *
 * Name:        audio_open
 *
 * Purpose:     Open the digital audio device.
 *
 *		New in version 1.0, we recognize "udp:" optionally
 *		followed by a port number.
 *
 * Inputs:      pa		- Address of structure of type audio_s.
 *
 *				Using a structure, rather than separate arguments
 *				seemed to make sense because we often pass around
 *				the same set of parameters various places.
 *
 *				The fields that we care about are:
 *					num_channels
 *					samples_per_sec
 *					bits_per_sample
 *				If zero, reasonable defaults will be provided.
 *
 *				The device names are in adevice_in and adevice_out.
 *				   where c is the "card" (for historical purposes)
 *				   and d is the "device" within the "card."
 *
 *
 * Outputs:	pa  - The ACTUAL values are returned here.
 *
 *				These might not be exactly the same as what was requested.
 *
 *				Example: ask for stereo, 16 bits, 22050 per second.
 *				An ordinary desktop/laptop PC should be able to handle this.
 *				However, some other sort of smaller device might be
 *				more restrictive in its capabilities.
 *				It might say, the best I can do is mono, 8 bit, 8000/sec.
 *
 *				The software modem must use this ACTUAL information
 *				that the device is supplying, that could be different
 *				than what the user specified.
 *
 * Returns:     0 for success, -1 for failure.
 *
 *
 *----------------------------------------------------------------*/

int audio_open (struct audio_s *pa)
{
	int err  = 0;
	int chan = 0;
	int a    = 0;
	int clear_value = 0;
	char audio_in_name[80];
	char audio_out_name[80];
	static int initalize_flag = 0;
	PaError paerr = paNoError;

	if(!initalize_flag) {
		paerr = Pa_Initialize();
		initalize_flag = -1;
	}

	if(paerr != paNoError ) return -1;

	save_audio_config_p = pa;

	memset (adev,           0, sizeof(adev));
	memset (audio_in_name,  0, sizeof(audio_in_name));
	memset (audio_out_name, 0, sizeof(audio_out_name));

	for (a = 0; a < MAX_ADEVS; a++) {
		adev[a].udp_sock = -1;
	}

	/*
	 * Fill in defaults for any missing values.
	 */

	for (a = 0; a < MAX_ADEVS; a++) {
		if (pa->adev[a].num_channels == 0)
			pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;

		if (pa->adev[a].samples_per_sec == 0)
			pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;

		if (pa->adev[a].bits_per_sample == 0)
			pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;

		for (chan = 0; chan < MAX_CHANS; chan++) {
			if (pa->achan[chan].mark_freq == 0)
				pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;

			if (pa->achan[chan].space_freq == 0)
				pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;

			if (pa->achan[chan].baud == 0)
				pa->achan[chan].baud = DEFAULT_BAUD;

			if (pa->achan[chan].num_subchan == 0)
				pa->achan[chan].num_subchan = 1;
		}
	}

	/*
	 * Open audio device(s).
	 */

	for (a = 0; a < MAX_ADEVS; a++) {
		if (pa->adev[a].defined) {

			adev[a].inbuf_size_in_bytes = 0;
			adev[a].outbuf_size_in_bytes = 0;

			/*
			 * Determine the type of audio input.
			 */

			adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;

			if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
				adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
				/* Change "-" to stdin for readability. */
				strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
			}

			if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
				adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
				/* Supply default port if none specified. */
				if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
					strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
					snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
				}
			}

			/* Let user know what is going on. */
			/* If not specified, the device names should be "default". */

			strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name));
			strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name));

			char ctemp[40];

			if (pa->adev[a].num_channels == 2) {
				snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
			} else {
				snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a));
			}

			text_color_set(DW_COLOR_INFO);

			if (strcmp(audio_in_name,audio_out_name) == 0) {
				dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
			} else {
				dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
				dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
			}

			/*
			 * Now attempt actual opens.
			 */

			/*
			 * Input device.
			 */

			switch (adev[a].g_audio_in_type) {

				case AUDIO_IN_TYPE_SOUNDCARD:
					print_pa_devices();
					err = set_portaudio_params (a, &adev[a], pa, audio_in_name, audio_out_name);
					if(err < 0) return -1;

					pthread_mutex_init(&adev[a].input_mutex, NULL);
					pthread_cond_init(&adev[a].input_cond, NULL);

					pthread_mutex_init(&adev[a].output_mutex, NULL);
					pthread_cond_init(&adev[a].output_cond, NULL);

					if(pa->adev[a].bits_per_sample == 8)
						clear_value = 128;
					else
						clear_value = 0;

					break;

					/*
					 * UDP.
					 */
				case AUDIO_IN_TYPE_SDR_UDP:

					// Create socket and bind socket

				{
					struct sockaddr_in si_me;
					//Create UDP Socket
					if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
						text_color_set(DW_COLOR_ERROR);
						dw_printf ("Couldn't create socket, errno %d\n", errno);
						return -1;
					}

					memset((char *) &si_me, 0, sizeof(si_me));
					si_me.sin_family = AF_INET;
					si_me.sin_port = htons((short)atoi(audio_in_name+4));
					si_me.sin_addr.s_addr = htonl(INADDR_ANY);

					//Bind to the socket
					if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
						text_color_set(DW_COLOR_ERROR);
						dw_printf ("Couldn't bind socket, errno %d\n", errno);
						return -1;
					}
				}
					//adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN;

					break;

					/*
					 * stdin.
					 */
				case AUDIO_IN_TYPE_STDIN:

					/* Do we need to adjust any properties of stdin? */

					//adev[a].inbuf_size_in_bytes = 1024;

					break;

				default:

					text_color_set(DW_COLOR_ERROR);
					dw_printf ("Internal error, invalid audio_in_type\n");
					return (-1);
			}

			/*
			 * Finally allocate buffer for each direction.
			 */

	                /* Version 1.3 - Add sanity check on buffer size. */
	                /* There was a reported case of assert failure on buffer size in audio_get(). */

	                if (adev[a].inbuf_size_in_bytes < 256 || adev[a].inbuf_size_in_bytes > 32768) {
	                  text_color_set(DW_COLOR_ERROR);
	                  dw_printf ("Audio input buffer has unexpected extreme size of %d bytes.\n", adev[a].inbuf_size_in_bytes);
	                  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
	                  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
	                  adev[a].inbuf_size_in_bytes = 2048;
	                  dw_printf ("Using %d to attempt recovery.\n", adev[a].inbuf_size_in_bytes);
	                }

	                if (adev[a].outbuf_size_in_bytes < 256 || adev[a].outbuf_size_in_bytes > 32768) {
	                  text_color_set(DW_COLOR_ERROR);
	                  dw_printf ("Audio output buffer has unexpected extreme size of %d bytes.\n", adev[a].outbuf_size_in_bytes);
	                  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
	                  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
	                  adev[a].outbuf_size_in_bytes = 2048;
	                  dw_printf ("Using %d to attempt recovery.\n", adev[a].outbuf_size_in_bytes);
	                }

			adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
			assert (adev[a].inbuf_ptr != NULL);
			adev[a].inbuf_len = 0;
			adev[a].inbuf_next = 0;
			memset(adev[a].inbuf_ptr, clear_value, adev[a].inbuf_size_in_bytes);

			adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
			assert (adev[a].outbuf_ptr != NULL);
			adev[a].outbuf_len = 0;
			adev[a].outbuf_next = 0;
			memset(adev[a].outbuf_ptr, clear_value, adev[a].outbuf_size_in_bytes);

			if(adev[a].inStream) {
				err = Pa_StartStream(adev[a].inStream);
				if(err != paNoError) {
					dw_printf ("Input stream start Error %s\n", Pa_GetErrorText(err));
				}
			}

			if(adev[a].outStream) {
				err = Pa_StartStream(adev[a].outStream);
				if(err != paNoError) {
					dw_printf ("Output stream start Error %s\n", Pa_GetErrorText(err));
				}
			}
		} /* end of audio device defined */
	} /* end of for each audio device */

	return (0);

} /* end audio_open */


/*
 * Set parameters for sound card.
 *
 * See  ??  for details.
 */
static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *_audio_in_name, char *_audio_out_name)
{
	int numDevices  = 0;
	int err = 0;
	int buffer_size = 0;
	int sampleFormat = 0;
	int no_of_bytes_per_sample = 0;
	int reqInDeviceNo  = 0;
	int reqOutDeviceNo = 0;
	char input_devName[80];
	char output_devName[80];

	text_color_set(DW_COLOR_ERROR);

	if(!dev || !pa || !_audio_in_name || !_audio_out_name) {
		dw_printf ("Internal error, invalid function parameter pointer(s) (null)\n");
		return -1;
	}

	if(_audio_in_name[0] == 0) {
		dw_printf ("Input device name null\n");
		return -1;
	}

	if(_audio_out_name[0] == 0) {
		dw_printf ("Output device name null\n");
		return -1;
	}

	numDevices = Pa_GetDeviceCount();
	if( numDevices < 0 ) {
		dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
		return -1;
	}

	err = pa_devNN(_audio_in_name, input_devName, sizeof(input_devName), &reqInDeviceNo);
	if(err < 0)	return -1;

	reqInDeviceNo = searchPADevice(dev, input_devName, reqInDeviceNo, PA_INPUT);
	if(reqInDeviceNo < 0) {
		dw_printf ("Requested Input Audio Device not found %s.\n", input_devName);
		return -1;
	}

	err = pa_devNN(_audio_out_name, output_devName, sizeof(output_devName), &reqOutDeviceNo);
	if(err < 0)	return -1;

	reqOutDeviceNo = searchPADevice(dev, output_devName, reqOutDeviceNo, PA_OUTPUT);
	if(reqOutDeviceNo < 0) {
		dw_printf ("Requested Output Audio Device not found %s.\n", output_devName);
		return -1;
	}

	dev->pa_input_device_number  = reqInDeviceNo;
	dev->pa_output_device_number = reqOutDeviceNo;

	switch(pa->adev[a].bits_per_sample) {
		case 8:
			sampleFormat = paInt8;
			no_of_bytes_per_sample = sizeof(int8_t);
			assert("int8_t size not equal to 1" && sizeof(int8_t) == 1);
			break;

		case 16:
			sampleFormat = paInt16;
			no_of_bytes_per_sample = sizeof(int16_t);
			assert("int16_t size not equal to 2" && sizeof(int16_t) == 2);
			break;

		default:
			dw_printf ("Unsupported Sample Size %s.\n", output_devName);
			return -1;
	}


	buffer_size = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);

	dev->inbuf_size_in_bytes     = buffer_size;
	dev->inbuf_bytes_per_frame   = no_of_bytes_per_sample * pa->adev[a].num_channels;
	dev->inbuf_frames_per_buffer = dev->inbuf_size_in_bytes  / dev->inbuf_bytes_per_frame;

	dev->inputParameters.device       = dev->pa_input_device_number;
	dev->inputParameters.channelCount = pa->adev[a].num_channels;
	dev->inputParameters.sampleFormat = sampleFormat;
	dev->inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->inputParameters.device)->defaultLowInputLatency;
	dev->inputParameters.hostApiSpecificStreamInfo = NULL;

	dev->outbuf_size_in_bytes     = buffer_size;
	dev->outbuf_bytes_per_frame   = no_of_bytes_per_sample * pa->adev[a].num_channels;
	dev->outbuf_frames_per_buffer = dev->outbuf_size_in_bytes / dev->outbuf_bytes_per_frame;

	dev->outputParameters.device       = dev->pa_output_device_number;
	dev->outputParameters.channelCount = pa->adev[a].num_channels;
	dev->outputParameters.sampleFormat = sampleFormat;
	dev->outputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->outputParameters.device)->defaultHighOutputLatency;
	dev->outputParameters.hostApiSpecificStreamInfo = NULL;

	err = check_pa_configure(dev, pa->adev[a].samples_per_sec);
	if(err) {
		if(err == paInvalidSampleRate)
			list_supported_sample_rates(dev);
		return -1;
	}

	err = Pa_OpenStream(&dev->inStream,	&dev->inputParameters, NULL,
						pa->adev[a].samples_per_sec, dev->inbuf_frames_per_buffer, 0, paInput16CB, dev );

	if( err != paNoError ) {
		dw_printf( "PortAudio OpenStream (input) Error: %s\n", Pa_GetErrorText(err));
		return -1;
	}

	err = Pa_OpenStream(&dev->outStream, NULL, &dev->outputParameters,
						// pa->adev[a].samples_per_sec, framesPerBuffer, 0, paOutput16CB, dev );
						pa->adev[a].samples_per_sec, dev->outbuf_frames_per_buffer, 0, NULL, dev );

	if( err != paNoError ) {
		dw_printf( "PortAudio OpenStream (output) Error: %s\n", Pa_GetErrorText(err));
		return -1;
	}

	dev->input_finished  = paContinue;
	dev->output_finished = paContinue;

	return buffer_size;
}


/*------------------------------------------------------------------
 *
 * Name:        audio_get
 *
 * Purpose:     Get one byte from the audio device.
 *
 * Inputs:	a	- Our number for audio device.
 *
 * Returns:     0 - 255 for a valid sample.
 *              -1 for any type of error.
 *
 * Description:	The caller must deal with the details of mono/stereo
 *		and number of bytes per sample.
 *
 *		This will wait if no data is currently available.
 *
 *----------------------------------------------------------------*/

// Use hot attribute for all functions called for every audio sample.

__attribute__((hot))
int audio_get (int a)
{
	int n;
	int retries = 0;


#if DEBUGx
	text_color_set(DW_COLOR_DEBUG);

	dw_printf ("audio_get():\n");

#endif

	assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);

	switch (adev[a].g_audio_in_type) {

			/*
			 * Soundcard - PortAudio
			 */
		case AUDIO_IN_TYPE_SOUNDCARD:

			while (adev[a].inbuf_next >= adev[a].inbuf_len) {

				assert (adev[a].inStream != NULL);
#if DEBUGx
				text_color_set(DW_COLOR_DEBUG);
				dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
#endif
				if(adev[a].inbuf_len >= adev[a].inbuf_size_in_bytes) {
					adev[a].inbuf_len = 0;
					adev[a].inbuf_next = 0;
				}

				pthread_mutex_lock(&adev[a].input_mutex);
				pthread_cond_wait(&adev[a].input_cond, &adev[a].input_mutex);
				pthread_mutex_unlock(&adev[a].input_mutex);

				n = adev[a].inbuf_len / adev[a].inbuf_bytes_per_frame;
#if DEBUGx
				text_color_set(DW_COLOR_DEBUG);
				dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
						   adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);
#endif


				if (n > 0) {

					/* Success */

					adev[a].inbuf_len = n * adev[a].inbuf_bytes_per_frame;		/* convert to number of bytes */
					adev[a].inbuf_next = 0;

	        			audio_stats (a, 
						save_audio_config_p->adev[a].num_channels, 
						n, 
						save_audio_config_p->statistics_interval);

				}
				else if (n == 0) {

					/* Didn't expect this, but it's not a problem. */
					/* Wait a little while and try again. */

					text_color_set(DW_COLOR_ERROR);
					dw_printf ("[%s], Audio input got zero bytes\n", __func__);
					SLEEP_MS(10);

					adev[a].inbuf_len = 0;
					adev[a].inbuf_next = 0;
				}
				else {
					/* Error */
					// TODO: Needs more study and testing.

					// TODO: print n.  should snd_strerror use n or errno?
					// Audio input device error: Unknown error

					text_color_set(DW_COLOR_ERROR);
					dw_printf ("Audio input device %d error\n", a);

	        			audio_stats (a, 
						save_audio_config_p->adev[a].num_channels, 
						0, 
						save_audio_config_p->statistics_interval);

					/* Try to recover a few times and eventually give up. */
					if (++retries > 10) {
						adev[a].inbuf_len = 0;
						adev[a].inbuf_next = 0;
						return (-1);
					}

					if (n == -EPIPE) {

						/* EPIPE means overrun */

						//snd_pcm_recover (adev[a].audio_in_handle, n, 1);

					}
					else {
						/* Could be some temporary condition. */
						/* Wait a little then try again. */
						/* Sometimes I get "Resource temporarily available" */
						/* when the Update Manager decides to run. */

						SLEEP_MS (250);
						//snd_pcm_recover (adev[a].audio_in_handle, n, 1);
					}
				}
			}

			break;

			/*
			 * UDP.
			 */

		case AUDIO_IN_TYPE_SDR_UDP:

			while (adev[a].inbuf_next >= adev[a].inbuf_len) {
				int res;

				assert (adev[a].udp_sock > 0);
				res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
				if (res < 0) {
					text_color_set(DW_COLOR_ERROR);
					dw_printf ("Can't read from udp socket, res=%d", res);
					adev[a].inbuf_len = 0;
					adev[a].inbuf_next = 0;

	        			audio_stats (a, 
						save_audio_config_p->adev[a].num_channels, 
						0, 
						save_audio_config_p->statistics_interval);

					return (-1);
				}

				adev[a].inbuf_len = res;
				adev[a].inbuf_next = 0;

	      			audio_stats (a, 
					save_audio_config_p->adev[a].num_channels, 
					res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
					save_audio_config_p->statistics_interval);
			}
			break;

			/*
			 * stdin.
			 */
		case AUDIO_IN_TYPE_STDIN:

			while (adev[a].inbuf_next >= adev[a].inbuf_len) {
				int res;

				res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
				if (res <= 0) {
					text_color_set(DW_COLOR_INFO);
					dw_printf ("\nEnd of file on stdin.  Exiting.\n");
					exit (0);
				}

	      			audio_stats (a, 
					save_audio_config_p->adev[a].num_channels, 
					res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
					save_audio_config_p->statistics_interval);

				adev[a].inbuf_len = res;
				adev[a].inbuf_next = 0;
			}

			break;
	}


	if (adev[a].inbuf_next < adev[a].inbuf_len)
		n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
	else
		n = 0;

#if DEBUGx

	text_color_set(DW_COLOR_DEBUG);
	dw_printf ("audio_get(): returns %d\n", n);

#endif


	return (n);

} /* end audio_get */


/*------------------------------------------------------------------
 *
 * Name:        audio_put
 *
 * Purpose:     Send one byte to the audio device.
 *
 * Inputs:	a
 *
 *		c	- One byte in range of 0 - 255.
 *
 * Returns:     Normally non-negative.
 *              -1 for any type of error.
 *
 * Description:	The caller must deal with the details of mono/stereo
 *		and number of bytes per sample.
 *
 * See Also:	audio_flush
 *		audio_wait
 *
 *----------------------------------------------------------------*/
int audio_put (int a, int c)
{
	int err = 0;
	size_t frames = 0;

	//#define __TIMED__
#ifdef __TIMED__
	static int count = 0;
	static double start = 0, end = 0, diff = 0;

	if(adev[a].outbuf_len == 0)
		start = dtime_monotonic();
#endif

	if(c >= 0) {
		adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
	}

	if ((adev[a].outbuf_len >= adev[a].outbuf_size_in_bytes) || (c < 0)) {

		frames = adev[a].outbuf_len / adev[a].outbuf_bytes_per_frame;

		if(frames > 0) {
			err =  Pa_WriteStream(adev[a].outStream, adev[a].outbuf_ptr, frames);
		}

		// Getting underflow error for some reason on the first pass. Upon examination of the
		// audio data revealed no discontinuity in the signal. Time measurements indicate this routine
		// on this machine (2.8Ghz/Xeon E5462/2008 vintage) can handle ~6 times the current
		// sample rate (44100/2 bytes per frame). For now, mask the error.
		// Transfer Time:0.184750080 No of Frames:56264 Per frame:0.000003284 speed:6.905695

		if ((err != paNoError) && (err != paOutputUnderflowed)) {
			text_color_set(DW_COLOR_ERROR);
			dw_printf ("[%s] Audio Output Error: %s\n", __func__, Pa_GetErrorText(err));
		}

#ifdef __TIMED__
		count += frames;
		if(c < 0) { // When the Ax25 frames are flushed.
			end = dtime_monotonic();
			diff = end - start;
			if(count)
				dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n",
						   diff, count, diff/(count * 1.0), (1.0/44100.0)/(diff/(count * 1.0)));
			count = 0;
		}
#endif
		adev[a].outbuf_len  = 0;
		adev[a].outbuf_next = 0;
	}

	return (0);
}

/*------------------------------------------------------------------
 *
 * Name:        audio_flush
 *
 * Purpose:     Push out any partially filled output buffer.
 *
 * Returns:     Normally non-negative.
 *              -1 for any type of error.
 *
 * See Also:	audio_flush
 *		audio_wait
 *
 *----------------------------------------------------------------*/

int audio_flush (int a)
{
	audio_put(a, -1);
	return 0;
} /* end audio_flush */


/*------------------------------------------------------------------
 *
 * Name:        audio_wait
 *
 * Purpose:	Finish up audio output before turning PTT off.
 *
 * Inputs:	a		- Index for audio device (not channel!)
 *
 * Returns:     None.
 *
 * Description:	Flush out any partially filled audio output buffer.
 *		Wait until all the queued up audio out has been played.
 *		Take any other necessary actions to stop audio output.
 *
 * In an ideal world:
 *
 *		We would like to ask the hardware when all the queued
 *		up sound has actually come out the speaker.
 *
 * In reality:
 *
 * 		This has been found to be less than reliable in practice.
 *
 *		Caller does the following:
 *
 *		(1) Make note of when PTT is turned on.
 *		(2) Calculate how long it will take to transmit the
 *			frame including TXDELAY, frame (including
 *			"flags", data, FCS and bit stuffing), and TXTAIL.
 *		(3) Call this function, which might or might not wait long enough.
 *		(4) Add (1) and (2) resulting in when PTT should be turned off.
 *		(5) Take difference between current time and desired PPT off time
 *			and wait for additional time if required.
 *
 *----------------------------------------------------------------*/

void audio_wait (int a)
{
	audio_flush(a);
	
#if DEBUG
	text_color_set(DW_COLOR_DEBUG);
	dw_printf ("audio_wait(): after sync, status=%d\n", err);
#endif
} /* end audio_wait */


/*------------------------------------------------------------------
 *
 * Name:        audio_close
 *
 * Purpose:     Close the audio device(s).
 *
 * Returns:     Normally non-negative.
 *              -1 for any type of error.
 *
 *
 *----------------------------------------------------------------*/

int audio_close (void)
{
	int err = 0;
	int a;
	
	for (a = 0; a < MAX_ADEVS; a++) {
		if(adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD) {
			
			audio_wait (a);
			
			if (adev[a].inStream != NULL) {
				pthread_mutex_destroy(&adev[a].input_mutex);
				pthread_cond_destroy(&adev[a].input_cond);
				err |= (int) Pa_CloseStream(adev[a].inStream);
			}
			
			if(adev[a].outStream != NULL) {
				pthread_mutex_destroy(&adev[a].output_mutex);
				pthread_cond_destroy(&adev[a].output_cond);
				err |= (int) Pa_CloseStream(adev[a].outStream);
			}
			
			err |= (int) Pa_Terminate();
		}
		
		if(adev[a].inbuf_ptr)
			free (adev[a].inbuf_ptr);
		
		if(adev[a].outbuf_ptr)
			free (adev[a].outbuf_ptr);
		
		adev[a].inbuf_size_in_bytes = 0;
		adev[a].inbuf_ptr  = NULL;
		adev[a].inbuf_len  = 0;
		adev[a].inbuf_next = 0;
		
		adev[a].outbuf_size_in_bytes = 0;
		adev[a].outbuf_ptr  = NULL;
		adev[a].outbuf_len  = 0;
		adev[a].outbuf_next = 0;
	}
	
	if(err < 0)
		err = -1;
	
	return (err);

} /* end audio_close */

/* end audio_portaudio.c */

#endif // USE_PORTAUDIO
