Archived
14
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
asterisk/channels/chan_usbradio.c
rizzo 91f1b6959d add some macros to simplify parsing the config file,
see description in config.h .

They are a variant of the set of macros i used in chan_oss.c,
structured in a way to be more robust to the presence of
spurious ';' - basically, they define wrappers for 'do {'
and '} while (0)', plus some helper functions to deal with simple
cases such as ast_copy_string, ast_malloc, strtoul, ast_true ...

The prefix (CV_ as 'Config Variable') tries to be easy to remember
and has been chosen to not conflict with other existing macros in the tree.

For the time being, I have only updated the three source files in the
tree that used the old M_* macros. Hopefully, more files will be
converted.

NOTE:

    I understand that inventing my own dialect of C is generally wrong;
    however, the lack of adequate support in the language encourages
    lazy programming practices (such as ignoring errors, bounds, etc.)
    and this increases the chance of vulnerability in the code, especially
    because we are parsing user input here.
    Hopefully, these macros and the use of ast_parse_arg (in config.h)
    should encourage the programmer to write more robust code.



git-svn-id: http://svn.digium.com/svn/asterisk/trunk@94191 f38db490-d61c-443f-a65b-d21fe96a405b
2007-12-20 12:56:07 +00:00

2710 lines
75 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2005, Digium, Inc.
* Copyright (C) 2007, Jim Dixon
*
* Jim Dixon, WB6NIL <jim@lambdatel.com>
* Steve Henke, W9SH <w9sh@arrl.net>
* Based upon work by Mark Spencer <markster@digium.com> and Luigi Rizzo
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief Channel driver for CM108 USB Cards with Radio Interface
*
* \author Jim Dixon <jim@lambdatel.com>
* \author Steve Henke <w9sh@arrl.net>
*
* \par See also
* \arg \ref Config_usbradio
*
* \ingroup channel_drivers
*/
/*** MODULEINFO
<depend>asound</depend>
<depend>usb</depend>
<defaultenabled>no</defaultenabled>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <ctype.h>
#include <math.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/time.h>
#include <usb.h>
#include <alsa/asoundlib.h>
#define CHAN_USBRADIO 1
#define DEBUG_USBRADIO 0
#define DEBUG_CAPTURES 1
#define DEBUG_CAP_RX_OUT 0
#define DEBUG_CAP_TX_OUT 0
#define DEBUG_FILETEST 0
#define RX_CAP_RAW_FILE "/tmp/rx_cap_in.pcm"
#define RX_CAP_TRACE_FILE "/tmp/rx_trace.pcm"
#define RX_CAP_OUT_FILE "/tmp/rx_cap_out.pcm"
#define TX_CAP_RAW_FILE "/tmp/tx_cap_in.pcm"
#define TX_CAP_TRACE_FILE "/tmp/tx_trace.pcm"
#define TX_CAP_OUT_FILE "/tmp/tx_cap_out.pcm"
#define MIXER_PARAM_MIC_PLAYBACK_SW "Mic Playback Switch"
#define MIXER_PARAM_MIC_PLAYBACK_VOL "Mic Playback Volume"
#define MIXER_PARAM_MIC_CAPTURE_SW "Mic Capture Switch"
#define MIXER_PARAM_MIC_CAPTURE_VOL "Mic Capture Volume"
#define MIXER_PARAM_MIC_BOOST "Auto Gain Control"
#define MIXER_PARAM_SPKR_PLAYBACK_SW "Speaker Playback Switch"
#define MIXER_PARAM_SPKR_PLAYBACK_VOL "Speaker Playback Volume"
#include "./xpmr/xpmr.h"
#if 0
#define traceusb1(a, ...) ast_debug(4, a __VA_ARGS__)
#else
#define traceusb1(a, ...)
#endif
#if 0
#define traceusb2(a, ...) ast_debug(4, a __VA_ARGS__)
#else
#define traceusb2(a, ...)
#endif
#ifdef __linux
#include <linux/soundcard.h>
#elif defined(__FreeBSD__)
#include <sys/soundcard.h>
#else
#include <soundcard.h>
#endif
#include "asterisk/lock.h"
#include "asterisk/frame.h"
#include "asterisk/callerid.h"
#include "asterisk/channel.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/config.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/endian.h"
#include "asterisk/stringfields.h"
#include "asterisk/abstract_jb.h"
#include "asterisk/musiconhold.h"
#include "asterisk/dsp.h"
/* ringtones we use */
#include "busy.h"
#include "ringtone.h"
#include "ring10.h"
#include "answer.h"
#define C108_VENDOR_ID 0x0d8c
#define C108_PRODUCT_ID 0x000c
#define C108_HID_INTERFACE 3
#define HID_REPORT_GET 0x01
#define HID_REPORT_SET 0x09
#define HID_RT_INPUT 0x01
#define HID_RT_OUTPUT 0x02
/*! Global jitterbuffer configuration - by default, jb is disabled */
static struct ast_jb_conf default_jbconf =
{
.flags = 0,
.max_size = -1,
.resync_threshold = -1,
.impl = "",
};
static struct ast_jb_conf global_jbconf;
/*!
* usbradio.conf parameters are
START_CONFIG
[general]
; General config options, with default values shown.
; You should use one section per device, with [general] being used
; for the device.
;
;
; debug = 0x0 ; misc debug flags, default is 0
; Set the device to use for I/O
; devicenum = 0
; Set hardware type here
; hdwtype=0 ; 0=limey, 1=sph
; rxboostset=0 ; no rx gain boost
; rxctcssrelax=1 ; reduce talkoff from radios w/o CTCSS Tx HPF
; rxctcssfreq=100.0 ; rx ctcss freq in floating point. must be in table
; txctcssfreq=100.0 ; tx ctcss freq, any frequency permitted
; carrierfrom=dsp ;no,usb,usbinvert,dsp,vox
; ctcssfrom=dsp ;no,usb,dsp
; rxdemod=flat ; input type from radio: no,speaker,flat
; txprelim=yes ; output is pre-emphasised and limited
; txtoctype=no ; no,phase,notone
; txmixa=composite ;no,voice,tone,composite,auxvoice
; txmixb=no ;no,voice,tone,composite,auxvoice
; invertptt=0
;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an
; USBRADIO channel. Defaults to "no". An enabled jitterbuffer will
; be used only if the sending side can create and the receiving
; side can not accept jitter. The USBRADIO channel can't accept jitter,
; thus an enabled jitterbuffer on the receive USBRADIO side will always
; be used if the sending side can create jitter.
; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds.
; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is
; resynchronized. Useful to improve the quality of the voice, with
; big jumps in/broken timestamps, usualy sent from exotic devices
; and programs. Defaults to 1000.
; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of an USBRADIO
; channel. Two implementations are currenlty available - "fixed"
; (with size always equals to jbmax-size) and "adaptive" (with
; variable size, actually the new jb of IAX2). Defaults to fixed.
; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no".
;-----------------------------------------------------------------------------------
END_CONFIG
*/
/*!
* The following parameters are used in the driver:
*
* FRAME_SIZE the size of an audio frame, in samples.
* 160 is used almost universally, so you should not change it.
*
* FRAGS the argument for the SETFRAGMENT ioctl.
* Overridden by the 'frags' parameter in usbradio.conf
*
* Bits 0-7 are the base-2 log of the device's block size,
* bits 16-31 are the number of blocks in the driver's queue.
* There are a lot of differences in the way this parameter
* is supported by different drivers, so you may need to
* experiment a bit with the value.
* A good default for linux is 30 blocks of 64 bytes, which
* results in 6 frames of 320 bytes (160 samples).
* FreeBSD works decently with blocks of 256 or 512 bytes,
* leaving the number unspecified.
* Note that this only refers to the device buffer size,
* this module will then try to keep the lenght of audio
* buffered within small constraints.
*
* QUEUE_SIZE The max number of blocks actually allowed in the device
* driver's buffer, irrespective of the available number.
* Overridden by the 'queuesize' parameter in usbradio.conf
*
* Should be >=2, and at most as large as the hw queue above
* (otherwise it will never be full).
*/
#define FRAME_SIZE 160
#define QUEUE_SIZE 20
#if defined(__FreeBSD__)
#define FRAGS 0x8
#else
#define FRAGS ( ( (6 * 5) << 16 ) | 0xc )
#endif
/*
* XXX text message sizes are probably 256 chars, but i am
* not sure if there is a suitable definition anywhere.
*/
#define TEXT_SIZE 256
#if 0
#define TRYOPEN 1 /* try to open on startup */
#endif
#define O_CLOSE 0x444 /* special 'close' mode for device */
/* Which device to use */
#if defined( __OpenBSD__ ) || defined( __NetBSD__ )
#define DEV_DSP "/dev/audio"
#else
#define DEV_DSP "/dev/dsp"
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
static char *config = "usbradio.conf"; /* default config file */
static char *config1 = "usbradio_tune.conf"; /* tune config file */
static FILE *frxcapraw = NULL, *frxcaptrace = NULL, *frxoutraw = NULL;
static FILE *ftxcapraw = NULL, *ftxcaptrace = NULL, *ftxoutraw = NULL;
static int usbradio_debug;
#if 0 /* maw asdf sph */
static int usbradio_debug_level = 0;
#endif
enum {RX_AUDIO_NONE,RX_AUDIO_SPEAKER,RX_AUDIO_FLAT};
enum {CD_IGNORE,CD_XPMR_NOISE,CD_XPMR_VOX,CD_HID,CD_HID_INVERT};
enum {SD_IGNORE,SD_HID,SD_HID_INVERT,SD_XPMR}; /* no,external,externalinvert,software */
enum {RX_KEY_CARRIER,RX_KEY_CARRIER_CODE};
enum {TX_OUT_OFF,TX_OUT_VOICE,TX_OUT_LSD,TX_OUT_COMPOSITE,TX_OUT_AUX};
enum {TOC_NONE,TOC_PHASE,TOC_NOTONE};
/* DECLARE STRUCTURES */
/*
* Each sound is made of 'datalen' samples of sound, repeated as needed to
* generate 'samplen' samples of data, then followed by 'silencelen' samples
* of silence. The loop is repeated if 'repeat' is set.
*/
struct sound {
int ind;
char *desc;
short *data;
int datalen;
int samplen;
int silencelen;
int repeat;
};
static struct sound sounds[] = {
{ AST_CONTROL_RINGING, "RINGING", ringtone, sizeof(ringtone)/2, 16000, 32000, 1 },
{ AST_CONTROL_BUSY, "BUSY", busy, sizeof(busy)/2, 4000, 4000, 1 },
{ AST_CONTROL_CONGESTION, "CONGESTION", busy, sizeof(busy)/2, 2000, 2000, 1 },
{ AST_CONTROL_RING, "RING10", ring10, sizeof(ring10)/2, 16000, 32000, 1 },
{ AST_CONTROL_ANSWER, "ANSWER", answer, sizeof(answer)/2, 2200, 0, 0 },
{ -1, NULL, 0, 0, 0, 0 }, /* end marker */
};
/*
* descriptor for one of our channels.
* There is one used for 'default' values (from the [general] entry in
* the configuration file), and then one instance for each device
* (the default is cloned from [general], others are only created
* if the relevant section exists).
*/
struct chan_usbradio_pvt {
struct chan_usbradio_pvt *next;
char *name;
/*
* cursound indicates which in struct sound we play. -1 means nothing,
* any other value is a valid sound, in which case sampsent indicates
* the next sample to send in [0..samplen + silencelen]
* nosound is set to disable the audio data from the channel
* (so we can play the tones etc.).
*/
int sndcmd[2]; /* Sound command pipe */
int cursound; /* index of sound to send */
int sampsent; /* # of sound samples sent */
int nosound; /* set to block audio from the PBX */
int total_blocks; /* total blocks in the output device */
int sounddev;
enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex;
i16 cdMethod;
int autoanswer;
int autohangup;
int hookstate;
unsigned int queuesize; /* max fragments in queue */
unsigned int frags; /* parameter for SETFRAGMENT */
int warned; /* various flags used for warnings */
#define WARN_used_blocks 1
#define WARN_speed 2
#define WARN_frag 4
int w_errors; /* overfull in the write path */
struct timeval lastopen;
int overridecontext;
int mute;
/* boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must
* be representable in 16 bits to avoid overflows.
*/
#define BOOST_SCALE (1<<9)
#define BOOST_MAX 40 /* slightly less than 7 bits */
int boost; /* input boost, scaled by BOOST_SCALE */
char devicenum;
int spkrmax;
int micmax;
pthread_t sthread;
pthread_t hidthread;
int stophid;
struct ast_channel *owner;
char ext[AST_MAX_EXTENSION];
char ctx[AST_MAX_CONTEXT];
char language[MAX_LANGUAGE];
char cid_name[256]; /* XXX */
char cid_num[256]; /* XXX */
char mohinterpret[MAX_MUSICCLASS];
/* buffers used in usbradio_write, 2 per int by 2 channels by 6 times oversampling (48KS/s) */
char usbradio_write_buf[FRAME_SIZE * 2 * 2 * 6];
char usbradio_write_buf_1[FRAME_SIZE * 2 * 2 * 6];
int usbradio_write_dst;
/* buffers used in usbradio_read - AST_FRIENDLY_OFFSET space for headers
* plus enough room for a full frame
*/
char usbradio_read_buf[FRAME_SIZE * (2 * 12) + AST_FRIENDLY_OFFSET];
char usbradio_read_buf_8k[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
int readpos; /* read position above */
struct ast_frame read_f; /* returned by usbradio_read */
char debuglevel;
char radioduplex;
char lastrx;
char rxhidsq;
char rxcarrierdetect; /*!< status from pmr channel */
char rxctcssdecode; /*!< status from pmr channel */
char rxkeytype;
char rxkeyed; /*!< indicates rx signal present */
char lasttx;
char txkeyed; /*! tx key request from upper layers */
char txchankey;
char txtestkey;
time_t lasthidtime;
struct ast_dsp *dsp;
t_pmr_chan *pmrChan;
char rxcpusaver;
char txcpusaver;
char rxdemod;
float rxgain;
char rxcdtype;
char rxsdtype;
int rxsquelchadj; /*!< this copy needs to be here for initialization */
char txtoctype;
char txprelim;
float txctcssgain;
char txmixa;
char txmixb;
char invertptt;
char rxctcssrelax;
float rxctcssgain;
float rxctcssfreq;
float txctcssfreq;
int rxmixerset;
int rxboostset;
float rxvoiceadj;
float rxctcssadj;
int txmixaset;
int txmixbset;
int txctcssadj;
int hdwtype;
int hid_gpio_ctl;
int hid_gpio_ctl_loc;
int hid_io_cor;
int hid_io_cor_loc;
int hid_io_ctcss;
int hid_io_ctcss_loc;
int hid_io_ptt;
int hid_gpio_loc;
struct {
unsigned rxcapraw:1;
unsigned txcapraw:1;
unsigned txcap2:1;
unsigned rxcap2:1;
} b;
};
/* maw add additional defaults !!! */
static struct chan_usbradio_pvt usbradio_default = {
.cursound = -1,
.sounddev = -1,
.duplex = M_UNSET, /* XXX check this */
.autoanswer = 1,
.autohangup = 1,
.queuesize = QUEUE_SIZE,
.frags = FRAGS,
.ext = "s",
.ctx = "default",
.readpos = AST_FRIENDLY_OFFSET, /* start here on reads */
.lastopen = { 0, 0 },
.boost = BOOST_SCALE,
};
/* DECLARE FUNCTION PROTOTYPES */
static void store_txtoctype(struct chan_usbradio_pvt *o, const char *s);
static int hidhdwconfig(struct chan_usbradio_pvt *o);
static int set_txctcss_level(struct chan_usbradio_pvt *o);
static void pmrdump(struct chan_usbradio_pvt *o);
static void mult_set(struct chan_usbradio_pvt *o);
static int mult_calc(int value);
static void mixer_write(struct chan_usbradio_pvt *o);
static void tune_rxinput(struct chan_usbradio_pvt *o);
static void tune_rxvoice(struct chan_usbradio_pvt *o);
static void tune_rxctcss(struct chan_usbradio_pvt *o);
static void tune_txoutput(struct chan_usbradio_pvt *o, int value);
static void tune_write(struct chan_usbradio_pvt *o);
static char *usbradio_active; /* the active device */
static int setformat(struct chan_usbradio_pvt *o, int mode);
static struct ast_channel *usbradio_request(const char *type, int format, void *data
, int *cause);
static int usbradio_digit_begin(struct ast_channel *c, char digit);
static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration);
static int usbradio_text(struct ast_channel *c, const char *text);
static int usbradio_hangup(struct ast_channel *c);
static int usbradio_answer(struct ast_channel *c);
static struct ast_frame *usbradio_read(struct ast_channel *chan);
static int usbradio_call(struct ast_channel *c, char *dest, int timeout);
static int usbradio_write(struct ast_channel *chan, struct ast_frame *f);
static int usbradio_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
#if DEBUG_FILETEST == 1
static int RxTestIt(struct chan_usbradio_pvt *o);
#endif
static char tdesc[] = "USB (CM108) Radio Channel Driver";
static const struct ast_channel_tech usbradio_tech = {
.type = "Radio",
.description = tdesc,
.capabilities = AST_FORMAT_SLINEAR,
.requester = usbradio_request,
.send_digit_begin = usbradio_digit_begin,
.send_digit_end = usbradio_digit_end,
.send_text = usbradio_text,
.hangup = usbradio_hangup,
.answer = usbradio_answer,
.read = usbradio_read,
.call = usbradio_call,
.write = usbradio_write,
.indicate = usbradio_indicate,
.fixup = usbradio_fixup,
};
/* Call with: devnum: alsa major device number, param: ascii Formal
Parameter Name, val1, first or only value, val2 second value, or 0
if only 1 value. Values: 0-99 (percent) or 0-1 for baboon.
Note: must add -lasound to end of linkage */
static int amixer_max(int devnum,char *param)
{
int rv,type;
char str[15];
snd_hctl_t *hctl;
snd_ctl_elem_id_t *id;
snd_hctl_elem_t *elem;
snd_ctl_elem_info_t *info;
snprintf(str, sizeof(str), "hw:%d", devnum);
if (snd_hctl_open(&hctl, str, 0))
return -1;
snd_hctl_load(hctl);
id = alloca(snd_ctl_elem_id_sizeof());
memset(id, 0, snd_ctl_elem_id_sizeof());
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, param);
elem = snd_hctl_find_elem(hctl, id);
if (!elem) {
snd_hctl_close(hctl);
return -1;
}
info = alloca(snd_ctl_elem_info_sizeof());
memset(info, 0, snd_ctl_elem_info_sizeof());
snd_hctl_elem_info(elem,info);
type = snd_ctl_elem_info_get_type(info);
rv = 0;
switch (type) {
case SND_CTL_ELEM_TYPE_INTEGER:
rv = snd_ctl_elem_info_get_max(info);
break;
case SND_CTL_ELEM_TYPE_BOOLEAN:
rv = 1;
break;
}
snd_hctl_close(hctl);
return(rv);
}
/*! \brief Call with: devnum: alsa major device number, param: ascii Formal
Parameter Name, val1, first or only value, val2 second value, or 0
if only 1 value. Values: 0-99 (percent) or 0-1 for baboon.
Note: must add -lasound to end of linkage */
static int setamixer(int devnum, char *param, int v1, int v2)
{
int type;
char str[15];
snd_hctl_t *hctl;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_hctl_elem_t *elem;
snd_ctl_elem_info_t *info;
snprintf(str, sizeof(str), "hw:%d", devnum);
if (snd_hctl_open(&hctl, str, 0))
return -1;
snd_hctl_load(hctl);
id = alloca(snd_ctl_elem_id_sizeof());
memset(id, 0, snd_ctl_elem_id_sizeof());
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, param);
elem = snd_hctl_find_elem(hctl, id);
if (!elem) {
snd_hctl_close(hctl);
return -1;
}
info = alloca(snd_ctl_elem_info_sizeof());
memset(info, 0, snd_ctl_elem_info_sizeof());
snd_hctl_elem_info(elem,info);
type = snd_ctl_elem_info_get_type(info);
control = alloca(snd_ctl_elem_value_sizeof());
memset(control, 0, snd_ctl_elem_value_sizeof());
snd_ctl_elem_value_set_id(control, id);
switch (type) {
case SND_CTL_ELEM_TYPE_INTEGER:
snd_ctl_elem_value_set_integer(control, 0, v1);
if (v2 > 0) snd_ctl_elem_value_set_integer(control, 1, v2);
break;
case SND_CTL_ELEM_TYPE_BOOLEAN:
snd_ctl_elem_value_set_integer(control, 0, (v1 != 0));
break;
}
if (snd_hctl_elem_write(elem, control)) {
snd_hctl_close(hctl);
return(-1);
}
snd_hctl_close(hctl);
return 0;
}
static void hid_set_outputs(struct usb_dev_handle *handle,
unsigned char *outputs)
{
usb_control_msg(handle,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
HID_REPORT_SET,
0 + (HID_RT_OUTPUT << 8),
C108_HID_INTERFACE,
(char *)outputs, 4, 5000);
}
static void hid_get_inputs(struct usb_dev_handle *handle,
unsigned char *inputs)
{
usb_control_msg(handle,
USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
HID_REPORT_GET,
0 + (HID_RT_INPUT << 8),
C108_HID_INTERFACE,
(char *)inputs, 4, 5000);
}
static struct usb_device *hid_device_init(void)
{
struct usb_bus *usb_bus;
struct usb_device *dev;
usb_init();
usb_find_busses();
usb_find_devices();
for (usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) {
for (dev = usb_bus->devices; dev; dev = dev->next) {
if ((dev->descriptor.idVendor == C108_VENDOR_ID) && (dev->descriptor.idProduct == C108_PRODUCT_ID))
return dev;
}
}
return NULL;
}
static int hidhdwconfig(struct chan_usbradio_pvt *o)
{
if (o->hdwtype == 1) { /*sphusb */
o->hid_gpio_ctl = 0x08; /* set GPIO4 to output mode */
o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
o->hid_io_cor = 4; /* GPIO3 is COR */
o->hid_io_cor_loc = 1; /* GPIO3 is COR */
o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
o->hid_io_ptt = 8; /* GPIO 4 is PTT */
o->hid_gpio_loc = 1; /* For ALL GPIO */
} else if (o->hdwtype == 0) { /* dudeusb */
o->hid_gpio_ctl = 0x0c;/* set GPIO 3 & 4 to output mode */
o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
o->hid_io_cor = 2; /* VOLD DN is COR */
o->hid_io_cor_loc = 0; /* VOL DN COR */
o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
o->hid_io_ptt = 4; /* GPIO 3 is PTT */
o->hid_gpio_loc = 1; /* For ALL GPIO */
} else if (o->hdwtype == 3) { /* custom version */
o->hid_gpio_ctl = 0x0c; /* set GPIO 3 & 4 to output mode */
o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
o->hid_io_cor = 2; /* VOLD DN is COR */
o->hid_io_cor_loc = 0; /* VOL DN COR */
o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
o->hid_io_ptt = 4; /* GPIO 3 is PTT */
o->hid_gpio_loc = 1; /* For ALL GPIO */
}
return 0;
}
static void *hidthread(void *arg)
{
unsigned char buf[4], keyed;
char lastrx, txtmp;
struct usb_device *usb_dev;
struct usb_dev_handle *usb_handle;
struct chan_usbradio_pvt *o = arg;
usb_dev = hid_device_init();
if (usb_dev == NULL) {
ast_log(LOG_ERROR, "USB HID device not found\n");
pthread_exit(NULL);
}
usb_handle = usb_open(usb_dev);
if (usb_handle == NULL) {
ast_log(LOG_ERROR, "Not able to open USB device\n");
pthread_exit(NULL);
}
if (usb_claim_interface(usb_handle, C108_HID_INTERFACE) < 0) {
if (usb_detach_kernel_driver_np(usb_handle, C108_HID_INTERFACE) < 0) {
ast_log(LOG_ERROR, "Not able to detach the USB device\n");
pthread_exit(NULL);
}
if (usb_claim_interface(usb_handle, C108_HID_INTERFACE) < 0) {
ast_log(LOG_ERROR, "Not able to claim the USB device\n");
pthread_exit(NULL);
}
}
memset(buf, 0, sizeof(buf));
buf[2] = o->hid_gpio_ctl;
buf[1] = 0;
hid_set_outputs(usb_handle, buf);
traceusb1("hidthread: Starting normally!!\n");
lastrx = 0;
while (!o->stophid) {
buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
hid_get_inputs(usb_handle, buf);
keyed = !(buf[o->hid_io_cor_loc] & o->hid_io_cor);
if (keyed != o->rxhidsq) {
if (o->debuglevel)
ast_log(LOG_NOTICE, "chan_usbradio() hidthread: update rxhidsq = %d\n", keyed);
o->rxhidsq = keyed;
}
/* if change in tx stuff */
txtmp = 0;
if (o->txkeyed || o->txchankey || o->txtestkey || o->pmrChan->txPttOut)
txtmp = 1;
if (o->lasttx != txtmp) {
o->lasttx = txtmp;
if (o->debuglevel)
ast_log(LOG_NOTICE, "hidthread: tx set to %d\n", txtmp);
buf[o->hid_gpio_loc] = 0;
if (txtmp)
buf[o->hid_gpio_loc] = o->hid_io_ptt;
buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
hid_set_outputs(usb_handle, buf);
}
time(&o->lasthidtime);
usleep(50000);
}
buf[o->hid_gpio_loc] = 0;
if (o->invertptt)
buf[o->hid_gpio_loc] = o->hid_io_ptt;
buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
hid_set_outputs(usb_handle, buf);
pthread_exit(0);
}
/*! \brief
* returns a pointer to the descriptor with the given name
*/
static struct chan_usbradio_pvt *find_desc(char *dev)
{
struct chan_usbradio_pvt *o = NULL;
if (!dev)
ast_log(LOG_WARNING, "null dev\n");
for (o = usbradio_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
if (!o)
ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
return o;
}
/*! \brief
* split a string in extension-context, returns pointers to malloc'ed
* strings.
* If we do not have 'overridecontext' then the last @ is considered as
* a context separator, and the context is overridden.
* This is usually not very necessary as you can play with the dialplan,
* and it is nice not to need it because you have '@' in SIP addresses.
* Return value is the buffer address.
*/
#if 0
static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
{
struct chan_usbradio_pvt *o = find_desc(usbradio_active);
if (ext == NULL || ctx == NULL)
return NULL; /* error */
*ext = *ctx = NULL;
if (src && *src != '\0')
*ext = ast_strdup(src);
if (*ext == NULL)
return NULL;
if (!o->overridecontext) {
/* parse from the right */
*ctx = strrchr(*ext, '@');
if (*ctx)
*(*ctx)++ = '\0';
}
return *ext;
}
#endif
/*! \brief
* Returns the number of blocks used in the audio output channel
*/
static int used_blocks(struct chan_usbradio_pvt *o)
{
struct audio_buf_info info;
if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
if (!(o->warned & WARN_used_blocks)) {
ast_log(LOG_WARNING, "Error reading output space\n");
o->warned |= WARN_used_blocks;
}
return 1;
}
if (o->total_blocks == 0) {
ast_debug(4, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
o->total_blocks = info.fragments;
}
return o->total_blocks - info.fragments;
}
/*! \brief Write an exactly FRAME_SIZE sized frame */
static int soundcard_writeframe(struct chan_usbradio_pvt *o, short *data)
{
int res;
if (o->sounddev < 0)
setformat(o, O_RDWR);
if (o->sounddev < 0)
return 0; /* not fatal */
/*
* Nothing complex to manage the audio device queue.
* If the buffer is full just drop the extra, otherwise write.
* XXX in some cases it might be useful to write anyways after
* a number of failures, to restart the output chain.
*/
res = used_blocks(o);
if (res > o->queuesize) { /* no room to write a block */
if (o->w_errors++ == 0 && (usbradio_debug & 0x4))
ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
return 0;
}
o->w_errors = 0;
return write(o->sounddev, ((void *) data), FRAME_SIZE * 2 * 12);
}
/*! \brief
* Handler for 'sound writable' events from the sound thread.
* Builds a frame from the high level description of the sounds,
* and passes it to the audio device.
* The actual sound is made of 1 or more sequences of sound samples
* (s->datalen, repeated to make s->samplen samples) followed by
* s->silencelen samples of silence. The position in the sequence is stored
* in o->sampsent, which goes between 0 .. s->samplen+s->silencelen.
* In case we fail to write a frame, don't update o->sampsent.
*/
static void send_sound(struct chan_usbradio_pvt *o)
{
short myframe[FRAME_SIZE];
int ofs, l, start;
int l_sampsent = o->sampsent;
struct sound *s;
if (o->cursound < 0) /* no sound to send */
return;
s = &sounds[o->cursound];
for (ofs = 0; ofs < FRAME_SIZE; ofs += l) {
l = s->samplen - l_sampsent; /* # of available samples */
if (l > 0) {
start = l_sampsent % s->datalen; /* source offset */
if (l > FRAME_SIZE - ofs) /* don't overflow the frame */
l = FRAME_SIZE - ofs;
if (l > s->datalen - start) /* don't overflow the source */
l = s->datalen - start;
memcpy(myframe + ofs, s->data + start, l * 2);
ast_debug(4, "send_sound sound %d/%d of %d into %d\n", l_sampsent, l, s->samplen, ofs);
l_sampsent += l;
} else { /* end of samples, maybe some silence */
static const short silence[FRAME_SIZE] = { 0, };
l += s->silencelen;
if (l > 0) {
if (l > FRAME_SIZE - ofs)
l = FRAME_SIZE - ofs;
memcpy(myframe + ofs, silence, l * 2);
l_sampsent += l;
} else { /* silence is over, restart sound if loop */
if (s->repeat == 0) { /* last block */
o->cursound = -1;
o->nosound = 0; /* allow audio data */
if (ofs < FRAME_SIZE) /* pad with silence */
memcpy(myframe + ofs, silence, (FRAME_SIZE - ofs) * 2);
}
l_sampsent = 0;
}
}
}
l = soundcard_writeframe(o, myframe);
if (l > 0)
o->sampsent = l_sampsent; /* update status */
}
static void *sound_thread(void *arg)
{
char ign[4096];
struct chan_usbradio_pvt *o = arg;
/*
* Just in case, kick the driver by trying to read from it.
* Ignore errors - this read is almost guaranteed to fail.
*/
read(o->sounddev, ign, sizeof(ign));
for (;;) {
fd_set rfds, wfds;
int maxfd, res;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(o->sndcmd[0], &rfds);
maxfd = o->sndcmd[0]; /* pipe from the main process */
if (o->cursound > -1 && o->sounddev < 0)
setformat(o, O_RDWR); /* need the channel, try to reopen */
else if (o->cursound == -1 && o->owner == NULL)
setformat(o, O_CLOSE); /* can close */
if (o->sounddev > -1) {
if (!o->owner) { /* no one owns the audio, so we must drain it */
FD_SET(o->sounddev, &rfds);
maxfd = MAX(o->sounddev, maxfd);
}
if (o->cursound > -1) {
FD_SET(o->sounddev, &wfds);
maxfd = MAX(o->sounddev, maxfd);
}
}
/* ast_select emulates linux behaviour in terms of timeout handling */
res = ast_select(maxfd + 1, &rfds, &wfds, NULL, NULL);
if (res < 1) {
ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno));
sleep(1);
continue;
}
if (FD_ISSET(o->sndcmd[0], &rfds)) {
/* read which sound to play from the pipe */
int i, what = -1;
read(o->sndcmd[0], &what, sizeof(what));
for (i = 0; sounds[i].ind != -1; i++) {
if (sounds[i].ind == what) {
o->cursound = i;
o->sampsent = 0;
o->nosound = 1; /* block audio from pbx */
break;
}
}
if (sounds[i].ind == -1)
ast_log(LOG_WARNING, "invalid sound index: %d\n", what);
}
if (o->sounddev > -1) {
if (FD_ISSET(o->sounddev, &rfds)) /* read and ignore errors */
read(o->sounddev, ign, sizeof(ign));
if (FD_ISSET(o->sounddev, &wfds))
send_sound(o);
}
}
return NULL; /* Never reached */
}
/*
* reset and close the device if opened,
* then open and initialize it in the desired mode,
* trigger reads and writes so we can start using it.
*/
static int setformat(struct chan_usbradio_pvt *o, int mode)
{
int fmt, desired, res, fd;
char device[20];
if (o->sounddev >= 0) {
ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
close(o->sounddev);
o->duplex = M_UNSET;
o->sounddev = -1;
}
if (mode == O_CLOSE) /* we are done */
return 0;
if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
return -1; /* don't open too often */
o->lastopen = ast_tvnow();
strcpy(device, "/dev/dsp");
if (o->devicenum)
snprintf(device + strlen("/dev/dsp"), sizeof(device) - strlen("/dev/dsp"), "%d", o->devicenum);
fd = o->sounddev = open(device, mode | O_NONBLOCK);
if (fd < 0) {
ast_log(LOG_WARNING, "Unable to re-open DSP device %d: %s\n", o->devicenum, strerror(errno));
return -1;
}
if (o->owner)
o->owner->fds[0] = fd;
#if __BYTE_ORDER == __LITTLE_ENDIAN
fmt = AFMT_S16_LE;
#else
fmt = AFMT_S16_BE;
#endif
res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
if (res < 0) {
ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
return -1;
}
switch (mode) {
case O_RDWR:
res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
/* Check to see if duplex set (FreeBSD Bug) */
res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
if (option_verbose > 1)
ast_verbose(VERBOSE_PREFIX_2 "Console is full duplex\n");
o->duplex = M_FULL;
};
break;
case O_WRONLY:
o->duplex = M_WRITE;
break;
case O_RDONLY:
o->duplex = M_READ;
break;
}
fmt = 1;
res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
if (res < 0) {
ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
return -1;
}
fmt = desired = 48000; /* 8000 Hz desired */
res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
if (res < 0) {
ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
return -1;
}
if (fmt != desired) {
if (!(o->warned & WARN_speed)) {
ast_log(LOG_WARNING,
"Requested %d Hz, got %d Hz -- sound may be choppy\n",
desired, fmt);
o->warned |= WARN_speed;
}
}
/*
* on Freebsd, SETFRAGMENT does not work very well on some cards.
* Default to use 256 bytes, let the user override
*/
if (o->frags) {
fmt = o->frags;
res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
if (res < 0) {
if (!(o->warned & WARN_frag)) {
ast_log(LOG_WARNING,
"Unable to set fragment size -- sound may be choppy\n");
o->warned |= WARN_frag;
}
}
}
/* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */
res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
/* it may fail if we are in half duplex, never mind */
return 0;
}
/*
* some of the standard methods supported by channels.
*/
static int usbradio_digit_begin(struct ast_channel *c, char digit)
{
return 0;
}
static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration)
{
/* no better use for received digits than print them */
ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
digit, duration);
return 0;
}
static int usbradio_text(struct ast_channel *c, const char *text)
{
/* print received messages */
ast_verbose(" << Console Received text %s >> \n", text);
return 0;
}
/* Play ringtone 'x' on device 'o' */
static void ring(struct chan_usbradio_pvt *o, int x)
{
write(o->sndcmd[1], &x, sizeof(x));
}
/*
* handler for incoming calls. Either autoanswer, or start ringing
*/
static int usbradio_call(struct ast_channel *c, char *dest, int timeout)
{
struct chan_usbradio_pvt *o = c->tech_pvt;
time(&o->lasthidtime);
ast_pthread_create_background(&o->hidthread, NULL, hidthread, o);
ast_setstate(c, AST_STATE_UP);
return 0;
}
/*
* remote side answered the phone
*/
static int usbradio_answer(struct ast_channel *c)
{
struct chan_usbradio_pvt *o = c->tech_pvt;
ast_setstate(c, AST_STATE_UP);
o->cursound = -1;
o->nosound = 0;
return 0;
}
static int usbradio_hangup(struct ast_channel *c)
{
struct chan_usbradio_pvt *o = c->tech_pvt;
o->cursound = -1;
o->nosound = 0;
c->tech_pvt = NULL;
o->owner = NULL;
ast_module_unref(ast_module_info->self);
if (o->hookstate) {
if (o->autoanswer || o->autohangup) {
/* Assume auto-hangup too */
o->hookstate = 0;
setformat(o, O_CLOSE);
} else {
/* Make congestion noise */
ring(o, AST_CONTROL_CONGESTION);
}
}
o->stophid = 1;
pthread_join(o->hidthread, NULL);
return 0;
}
/* used for data coming from the network */
static int usbradio_write(struct ast_channel *c, struct ast_frame *f)
{
int src,datalen;
struct chan_usbradio_pvt *o = c->tech_pvt;
traceusb2("usbradio_write() o->nosound=%d\n", o->nosound); /*sph maw asdf */
/* Immediately return if no sound is enabled */
if (o->nosound)
return 0;
/* Stop any currently playing sound */
o->cursound = -1;
/*
* we could receive a block which is not a multiple of our
* FRAME_SIZE, so buffer it locally and write to the device
* in FRAME_SIZE chunks.
* Keep the residue stored for future use.
*/
if (o->txkeyed || o->txtestkey)
o->pmrChan->txPttIn = 1;
else
o->pmrChan->txPttIn = 0;
#if DEBUG_CAPTURES == 1 /* to write input data to a file datalen=320 */
if (ftxcapraw && o->b.txcapraw) {
i16 i, tbuff[f->datalen];
for (i = 0; i < f->datalen; i += 2) {
tbuff[i] = ((i16 *)(f->data))[i / 2];
tbuff[i + 1] = o->txkeyed * M_Q13;
}
fwrite(tbuff, 2, f->datalen, ftxcapraw);
/*fwrite(f->data,1,f->datalen,ftxcapraw); */
}
#endif
PmrTx(o->pmrChan,(i16*)f->data,(i16*)o->usbradio_write_buf_1);
#if 0 /* to write 48KS/s stereo data to a file */
if (!ftxoutraw) ftxoutraw = fopen(TX_CAP_OUT_FILE,"w");
if (ftxoutraw) fwrite(o->usbradio_write_buf_1,1,f->datalen * 2 * 6,ftxoutraw);
#endif
#if DEBUG_CAPTURES == 1
if (o->b.txcap2 && ftxcaptrace)
fwrite((o->pmrChan->ptxDebug), 1, FRAME_SIZE * 2 * 16, ftxcaptrace);
#endif
src = 0; /* read position into f->data */
datalen = f->datalen * 12;
while (src < datalen) {
/* Compute spare room in the buffer */
int l = sizeof(o->usbradio_write_buf) - o->usbradio_write_dst;
if (datalen - src >= l) { /* enough to fill a frame */
memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l);
soundcard_writeframe(o, (short *) o->usbradio_write_buf);
src += l;
o->usbradio_write_dst = 0;
} else { /* copy residue */
l = datalen - src;
memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l);
src += l; /* but really, we are done */
o->usbradio_write_dst += l;
}
}
return 0;
}
static struct ast_frame *usbradio_read(struct ast_channel *c)
{
int res;
struct chan_usbradio_pvt *o = c->tech_pvt;
struct ast_frame *f = &o->read_f, *f1;
struct ast_frame wf = { AST_FRAME_CONTROL };
time_t now;
traceusb2("usbradio_read()\n"); /* sph maw asdf */
if (o->lasthidtime) {
time(&now);
if ((now - o->lasthidtime) > 3) {
ast_log(LOG_ERROR, "HID process has died or something!!\n");
return NULL;
}
}
if (o->lastrx && (!o->rxkeyed)) {
o->lastrx = 0;
wf.subclass = AST_CONTROL_RADIO_UNKEY;
ast_queue_frame(o->owner, &wf);
} else if ((!o->lastrx) && (o->rxkeyed)) {
o->lastrx = 1;
wf.subclass = AST_CONTROL_RADIO_KEY;
ast_queue_frame(o->owner, &wf);
}
/* XXX can be simplified returning &ast_null_frame */
/* prepare a NULL frame in case we don't have enough data to return */
memset(f, 0, sizeof(struct ast_frame));
f->frametype = AST_FRAME_NULL;
f->src = usbradio_tech.type;
res = read(o->sounddev, o->usbradio_read_buf + o->readpos,
sizeof(o->usbradio_read_buf) - o->readpos);
if (res < 0) /* audio data not ready, return a NULL frame */
return f;
o->readpos += res;
if (o->readpos < sizeof(o->usbradio_read_buf)) /* not enough samples */
return f;
if (o->mute)
return f;
#if DEBUG_CAPTURES == 1
if (o->b.rxcapraw && frxcapraw)
fwrite((o->usbradio_read_buf + AST_FRIENDLY_OFFSET), 1, FRAME_SIZE * 2 * 2 * 6, frxcapraw);
#endif
#if 1
PmrRx( o->pmrChan,
(i16 *)(o->usbradio_read_buf + AST_FRIENDLY_OFFSET),
(i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET));
#else
static FILE *hInput;
i16 iBuff[FRAME_SIZE * 2 * 6];
o->pmrChan->b.rxCapture = 1;
if(!hInput) {
hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm", "r");
if(!hInput) {
ast_log(LOG_ERROR, " Input Data File Not Found.\n");
return 0;
}
}
if (0 == fread((void *)iBuff, 2, FRAME_SIZE * 2 * 6, hInput))
exit;
PmrRx( o->pmrChan,
(i16 *)iBuff,
(i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET));
#endif
#if 0
if (!frxoutraw) frxoutraw = fopen(RX_CAP_OUT_FILE, "w");
if (frxoutraw) fwrite((o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET), 1, FRAME_SIZE * 2, frxoutraw);
#endif
#if DEBUG_CAPTURES == 1
if (frxcaptrace && o->b.rxcap2) fwrite((o->pmrChan->prxDebug), 1, FRAME_SIZE * 2 * 16, frxcaptrace);
#endif
if (o->rxcdtype == CD_HID && (o->pmrChan->rxExtCarrierDetect != o->rxhidsq))
o->pmrChan->rxExtCarrierDetect = o->rxhidsq;
if (o->rxcdtype == CD_HID_INVERT && (o->pmrChan->rxExtCarrierDetect == o->rxhidsq))
o->pmrChan->rxExtCarrierDetect = !o->rxhidsq;
if ( (o->rxcdtype == CD_HID && o->rxhidsq) ||
(o->rxcdtype == CD_HID_INVERT && !o->rxhidsq) ||
(o->rxcdtype == CD_XPMR_NOISE && o->pmrChan->rxCarrierDetect) ||
(o->rxcdtype == CD_XPMR_VOX && o->pmrChan->rxCarrierDetect) )
res = 1;
else
res = 0;
if (res != o->rxcarrierdetect) {
o->rxcarrierdetect = res;
if (o->debuglevel)
ast_debug(4, "rxcarrierdetect = %d\n", res);
}
if (o->pmrChan->rxCtcss->decode != o->rxctcssdecode) {
if (o->debuglevel)
ast_debug(4, "rxctcssdecode = %d\n", o->pmrChan->rxCtcss->decode);
o->rxctcssdecode = o->pmrChan->rxCtcss->decode;
}
if ( ( o->rxctcssfreq && (o->rxctcssdecode == o->pmrChan->rxCtcssIndex)) ||
( !o->rxctcssfreq && o->rxcarrierdetect) )
o->rxkeyed = 1;
else
o->rxkeyed = 0;
o->readpos = AST_FRIENDLY_OFFSET; /* reset read pointer for next frame */
if (c->_state != AST_STATE_UP) /* drop data if frame is not up */
return f;
/* ok we can build and deliver the frame to the caller */
f->frametype = AST_FRAME_VOICE;
f->subclass = AST_FORMAT_SLINEAR;
f->samples = FRAME_SIZE;
f->datalen = FRAME_SIZE * 2;
f->data = o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET;
if (o->boost != BOOST_SCALE) { /* scale and clip values */
int i, x;
int16_t *p = (int16_t *) f->data;
for (i = 0; i < f->samples; i++) {
x = (p[i] * o->boost) / BOOST_SCALE;
if (x > 32767)
x = 32767;
else if (x < -32768)
x = -32768;
p[i] = x;
}
}
f->offset = AST_FRIENDLY_OFFSET;
if (o->dsp) {
f1 = ast_dsp_process(c, o->dsp, f);
if ((f1->frametype == AST_FRAME_DTMF_END) || (f1->frametype == AST_FRAME_DTMF_BEGIN)) {
if ((f1->subclass == 'm') || (f1->subclass == 'u'))
f1->frametype = AST_FRAME_DTMF_BEGIN;
if (f1->frametype == AST_FRAME_DTMF_END)
ast_log(LOG_NOTICE,"Got DTMF char %c\n",f1->subclass);
return f1;
}
}
return f;
}
static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{
struct chan_usbradio_pvt *o = newchan->tech_pvt;
ast_log(LOG_WARNING,"usbradio_fixup()\n");
o->owner = newchan;
return 0;
}
static int usbradio_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
{
struct chan_usbradio_pvt *o = c->tech_pvt;
int res = -1;
switch (cond) {
case AST_CONTROL_BUSY:
case AST_CONTROL_CONGESTION:
case AST_CONTROL_RINGING:
res = cond;
break;
case -1:
o->cursound = -1;
o->nosound = 0; /* when cursound is -1 nosound must be 0 */
return 0;
case AST_CONTROL_VIDUPDATE:
res = -1;
break;
case AST_CONTROL_HOLD:
ast_verbose(" << Console Has Been Placed on Hold >> \n");
ast_moh_start(c, data, o->mohinterpret);
break;
case AST_CONTROL_UNHOLD:
ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
ast_moh_stop(c);
break;
case AST_CONTROL_PROCEEDING:
ast_verbose(" << Call Proceeding... >> \n");
ast_moh_stop(c);
break;
case AST_CONTROL_PROGRESS:
ast_verbose(" << Call Progress... >> \n");
ast_moh_stop(c);
break;
case AST_CONTROL_RADIO_KEY:
o->txkeyed = 1;
if(o->debuglevel)ast_verbose(" << Radio Transmit On. >> \n");
break;
case AST_CONTROL_RADIO_UNKEY:
o->txkeyed = 0;
if(o->debuglevel)ast_verbose(" << Radio Transmit Off. >> \n");
break;
default:
ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name);
return -1;
}
if (res > -1)
ring(o, res);
return 0;
}
/*
* allocate a new channel.
*/
static struct ast_channel *usbradio_new(struct chan_usbradio_pvt *o, char *ext, char *ctx, int state)
{
struct ast_channel *c;
char device[15] = "dsp";
if (o->devicenum)
snprintf(device + 3, sizeof(device) - 3, "%d", o->devicenum);
c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, 0, "usbRadio/%s", device);
if (c == NULL)
return NULL;
c->tech = &usbradio_tech;
if (o->sounddev < 0)
setformat(o, O_RDWR);
c->fds[0] = o->sounddev; /* -1 if device closed, override later */
c->nativeformats = AST_FORMAT_SLINEAR;
c->readformat = AST_FORMAT_SLINEAR;
c->writeformat = AST_FORMAT_SLINEAR;
c->tech_pvt = o;
if (!ast_strlen_zero(o->language))
ast_string_field_set(c, language, o->language);
/* Don't use ast_set_callerid() here because it will
* generate a needless NewCallerID event */
c->cid.cid_num = ast_strdup(o->cid_num);
c->cid.cid_ani = ast_strdup(o->cid_num);
c->cid.cid_name = ast_strdup(o->cid_name);
if (!ast_strlen_zero(ext))
c->cid.cid_dnid = ast_strdup(ext);
o->owner = c;
ast_module_ref(ast_module_info->self);
ast_jb_configure(c, &global_jbconf);
if (state != AST_STATE_DOWN) {
if (ast_pbx_start(c)) {
ast_log(LOG_WARNING, "Unable to start PBX on %s\n", c->name);
ast_hangup(c);
o->owner = c = NULL;
/* XXX what about the channel itself ? */
/* XXX what about usecnt ? */
}
}
return c;
}
static struct ast_channel *usbradio_request(const char *type, int format, void *data, int *cause)
{
struct ast_channel *c;
struct chan_usbradio_pvt *o = find_desc(data);
ast_debug(4, "usbradio_request ty <%s> data 0x%p <%s>\n", type, data, (char *) data);
if (o == NULL) {
ast_log(LOG_NOTICE, "Device %s not found\n", (char *) data);
/* XXX we could default to 'dsp' perhaps ? */
return NULL;
}
if ((format & AST_FORMAT_SLINEAR) == 0) {
ast_log(LOG_NOTICE, "Format 0x%x unsupported\n", format);
return NULL;
}
if (o->owner) {
ast_log(LOG_NOTICE, "Already have a call (chan %p) on the usb channel\n", o->owner);
*cause = AST_CAUSE_BUSY;
return NULL;
}
c = usbradio_new(o, NULL, NULL, AST_STATE_DOWN);
if (c == NULL) {
ast_log(LOG_WARNING, "Unable to create new usb channel\n");
return NULL;
}
return c;
}
static char *handle_cli_radio_key(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct chan_usbradio_pvt *o = NULL;
switch (cmd) {
case CLI_INIT:
e->command = "radio key";
e->usage =
"Usage: radio key\n"
" Simulates COR active.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 2)
return CLI_SHOWUSAGE;
o = find_desc(usbradio_active);
o->txtestkey = 1;
return CLI_SUCCESS;
}
static char *handle_cli_radio_unkey(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct chan_usbradio_pvt *o = NULL;
switch (cmd) {
case CLI_INIT:
e->command = "radio unkey";
e->usage =
"Usage: radio unkey\n"
" Simulates COR un-active.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 2)
return CLI_SHOWUSAGE;
o = find_desc(usbradio_active);
o->txtestkey = 0;
return CLI_SUCCESS;
}
static char *handle_cli_radio_tune(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct chan_usbradio_pvt *o = NULL;
int i = 0;
switch (cmd) {
case CLI_INIT:
e->command = "radio tune [rxnoise|rxvoice|rxtone|rxsquelch|rxcap|rxtracecap|"
"txvoice|txtone|txcap|txtracecap|auxvoice|nocap|dump|save]";
/* radio tune 6 3000 measured tx value */
e->usage =
"Usage: radio tune <function>\n"
" rxnoise\n"
" rxvoice\n"
" rxtone\n"
" rxsquelch [newsetting]\n"
" rxcap\n"
" rxtracecap\n"
" txvoice [newsetting]\n"
" txtone [newsetting]\n"
" txcap\n"
" txtracecap\n"
" auxvoice [newsetting]\n"
" nocap\n"
" dump\n"
" save (settings to tuning file)\n"
"\n"
" All [newsetting]s are values 0-999\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if ((a->argc < 2) || (a->argc > 4))
return CLI_SHOWUSAGE;
if (a->argc == 2) { /* just show stuff */
ast_cli(a->fd, "Output A is currently set to %s.\n",
o->txmixa == TX_OUT_COMPOSITE ? "composite" :
o->txmixa == TX_OUT_VOICE ? "voice" :
o->txmixa == TX_OUT_LSD ? "tone" :
o->txmixa == TX_OUT_AUX ? "auxvoice" :
"off");
ast_cli(a->fd, "Output B is currently set to %s.\n",
o->txmixb == TX_OUT_COMPOSITE ? "composite" :
o->txmixb == TX_OUT_VOICE ? "voice" :
o->txmixb == TX_OUT_LSD ? "tone" :
o->txmixb == TX_OUT_AUX ? "auxvoice" :
"off");
ast_cli(a->fd, "Tx Voice Level currently set to %d\n", o->txmixaset);
ast_cli(a->fd, "Tx Tone Level currently set to %d\n", o->txctcssadj);
ast_cli(a->fd, "Rx Squelch currently set to %d\n", o->rxsquelchadj);
return CLI_SHOWUSAGE;
}
o = find_desc(usbradio_active);
if (!strcasecmp(a->argv[2], "rxnoise"))
tune_rxinput(o);
else if (!strcasecmp(a->argv[2], "rxvoice"))
tune_rxvoice(o);
else if (!strcasecmp(a->argv[2], "rxtone"))
tune_rxctcss(o);
else if (!strcasecmp(a->argv[2], "rxsquelch")) {
if (a->argc == 3) {
ast_cli(a->fd, "Current Signal Strength is %d\n", ((32767 - o->pmrChan->rxRssi) * 1000 / 32767));
ast_cli(a->fd, "Current Squelch setting is %d\n", o->rxsquelchadj);
#if 0
ast_cli(a->fd,"Current Raw RSSI is %d\n",o->pmrChan->rxRssi);
ast_cli(a->fd,"Current (real) Squelch setting is %d\n",*(o->pmrChan->prxSquelchAdjust));
#endif
} else {
i = atoi(a->argv[3]);
if ((i < 0) || (i > 999))
return CLI_SHOWUSAGE;
ast_cli(a->fd, "Changed Squelch setting to %d\n", i);
o->rxsquelchadj = i;
*(o->pmrChan->prxSquelchAdjust) = ((999 - i) * 32767) / 1000;
}
} else if (!strcasecmp(a->argv[2], "txvoice")) {
i = 0;
if ((o->txmixa != TX_OUT_VOICE) && (o->txmixb != TX_OUT_VOICE) &&
(o->txmixa != TX_OUT_COMPOSITE) && (o->txmixb != TX_OUT_COMPOSITE)) {
ast_log(LOG_ERROR, "No txvoice output configured.\n");
} else if (a->argc == 3) {
if ((o->txmixa == TX_OUT_VOICE) || (o->txmixa == TX_OUT_COMPOSITE))
ast_cli(a->fd, "Current txvoice setting on Channel A is %d\n", o->txmixaset);
else
ast_cli(a->fd, "Current txvoice setting on Channel B is %d\n", o->txmixbset);
} else {
i = atoi(a->argv[3]);
if ((i < 0) || (i > 999))
return CLI_SHOWUSAGE;
if ((o->txmixa == TX_OUT_VOICE) || (o->txmixa == TX_OUT_COMPOSITE)) {
o->txmixaset = i;
ast_cli(a->fd, "Changed txvoice setting on Channel A to %d\n", o->txmixaset);
} else {
o->txmixbset = i;
ast_cli(a->fd, "Changed txvoice setting on Channel B to %d\n", o->txmixbset);
}
mixer_write(o);
mult_set(o);
ast_cli(a->fd, "Changed Tx Voice Output setting to %d\n", i);
}
tune_txoutput(o,i);
} else if (!strcasecmp(a->argv[2], "auxvoice")) {
i = 0;
if ( (o->txmixa != TX_OUT_AUX) && (o->txmixb != TX_OUT_AUX))
ast_log(LOG_WARNING, "No auxvoice output configured.\n");
else if (a->argc == 3) {
if (o->txmixa == TX_OUT_AUX)
ast_cli(a->fd, "Current auxvoice setting on Channel A is %d\n", o->txmixaset);
else
ast_cli(a->fd, "Current auxvoice setting on Channel B is %d\n", o->txmixbset);
} else {
i = atoi(a->argv[3]);
if ((i < 0) || (i > 999))
return CLI_SHOWUSAGE;
if (o->txmixa == TX_OUT_AUX) {
o->txmixbset = i;
ast_cli(a->fd, "Changed auxvoice setting on Channel A to %d\n", o->txmixaset);
} else {
o->txmixbset = i;
ast_cli(a->fd, "Changed auxvoice setting on Channel B to %d\n", o->txmixbset);
}
mixer_write(o);
mult_set(o);
}
/* tune_auxoutput(o,i); */
} else if (!strcasecmp(a->argv[2], "txtone")) {
if (a->argc == 3)
ast_cli(a->fd, "Current Tx CTCSS modulation setting = %d\n", o->txctcssadj);
else {
i = atoi(a->argv[3]);
if ((i < 0) || (i > 999))
return CLI_SHOWUSAGE;
o->txctcssadj = i;
set_txctcss_level(o);
ast_cli(a->fd, "Changed Tx CTCSS modulation setting to %i\n", i);
}
o->txtestkey = 1;
usleep(5000000);
o->txtestkey = 0;
} else if (!strcasecmp(a->argv[2],"dump"))
pmrdump(o);
else if (!strcasecmp(a->argv[2],"nocap")) {
ast_cli(a->fd, "File capture (trace) was rx=%d tx=%d and now off.\n", o->b.rxcap2, o->b.txcap2);
ast_cli(a->fd, "File capture (raw) was rx=%d tx=%d and now off.\n", o->b.rxcapraw, o->b.txcapraw);
o->b.rxcapraw = o->b.txcapraw = o->b.rxcap2 = o->b.txcap2 = o->pmrChan->b.rxCapture = o->pmrChan->b.txCapture = 0;
if (frxcapraw) {
fclose(frxcapraw);
frxcapraw = NULL;
}
if (frxcaptrace) {
fclose(frxcaptrace);
frxcaptrace = NULL;
}
if (frxoutraw) {
fclose(frxoutraw);
frxoutraw = NULL;
}
if (ftxcapraw) {
fclose(ftxcapraw);
ftxcapraw = NULL;
}
if (ftxcaptrace) {
fclose(ftxcaptrace);
ftxcaptrace = NULL;
}
if (ftxoutraw) {
fclose(ftxoutraw);
ftxoutraw = NULL;
}
} else if (!strcasecmp(a->argv[2], "rxtracecap")) {
if (!frxcaptrace)
frxcaptrace = fopen(RX_CAP_TRACE_FILE, "w");
ast_cli(a->fd, "Trace rx on.\n");
o->b.rxcap2 = o->pmrChan->b.rxCapture = 1;
} else if (!strcasecmp(a->argv[2], "txtracecap")) {
if (!ftxcaptrace)
ftxcaptrace = fopen(TX_CAP_TRACE_FILE, "w");
ast_cli(a->fd, "Trace tx on.\n");
o->b.txcap2 = o->pmrChan->b.txCapture = 1;
} else if (!strcasecmp(a->argv[2], "rxcap")) {
if (!frxcapraw)
frxcapraw = fopen(RX_CAP_RAW_FILE, "w");
ast_cli(a->fd, "cap rx raw on.\n");
o->b.rxcapraw = 1;
} else if (!strcasecmp(a->argv[2], "txcap")) {
if (!ftxcapraw)
ftxcapraw = fopen(TX_CAP_RAW_FILE, "w");
ast_cli(a->fd, "cap tx raw on.\n");
o->b.txcapraw = 1;
} else if (!strcasecmp(a->argv[2], "save")) {
tune_write(o);
ast_cli(a->fd, "Saved radio tuning settings to usbradio_tune.conf\n");
} else
return CLI_SHOWUSAGE;
return CLI_SUCCESS;
}
/*
set transmit ctcss modulation level
adjust mixer output or internal gain depending on output type
setting range is 0.0 to 0.9
*/
static int set_txctcss_level(struct chan_usbradio_pvt *o)
{
if (o->txmixa == TX_OUT_LSD) {
o->txmixaset = (151 * o->txctcssadj) / 1000;
mixer_write(o);
mult_set(o);
} else if (o->txmixb == TX_OUT_LSD) {
o->txmixbset = (151 * o->txctcssadj) / 1000;
mixer_write(o);
mult_set(o);
} else {
*o->pmrChan->ptxCtcssAdjust = (o->txctcssadj * M_Q8) / 1000;
}
return 0;
}
/*
CLI debugging on and off
*/
static char *handle_cli_radio_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct chan_usbradio_pvt *o = NULL;
switch (cmd) {
case CLI_INIT:
e->command = "radio set debug [off]";
e->usage =
"Usage: radio set debug [off]\n"
" Enable/Disable radio debugging.\n";
case CLI_GENERATE:
return NULL;
}
if (a->argc < 3 || a->argc > 4)
return CLI_SHOWUSAGE;
if (a->argc == 4 && strncasecmp(a->argv[3], "off", 3))
return CLI_SHOWUSAGE;
o = find_desc(usbradio_active);
if (a->argc == 3)
o->debuglevel = 1;
else
o->debuglevel = 0;
ast_cli(a->fd, "USB Radio debugging %s.\n", o->debuglevel ? "enabled" : "disabled");
return CLI_SUCCESS;
}
static struct ast_cli_entry cli_usbradio[] = {
AST_CLI_DEFINE(handle_cli_radio_key, "Simulate Rx Signal Present"),
AST_CLI_DEFINE(handle_cli_radio_unkey, "Simulate Rx Signal Lusb"),
AST_CLI_DEFINE(handle_cli_radio_tune, "Radio Tune"),
AST_CLI_DEFINE(handle_cli_radio_set_debug, "Enable/Disable Radio Debugging"),
};
/*
* store the callerid components
*/
#if 0
static void store_callerid(struct chan_usbradio_pvt *o, const char *s)
{
ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
}
#endif
static void store_rxdemod(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no")) {
o->rxdemod = RX_AUDIO_NONE;
} else if (!strcasecmp(s, "speaker")) {
o->rxdemod = RX_AUDIO_SPEAKER;
} else if (!strcasecmp(s, "flat")) {
o->rxdemod = RX_AUDIO_FLAT;
} else {
ast_log(LOG_WARNING, "Unrecognized rxdemod parameter: %s\n", s);
}
ast_debug(4, "set rxdemod = %s\n", s);
}
static void store_txmixa(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no"))
o->txmixa = TX_OUT_OFF;
else if (!strcasecmp(s, "voice"))
o->txmixa = TX_OUT_VOICE;
else if (!strcasecmp(s, "tone"))
o->txmixa = TX_OUT_LSD;
else if (!strcasecmp(s, "composite"))
o->txmixa = TX_OUT_COMPOSITE;
else if (!strcasecmp(s, "auxvoice"))
o->txmixb = TX_OUT_AUX;
else
ast_log(LOG_WARNING, "Unrecognized txmixa parameter: %s\n", s);
ast_debug(4, "set txmixa = %s\n", s);
}
static void store_txmixb(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no"))
o->txmixb = TX_OUT_OFF;
else if (!strcasecmp(s, "voice"))
o->txmixb = TX_OUT_VOICE;
else if (!strcasecmp(s, "tone"))
o->txmixb = TX_OUT_LSD;
else if (!strcasecmp(s, "composite"))
o->txmixb = TX_OUT_COMPOSITE;
else if (!strcasecmp(s, "auxvoice"))
o->txmixb = TX_OUT_AUX;
else
ast_log(LOG_WARNING, "Unrecognized txmixb parameter: %s\n", s);
ast_debug(4, "set txmixb = %s\n", s);
}
static void store_rxcdtype(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no"))
o->rxcdtype = CD_IGNORE;
else if (!strcasecmp(s, "usb"))
o->rxcdtype = CD_HID;
else if (!strcasecmp(s, "dsp"))
o->rxcdtype = CD_XPMR_NOISE;
else if (!strcasecmp(s, "vox"))
o->rxcdtype = CD_XPMR_VOX;
else if (!strcasecmp(s, "usbinvert"))
o->rxcdtype = CD_HID_INVERT;
else
ast_log(LOG_WARNING, "Unrecognized rxcdtype parameter: %s\n", s);
ast_debug(4, "set rxcdtype = %s\n", s);
}
static void store_rxsdtype(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no") || !strcasecmp(s, "SD_IGNORE"))
o->rxsdtype = SD_IGNORE;
else if (!strcasecmp(s, "usb") || !strcasecmp(s, "SD_HID"))
o->rxsdtype = SD_HID;
else if (!strcasecmp(s, "usbinvert") || !strcasecmp(s, "SD_HID_INVERT"))
o->rxsdtype = SD_HID_INVERT;
else if (!strcasecmp(s, "software") || !strcasecmp(s, "SD_XPMR"))
o->rxsdtype = SD_XPMR;
else
ast_log(LOG_WARNING, "Unrecognized rxsdtype parameter: %s\n", s);
ast_debug(4, "set rxsdtype = %s\n", s);
}
static void store_rxgain(struct chan_usbradio_pvt *o, const char *s)
{
float f;
if (sscanf(s, "%f", &f) == 1)
o->rxgain = f;
ast_debug(4, "set rxgain = %f\n", f);
}
static void store_rxvoiceadj(struct chan_usbradio_pvt *o, const char *s)
{
float f;
if (sscanf(s, "%f", &f) == 1)
o->rxvoiceadj = f;
ast_debug(4, "set rxvoiceadj = %f\n", f);
}
static void store_rxctcssadj(struct chan_usbradio_pvt *o, const char *s)
{
float f;
if (sscanf(s, "%f", &f) == 1)
o->rxctcssadj = f;
ast_debug(4, "set rxctcssadj = %f\n", f);
}
static void store_txtoctype(struct chan_usbradio_pvt *o, const char *s)
{
if (!strcasecmp(s, "no") || !strcasecmp(s, "TOC_NONE"))
o->txtoctype = TOC_NONE;
else if (!strcasecmp(s, "phase") || !strcasecmp(s, "TOC_PHASE"))
o->txtoctype = TOC_PHASE;
else if (!strcasecmp(s, "notone") || !strcasecmp(s, "TOC_NOTONE"))
o->txtoctype = TOC_NOTONE;
else
ast_log(LOG_WARNING, "Unrecognized txtoctype parameter: %s\n", s);
ast_debug(4, "set txtoctype = %s\n", s);
}
static void store_rxctcssfreq(struct chan_usbradio_pvt *o, const char *s)
{
float f;
if (sscanf(s, "%f", &f) == 1)
o->rxctcssfreq = f;
ast_debug(4, "set rxctcss = %f\n", f);
}
static void store_txctcssfreq(struct chan_usbradio_pvt *o, const char *s)
{
float f;
if (sscanf(s, "%f", &f) == 1)
o->txctcssfreq = f;
ast_debug(4, "set txctcss = %f\n", f);
}
static void tune_txoutput(struct chan_usbradio_pvt *o, int value)
{
o->txtestkey = 1;
o->pmrChan->txPttIn = 1;
#if 0
/* generate 1KHz tone at 7200 peak */
o->pmrChan->spsSigGen1->freq = 10000;
o->pmrChan->spsSigGen1->outputGain = (float)(0.22 * M_Q8);
o->pmrChan->b.startSpecialTone = 1;
#endif
TxTestTone(o->pmrChan, 1);
usleep(5000000);
/* o->pmrChan->b.stopSpecialTone = 1; */
usleep(100000);
TxTestTone(o->pmrChan, 0);
o->pmrChan->txPttIn = 0;
o->txtestkey = 0;
}
static void tune_rxinput(struct chan_usbradio_pvt *o)
{
const int target = 23000;
const int tolerance = 2000;
const int settingmin = 1;
const int settingstart = 2;
const int maxtries = 12;
float settingmax;
int setting = 0, tries = 0, tmpdiscfactor, meas;
int tunetype = 0;
settingmax = o->micmax;
if (o->pmrChan->rxDemod)
tunetype = 1;
setting = settingstart;
while (tries < maxtries) {
setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL, setting, 0);
setamixer(o->devicenum, MIXER_PARAM_MIC_BOOST, o->rxboostset, 0);
usleep(100000);
if (o->rxcdtype == CD_XPMR_VOX || o->rxdemod == RX_AUDIO_SPEAKER) {
ast_debug(4, "Measure Direct Input\n");
o->pmrChan->spsMeasure->source = o->pmrChan->spsRx->source;
o->pmrChan->spsMeasure->discfactor = 1000;
o->pmrChan->spsMeasure->enabled = 1;
o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
usleep(400000);
meas = o->pmrChan->spsMeasure->apeak;
o->pmrChan->spsMeasure->enabled = 0;
} else {
ast_debug(4, "Measure HF Noise\n");
tmpdiscfactor = o->pmrChan->spsRx->discfactor;
o->pmrChan->spsRx->discfactor = (i16)1000;
o->pmrChan->spsRx->discounteru = o->pmrChan->spsRx->discounterl = 0;
o->pmrChan->spsRx->amax = o->pmrChan->spsRx->amin = 0;
usleep(200000);
meas = o->pmrChan->rxRssi;
o->pmrChan->spsRx->discfactor = tmpdiscfactor;
o->pmrChan->spsRx->discounteru = o->pmrChan->spsRx->discounterl = 0;
o->pmrChan->spsRx->amax = o->pmrChan->spsRx->amin = 0;
}
if (!meas)
meas++;
ast_log(LOG_NOTICE, "tries=%d, setting=%d, meas=%i\n", tries, setting, meas);
if ( meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
setting = setting * target / meas;
else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance) )
break;
if (setting < settingmin)
setting = settingmin;
else if (setting > settingmax)
setting = settingmax;
tries++;
}
ast_log(LOG_NOTICE, "DONE tries=%d, setting=%d, meas=%i\n", tries,
(setting * 1000) / o->micmax, meas);
if (meas < (target - tolerance) || meas > (target + tolerance))
ast_log(LOG_NOTICE, "ERROR: RX INPUT ADJUST FAILED.\n");
else {
ast_log(LOG_NOTICE, "INFO: RX INPUT ADJUST SUCCESS.\n");
o->rxmixerset = (setting * 1000) / o->micmax;
}
}
/*
*/
static void tune_rxvoice(struct chan_usbradio_pvt *o)
{
const int target = 7200; /* peak */
const int tolerance = 360; /* peak to peak */
const float settingmin = 0.1;
const float settingmax = 4;
const float settingstart = 1;
const int maxtries = 12;
float setting;
int tries = 0, meas;
ast_log(LOG_NOTICE, "INFO: RX VOICE ADJUST START.\n");
ast_log(LOG_NOTICE, "target=%d tolerance=%d\n", target, tolerance);
if (!o->pmrChan->spsMeasure)
ast_log(LOG_ERROR, "NO MEASURE BLOCK.\n");
if (!o->pmrChan->spsMeasure->source || !o->pmrChan->prxVoiceAdjust )
ast_log(LOG_ERROR, "NO SOURCE OR MEASURE SETTING.\n");
o->pmrChan->spsMeasure->source = o->pmrChan->spsRxOut->sink;
o->pmrChan->spsMeasure->enabled = 1;
o->pmrChan->spsMeasure->discfactor = 1000;
setting=settingstart;
ast_debug(4, "ERROR: NO MEASURE BLOCK.\n");
while (tries < maxtries) {
*(o->pmrChan->prxVoiceAdjust) = setting * M_Q8;
usleep(10000);
o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
usleep(1000000);
meas = o->pmrChan->spsMeasure->apeak;
ast_log(LOG_NOTICE, "tries=%d, setting=%f, meas=%i\n", tries, setting, meas);
if (meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
setting = setting * target / meas;
else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance))
break;
if (setting < settingmin)
setting = settingmin;
else if (setting > settingmax)
setting = settingmax;
tries++;
}
o->pmrChan->spsMeasure->enabled = 0;
ast_log(LOG_NOTICE, "DONE tries=%d, setting=%f, meas=%f\n", tries, setting, (float)meas);
if (meas < (target - tolerance) || meas > (target + tolerance))
ast_log(LOG_ERROR, "RX VOICE GAIN ADJUST FAILED.\n");
else {
ast_log(LOG_NOTICE, "RX VOICE GAIN ADJUST SUCCESS.\n");
o->rxvoiceadj = setting;
}
}
static void tune_rxctcss(struct chan_usbradio_pvt *o)
{
const int target = 4096;
const int tolerance = 100;
const float settingmin = 0.1;
const float settingmax = 4;
const float settingstart = 1;
const int maxtries = 12;
float setting;
int tries = 0, meas;
ast_log(LOG_NOTICE, "RX CTCSS ADJUST START.\n");
ast_log(LOG_NOTICE, "target=%d tolerance=%d \n", target, tolerance);
o->pmrChan->spsMeasure->source = o->pmrChan->prxCtcssMeasure;
o->pmrChan->spsMeasure->discfactor = 400;
o->pmrChan->spsMeasure->enabled = 1;
setting = settingstart;
while (tries < maxtries) {
*(o->pmrChan->prxCtcssAdjust) = setting * M_Q8;
usleep(10000);
o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
usleep(500000);
meas = o->pmrChan->spsMeasure->apeak;
ast_debug(4, "tries=%d, setting=%f, meas=%i\n", tries, setting, meas);
if (meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
setting = setting * target / meas;
else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance))
break;
if (setting < settingmin)
setting = settingmin;
else if (setting > settingmax)
setting = settingmax;
tries++;
}
o->pmrChan->spsMeasure->enabled = 0;
ast_debug(4, "DONE tries=%d, setting=%f, meas=%f\n", tries, setting, (float)meas);
if (meas < (target - tolerance) || meas > (target + tolerance))
ast_log(LOG_ERROR, "RX CTCSS GAIN ADJUST FAILED.\n");
else {
ast_log(LOG_NOTICE, "RX CTCSS GAIN ADJUST SUCCESS.\n");
o->rxctcssadj = setting;
}
}
/*
this file then is included in chan_usbradio.conf
#include /etc/asterisk/usbradio_tune.conf
*/
static void tune_write(struct chan_usbradio_pvt *o)
{
FILE *fp;
fp = fopen("/etc/asterisk/usbradio_tune.conf", "w");
if (!strcmp(o->name, "dsp"))
fprintf(fp, "[general]\n");
else
fprintf(fp, "[%s]\n", o->name);
fprintf(fp, "; name=%s\n", o->name);
fprintf(fp, "; devicenum=%d\n", o->devicenum);
fprintf(fp, "rxmixerset=%d\n", o->rxmixerset);
fprintf(fp, "rxboostset=%d\n", o->rxboostset);
fprintf(fp, "txmixaset=%d\n", o->txmixaset);
fprintf(fp, "txmixbset=%d\n", o->txmixbset);
fprintf(fp, "rxvoiceadj=%f\n", o->rxvoiceadj);
fprintf(fp, "rxctcssadj=%f\n", o->rxctcssadj);
fprintf(fp, "txctcssadj=%d\n", o->txctcssadj);
fprintf(fp, "rxsquelchadj=%d\n", o->rxsquelchadj);
fclose(fp);
}
static void mixer_write(struct chan_usbradio_pvt *o)
{
setamixer(o->devicenum, MIXER_PARAM_MIC_PLAYBACK_SW, 0, 0);
setamixer(o->devicenum, MIXER_PARAM_MIC_PLAYBACK_VOL, 0, 0);
setamixer(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_SW, 1, 0);
setamixer(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_VOL,
o->txmixaset * o->spkrmax / 1000,
o->txmixbset * o->spkrmax / 1000);
setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL,
o->rxmixerset * o->micmax / 1000, 0);
setamixer(o->devicenum, MIXER_PARAM_MIC_BOOST, o->rxboostset, 0);
setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_SW, 1, 0);
}
/*
adjust dsp multiplier to add resolution to tx level adjustment
*/
static void mult_set(struct chan_usbradio_pvt *o)
{
if (o->pmrChan->spsTxOutA) {
o->pmrChan->spsTxOutA->outputGain =
mult_calc((o->txmixaset * 152) / 1000);
}
if (o->pmrChan->spsTxOutB) {
o->pmrChan->spsTxOutB->outputGain =
mult_calc((o->txmixbset * 152) / 1000);
}
}
/*
* input 0 - 151 outputs are pot and multiplier
*/
static int mult_calc(int value)
{
const int multx = M_Q8;
int pot, mult;
pot= ((int)(value / 4) * 4) + 2;
mult = multx - ((multx * (3 - (value % 4))) / (pot + 2));
return mult;
}
#define pd(x) ast_debug(4, #x" = %d\n", x)
#define pp(x) ast_debug(4, #x" = %p\n", x)
#define ps(x) ast_debug(4, #x" = %s\n", x)
#define pf(x) ast_debug(4, #x" = %f\n", x)
/*
*/
static void pmrdump(struct chan_usbradio_pvt *o)
{
t_pmr_chan *p;
p = o->pmrChan;
ast_debug(4, "odump()\n");
pd(o->devicenum);
pd(o->rxdemod);
pd(o->rxcdtype);
pd(o->rxsdtype);
pd(o->txtoctype);
pd(o->rxmixerset);
pf(o->rxvoiceadj);
pf(o->rxctcssadj);
pd(o->rxsquelchadj);
pd(o->txprelim);
pd(o->txmixa);
pd(o->txmixb);
pd(o->txmixaset);
pd(o->txmixbset);
ast_debug(4, "pmrdump()\n");
ast_debug(4, "prxSquelchAdjust=%d\n", *(o->pmrChan->prxSquelchAdjust));
pd(p->rxCarrierPoint);
pd(p->rxCarrierHyst);
pd(p->rxCtcss->relax);
pf(p->rxCtcssFreq);
pd(p->rxCtcssIndex);
pf(p->txCtcssFreq);
pd(p->txMixA);
pd(p->txMixB);
pd(p->rxDeEmpEnable);
pd(p->rxCenterSlicerEnable);
pd(p->rxCtcssDecodeEnable);
pd(p->rxDcsDecodeEnable);
pd(p->txHpfEnable);
pd(p->txLimiterEnable);
pd(p->txPreEmpEnable);
pd(p->txLpfEnable);
if (p->spsTxOutA)
pd(p->spsTxOutA->outputGain);
if (p->spsTxOutB)
pd(p->spsTxOutB->outputGain);
return;
}
/*
* grab fields from the config file, init the descriptor and open the device.
*/
static struct chan_usbradio_pvt *store_config(struct ast_config *cfg, char *ctg)
{
struct ast_variable *v;
struct chan_usbradio_pvt *o;
struct ast_config *cfg1;
struct ast_flags config_flags = { 0 };
if (ctg == NULL) {
traceusb1(" store_config() ctg == NULL\n");
o = &usbradio_default;
ctg = "general";
} else {
if (!(o = ast_calloc(1, sizeof(*o)))){
return NULL;
}
*o = usbradio_default;
/* "general" is also the default thing */
if (strcmp(ctg, "general") == 0) {
o->name = ast_strdup("dsp");
usbradio_active = o->name;
} else
o->name = ast_strdup(ctg);
}
strcpy(o->mohinterpret, "default");
o->micmax = amixer_max(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL);
o->spkrmax = amixer_max(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_VOL);
/* fill other fields from configuration */
for (v = ast_variable_browse(cfg, ctg); v; v = v->next) {
/* handle jb conf */
if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
continue;
CV_START(v->name, v->value);
CV_UINT("frags", o->frags);
CV_UINT("queuesize", o->queuesize);
CV_UINT("devicenum", o->devicenum);
CV_UINT("debug", usbradio_debug);
CV_BOOL("rxcpusaver", o->rxcpusaver);
CV_BOOL("txcpusaver", o->txcpusaver);
CV_BOOL("invertptt", o->invertptt);
CV_F("rxdemod", store_rxdemod(o, v->value));
CV_BOOL("txprelim", o->txprelim);;
CV_F("txmixa", store_txmixa(o, v->value));
CV_F("txmixb", store_txmixb(o, v->value));
CV_F("carrierfrom", store_rxcdtype(o, v->value));
CV_F("rxsdtype", store_rxsdtype(o, v->value));
CV_F("rxctcssfreq", store_rxctcssfreq(o, v->value));
CV_F("txctcssfreq", store_txctcssfreq(o, v->value));
CV_F("rxgain", store_rxgain(o, v->value));
CV_BOOL("rxboostset", o->rxboostset);
CV_UINT("rxctcssrelax", o->rxctcssrelax);
CV_F("txtoctype", store_txtoctype(o, v->value));
CV_UINT("hdwtype", o->hdwtype);
CV_UINT("duplex", o->radioduplex);
CV_END;
}
cfg1 = ast_config_load(config1, config_flags);
if (!cfg1) {
o->rxmixerset = 500;
o->txmixaset = 500;
o->txmixbset = 500;
o->rxvoiceadj = 0.5;
o->rxctcssadj = 0.5;
o->txctcssadj = 200;
o->rxsquelchadj = 500;
ast_log(LOG_WARNING, "File %s not found, using default parameters.\n", config1);
} else {
for (v = ast_variable_browse(cfg1, ctg); v; v = v->next) {
CV_START(v->name, v->value);
CV_UINT("rxmixerset", o->rxmixerset);
CV_UINT("txmixaset", o->txmixaset);
CV_UINT("txmixbset", o->txmixbset);
CV_F("rxvoiceadj", store_rxvoiceadj(o, v->value));
CV_F("rxctcssadj", store_rxctcssadj(o, v->value));
CV_UINT("txctcssadj", o->txctcssadj);
CV_UINT("rxsquelchadj", o->rxsquelchadj);
CV_END;
}
ast_config_destroy(cfg1);
}
o->debuglevel = 0;
if (o == &usbradio_default) /* we are done with the default */
return NULL;
o->lastopen = ast_tvnow(); /* don't leave it 0 or tvdiff may wrap */
o->dsp = ast_dsp_new();
if (o->dsp) {
ast_dsp_set_features(o->dsp, DSP_FEATURE_DTMF_DETECT);
ast_dsp_digitmode(o->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_RELAXDTMF);
}
if (o->rxctcssfreq != 0 && o->rxdemod == RX_AUDIO_SPEAKER)
ast_log(LOG_ERROR, "Incompatable Options o->rxctcssfreq=%f and o->rxdemod=speaker\n", o->rxctcssfreq);
if (o->pmrChan == NULL) {
t_pmr_chan tChan;
memset(&tChan, 0, sizeof(tChan));
tChan.rxDemod = o->rxdemod;
tChan.rxCdType = o->rxcdtype;
tChan.txMod = o->txprelim;
tChan.txMixA = o->txmixa;
tChan.txMixB = o->txmixb;
tChan.rxCpuSaver = o->rxcpusaver;
tChan.txCpuSaver = o->txcpusaver;
tChan.rxCtcssFreq = o->rxctcssfreq;
tChan.txCtcssFreq = o->txctcssfreq;
o->pmrChan = createPmrChannel(&tChan, FRAME_SIZE);
o->pmrChan->radioDuplex = o->radioduplex;
o->pmrChan->rxCpuSaver = o->rxcpusaver;
o->pmrChan->txCpuSaver = o->txcpusaver;
*(o->pmrChan->prxSquelchAdjust) =
((999 - o->rxsquelchadj) * 32767) / 1000;
o->pmrChan->spsRx->outputGain = o->rxvoiceadj*M_Q8;
o->pmrChan->txTocType = o->txtoctype;
if ((o->txmixa == TX_OUT_LSD) ||
(o->txmixa == TX_OUT_COMPOSITE) ||
(o->txmixb == TX_OUT_LSD) ||
(o->txmixb == TX_OUT_COMPOSITE)) {
*(o->pmrChan->prxCtcssAdjust) = o->rxctcssadj * M_Q8;
set_txctcss_level(o);
}
o->pmrChan->rxCtcss->relax = o->rxctcssrelax;
}
if ((o->txmixa != TX_OUT_VOICE) && (o->txmixb != TX_OUT_VOICE) &&
(o->txmixa != TX_OUT_COMPOSITE) && (o->txmixb != TX_OUT_COMPOSITE))
ast_log(LOG_ERROR, "No txvoice output configured.\n");
if (o->txctcssfreq &&
o->txmixa != TX_OUT_LSD && o->txmixa != TX_OUT_COMPOSITE &&
o->txmixb != TX_OUT_LSD && o->txmixb != TX_OUT_COMPOSITE)
ast_log(LOG_ERROR, "No txtone output configured.\n");
if (o->rxctcssfreq && o->pmrChan->rxCtcssIndex < 0)
ast_log(LOG_ERROR, "Invalid CTCSS Frequency.\n");
/* RxTestIt(o); */
mixer_write(o);
mult_set(o);
hidhdwconfig(o);
/* pmrdump(o); */
if (pipe(o->sndcmd) != 0) {
ast_log(LOG_ERROR, "Unable to create pipe\n");
goto error;
}
ast_debug(4, "creating sound thread\n");
ast_pthread_create_background(&o->sthread, NULL, sound_thread, o);
/* link into list of devices */
if (o != &usbradio_default) {
o->next = usbradio_default.next;
usbradio_default.next = o;
}
return o;
error:
if (o != &usbradio_default)
ast_free(o);
return NULL;
}
#if DEBUG_FILETEST == 1
/*
Test It on a File
*/
int RxTestIt(struct chan_usbradio_pvt *o)
{
const int numSamples = SAMPLES_PER_BLOCK;
const int numChannels = 16;
i16 sample, i, ii;
i32 txHangTime;
i16 txEnable;
t_pmr_chan tChan;
t_pmr_chan *pChan;
FILE *hInput = NULL, *hOutput = NULL, *hOutputTx = NULL;
i16 iBuff[numSamples * 2 * 6], oBuff[numSamples];
ast_debug(4, "RxTestIt()\n");
pChan = o->pmrChan;
pChan->b.txCapture = 1;
pChan->b.rxCapture = 1;
txEnable = 0;
hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm", "r");
if (!hInput){
ast_debug(4, " RxTestIt() File Not Found.\n");
return 0;
}
hOutput = fopen("/usr/src/xpmr/testdata/rx_debug.pcm", "w");
ast_debug(4, " RxTestIt() Working...\n");
while (!feof(hInput)) {
fread((void *)iBuff, 2, numSamples * 2 * 6, hInput);
if (txHangTime)
txHangTime -= numSamples;
if (txHangTime < 0)
txHangTime = 0;
if (pChan->rxCtcss->decode)
txHangTime = (8000 / 1000 * 2000);
if (pChan->rxCtcss->decode && !txEnable) {
txEnable = 1;
/* pChan->inputBlanking = (8000 / 1000 * 200); */
} else if (!pChan->rxCtcss->decode && txEnable) {
txEnable = 0;
}
PmrRx(pChan, iBuff, oBuff);
fwrite((void *)pChan->prxDebug, 2, numSamples * numChannels, hOutput);
}
pChan->b.txCapture = 0;
pChan->b.rxCapture = 0;
if (hInput)
fclose(hInput);
if (hOutput)
fclose(hOutput);
ast_debug(4, " RxTestIt() Complete.\n");
return 0;
}
#endif
#include "./xpmr/xpmr.c"
/*
*/
static int load_module(void)
{
struct ast_config *cfg = NULL;
char *ctg = NULL;
struct ast_flags config_flags = { 0 };
/* Copy the default jb config over global_jbconf */
memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
/* load config file */
if (!(cfg = ast_config_load(config, config_flags))) {
ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
return AST_MODULE_LOAD_DECLINE;
}
do {
store_config(cfg, ctg);
} while ( (ctg = ast_category_browse(cfg, ctg)) != NULL);
ast_config_destroy(cfg);
if (find_desc(usbradio_active) == NULL) {
ast_log(LOG_NOTICE, "Device %s not found\n", usbradio_active);
/* XXX we could default to 'dsp' perhaps ? */
/* XXX should cleanup allocated memory etc. */
return AST_MODULE_LOAD_FAILURE;
}
if (ast_channel_register(&usbradio_tech)) {
ast_log(LOG_ERROR, "Unable to register channel type 'usb'\n");
return AST_MODULE_LOAD_FAILURE;
}
ast_cli_register_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry));
return AST_MODULE_LOAD_SUCCESS;
}
/*
*/
static int unload_module(void)
{
struct chan_usbradio_pvt *o;
ast_log(LOG_WARNING, "unload_module() called\n");
ast_channel_unregister(&usbradio_tech);
ast_cli_unregister_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry));
for (o = usbradio_default.next; o; o = o->next) {
ast_log(LOG_WARNING, "destroyPmrChannel() called\n");
if (o->pmrChan)
destroyPmrChannel(o->pmrChan);
#if DEBUG_CAPTURES == 1
if (frxcapraw) { fclose(frxcapraw); frxcapraw = NULL; }
if (frxcaptrace) { fclose(frxcaptrace); frxcaptrace = NULL; }
if (frxoutraw) { fclose(frxoutraw); frxoutraw = NULL; }
if (ftxcapraw) { fclose(ftxcapraw); ftxcapraw = NULL; }
if (ftxcaptrace) { fclose(ftxcaptrace); ftxcaptrace = NULL; }
if (ftxoutraw) { fclose(ftxoutraw); ftxoutraw = NULL; }
#endif
close(o->sounddev);
if (o->sndcmd[0] > 0) {
close(o->sndcmd[0]);
close(o->sndcmd[1]);
}
if (o->dsp)
ast_dsp_free(o->dsp);
if (o->owner)
ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD);
if (o->owner) /* XXX how ??? */
return -1;
/* XXX what about the thread ? */
/* XXX what about the memory allocated ? */
}
return 0;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "usb Console Channel Driver");
/* end of file */