Browse Source

initial commit

master
Jeroen Vreeken 5 years ago
commit
43096da756
14 changed files with 1598 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +28
    -0
      Makefile
  3. +420
    -0
      analog_trx.c
  4. +98
    -0
      build.mk
  5. +6
    -0
      dtmf.h
  6. +238
    -0
      dtmf_detect.c
  7. +143
    -0
      dtmf_detect.h
  8. +148
    -0
      dtmf_gen.c
  9. +109
    -0
      eth_ar.c
  10. +41
    -0
      eth_ar.h
  11. +119
    -0
      interface.c
  12. +28
    -0
      interface.h
  13. +182
    -0
      sound.c
  14. +35
    -0
      sound.h

+ 3
- 0
.gitignore View File

@ -0,0 +1,3 @@
*.o
*.d
analog_trx

+ 28
- 0
Makefile View File

@ -0,0 +1,28 @@
include build.mk
CFLAGS += -Wall -Werror -O3
LDFLAGS += -lm -lasound -lhamlib -lcodec2
SRCS = \
analog_trx.c \
eth_ar.c \
interface.c \
sound.c \
dtmf_detect.c \
dtmf_gen.c
OBJS = $(SRCS:.c=.o)
all: analog_trx
analog_trx: $(OBJS)
DEPS:=$(SRCS:.c=.d)
-include $(DEPS)
$(OBJS): Makefile
clean:
rm -rf $(OBJS) $(DEPS)\
analog_trx

+ 420
- 0
analog_trx.c View File

@ -0,0 +1,420 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <poll.h>
#include <sched.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <sys/mman.h>
#include <hamlib/rig.h>
#include <codec2/codec2.h>
#include "interface.h"
#include "eth_ar.h"
#include "sound.h"
#include "dtmf.h"
static bool verbose = false;
static bool cdc = false;
static bool fullduplex = false;
int16_t *mod_silence;
struct CODEC2 *rx_codec;
int nr_samples;
int16_t *samples_rx;
int nr_rx;
void squelch(int16_t *samples, int nr)
{
double total = 0;
double high = 0;
int i;
for (i = 0; i < nr; i++) {
total += samples[i] * samples[i];
if (i & 1)
high += samples[i];
else
high -= samples[i];
}
high = high * high;
printf("%f\t%f\t%f\n", total, high, total/high);
}
static void cb_control(char *ctrl)
{
return;
}
static void cb_sound_in(int16_t *samples, int nr)
{
// squelch(samples, nr);
dtmf_decode(samples, nr, cb_control);
while (nr) {
int copy = nr_samples - nr_rx;
if (copy > nr)
copy = nr;
memcpy(samples_rx + nr_rx, samples, copy);
samples += copy;
nr -= copy;
nr_rx += copy;
if (nr_rx == nr_samples) {
int bytes_per_codec_frame = (codec2_bits_per_frame(rx_codec) + 7)/8;
unsigned char packed_codec_bits[bytes_per_codec_frame];
codec2_encode(rx_codec, packed_codec_bits, samples_rx);
interface_rx(packed_codec_bits, bytes_per_codec_frame,
ETH_P_CODEC2_3200);
nr_rx = 0;
}
}
}
uint8_t *tx_data;
size_t tx_data_len;
int tx_mode = -1;
struct CODEC2 *tx_codec = NULL;
static int cb_int_tx(uint8_t *data, size_t len, uint16_t eth_type)
{
int newmode = 0;
switch (eth_type) {
case ETH_P_CODEC2_3200:
newmode = CODEC2_MODE_3200;
break;
case ETH_P_CODEC2_2400:
newmode = CODEC2_MODE_2400;
break;
case ETH_P_CODEC2_1600:
newmode = CODEC2_MODE_1600;
break;
case ETH_P_CODEC2_1400:
newmode = CODEC2_MODE_1400;
break;
case ETH_P_CODEC2_1300:
newmode = CODEC2_MODE_1300;
break;
case ETH_P_CODEC2_1200:
newmode = CODEC2_MODE_1200;
break;
case ETH_P_CODEC2_700:
newmode = CODEC2_MODE_700;
break;
case ETH_P_CODEC2_700B:
newmode = CODEC2_MODE_700B;
break;
default:
return 0;
}
if (newmode != tx_mode) {
if (tx_codec)
codec2_destroy(tx_codec);
tx_codec = codec2_create(newmode);
tx_mode = newmode;
tx_data_len = 0;
}
int bytes_per_codec_frame = (codec2_bits_per_frame(tx_codec) + 7)/8;
while (len) {
size_t copy = len;
if (copy + tx_data_len > bytes_per_codec_frame)
copy = bytes_per_codec_frame - tx_data_len;
memcpy(tx_data + tx_data_len, data, copy);
tx_data_len += copy;
data += copy;
len -= copy;
if (tx_data_len == bytes_per_codec_frame) {
int nr = codec2_samples_per_frame(tx_codec);
int16_t mod_out[nr];
codec2_decode(tx_codec, mod_out, tx_data);
if (nr > 0) {
sound_out(mod_out, nr);
}
tx_data_len = 0;
}
}
return 0;
}
static int prio(void)
{
struct sched_param param;
if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
printf("sched_setscheduler() failed: %s",
strerror(errno));
}
if (mlockall(MCL_CURRENT | MCL_FUTURE )) {
printf("mlockall failed: %s",
strerror(errno));
}
return 0;
}
static RIG *rig;
static rig_model_t rig_model;
static char *ptt_file = NULL;
static ptt_type_t ptt_type = RIG_PTT_NONE;
static int hl_init(void)
{
int retcode;
rig = rig_init(rig_model);
if (!rig) {
printf("Could not init rig\n");
return -1;
}
if (ptt_type != RIG_PTT_NONE)
rig->state.pttport.type.ptt = ptt_type;
if (ptt_file)
strncpy(rig->state.pttport.pathname, ptt_file, FILPATHLEN - 1);
retcode = rig_open(rig);
if (retcode != RIG_OK) {
fprintf(stderr,"rig_open: error = %s \n", rigerror(retcode));
return -2;
}
return 0;
}
uint64_t tx_delay = 10000000;
uint64_t tx_tail = 100000000;
int tx_tail_ms = 100;
bool tx_state = false;
bool tx_started = false;
struct timespec tx_time;
void tx_delay_start(void)
{
if (!tx_state) {
if (!tx_started) {
tx_started = true;
if (verbose)
printf("TX on\n");
rig_set_ptt(rig, RIG_VFO_CURR, RIG_PTT_ON);
clock_gettime(CLOCK_MONOTONIC, &tx_time);
} else {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t ontime = now.tv_sec - tx_time.tv_sec;
ontime *= 1000000000;
ontime += now.tv_nsec - tx_time.tv_nsec;
if (ontime >= tx_delay) {
if (verbose)
printf("TX-delay done\n");
tx_state = true;
}
}
}
}
void tx_tail_extend(void)
{
clock_gettime(CLOCK_MONOTONIC, &tx_time);
}
void tx_tail_check(void)
{
if (tx_state) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t ontime = now.tv_sec - tx_time.tv_sec;
ontime *= 1000000000;
ontime += now.tv_nsec - tx_time.tv_nsec;
if (ontime >= tx_tail) {
rig_set_ptt(rig, RIG_VFO_CURR, RIG_PTT_OFF);
tx_state = false;
tx_started = false;
if (verbose)
printf("TX tail done\n");
}
}
}
static void usage(void)
{
printf("Options:\n");
printf("-v\tverbose\n");
printf("-c [call]\town callsign\n");
printf("-f\tfull-duplex\n");
printf("-s [dev]\tSound device (default: \"default\")\n");
printf("-n [dev]\tNetwork device name (default: \"freedv\")\n");
printf("-m [model]\tHAMlib rig model\n");
printf("-P [type]\tHAMlib PTT type\n");
printf("-p [dev]\tHAMlib PTT device file\n");
printf("-d [msec]\tTX delay\n");
printf("-t [msec]\tTX tail\n");
}
int main(int argc, char **argv)
{
char *call = "pirate";
char *sounddev = "default";
char *netname = "analog";
int ssid = 0;
uint8_t mac[6];
int fd_int;
struct pollfd *fds;
int sound_fdc_tx;
int sound_fdc_rx;
int nfds;
int poll_int;
int opt;
int mode = CODEC2_MODE_3200;
rig_model = 1; // set to dummy.
while ((opt = getopt(argc, argv, "vc:s:n:m:d:t:p:P:f")) != -1) {
switch(opt) {
case 'v':
verbose = true;
break;
case 'c':
call = optarg;
break;
case 's':
sounddev = optarg;
break;
case 'n':
netname = optarg;
break;
case 'm':
rig_model = atoi(optarg);
break;
case 'p':
ptt_file = optarg;
break;
case 'P':
if (!strcmp(optarg, "RIG"))
ptt_type = RIG_PTT_RIG;
else if (!strcmp(optarg, "DTR"))
ptt_type = RIG_PTT_SERIAL_DTR;
else if (!strcmp(optarg, "RTS"))
ptt_type = RIG_PTT_SERIAL_RTS;
else if (!strcmp(optarg, "PARALLEL"))
ptt_type = RIG_PTT_PARALLEL;
else if (!strcmp(optarg, "CM108"))
ptt_type = RIG_PTT_CM108;
else if (!strcmp(optarg, "NONE"))
ptt_type = RIG_PTT_NONE;
else
ptt_type = atoi(optarg);
break;
case 'd':
tx_delay = 1000000 * atoi(optarg);
break;
case 't':
tx_tail = 1000000 * atoi(optarg);
tx_tail_ms = tx_tail / 1000000;
break;
case 'f':
fullduplex = true;
break;
default:
usage();
return -1;
}
}
eth_ar_call2mac(mac, call, ssid, false);
rx_codec = codec2_create(mode);
nr_samples = codec2_samples_per_frame(rx_codec);
samples_rx = calloc(nr_samples, sizeof(samples_rx[0]));
tx_data = calloc(16, sizeof(uint8_t));
mod_silence = calloc(nr_samples, sizeof(mod_silence[0]));
fd_int = interface_init(netname, mac);
sound_init(sounddev, cb_sound_in, nr_samples);
hl_init();
prio();
if (fd_int < 0) {
printf("Could not create interface\n");
return -1;
}
sound_fdc_tx = sound_poll_count_tx();
sound_fdc_rx = sound_poll_count_rx();
nfds = sound_fdc_tx + sound_fdc_rx + 1;
fds = calloc(sizeof(struct pollfd), nfds);
sound_poll_fill_tx(fds, sound_fdc_tx);
sound_poll_fill_rx(fds + sound_fdc_tx, sound_fdc_rx);
poll_int = sound_fdc_tx + sound_fdc_rx;
fds[poll_int].fd = fd_int;
fds[poll_int].events = POLLIN;
do {
poll(fds, nfds, tx_state ? tx_tail_ms : -1);
if (fds[poll_int].revents & POLLIN) {
if (!tx_state && (!cdc || fullduplex)) {
tx_delay_start();
} else {
interface_tx(cb_int_tx);
tx_tail_extend();
}
}
if (sound_poll_out_tx(fds, sound_fdc_tx)) {
sound_out(mod_silence, nr_samples);
}
if (sound_poll_in_rx(fds + sound_fdc_tx, sound_fdc_rx)) {
sound_rx();
}
tx_tail_check();
} while (1);
return 0;
}

+ 98
- 0
build.mk View File

@ -0,0 +1,98 @@
# Some make rules to make output pretty....
# default ARFLAGS also has 'v', but we don't want it to be verbose.
ARFLAGS= -r
# make sure libs from /usr/local/lib are found
VPATH= /lib64 /usr/lib64 /usr/local/lib64 /lib /usr/lib /usr/local/lib
LIBTOOL=libtool
OS= $(shell uname -s)
HW= $(shell uname -m)
ifneq ($(OS), FreeBSD)
FLEX=flex
else
FLEX=/usr/local/bin/flex
endif
BUILDCC:=${CC}
BUILDLIBTOOL:=${LIBTOOL}
ifdef BUILDSYS
BUILDCC:=${BUILDSYS}-gcc
BUILDLIBTOOL:=${BUILDSYS}-libtool
endif
ifdef HOSTSYS
CC=${HOSTSYS}-gcc
LIBTOOL=${HOSTSYS}-libtool
CONF_HOST=--host=${HOSTSYS}
HW=$(HOSTSYS)
endif
%.o : %.c
@echo " CC $<"
@$(CC) -MMD $(CFLAGS) -c $< -o $@
%.o : %.il2c.c
@echo "LT CCil $<"
@${LIBTOOL} --quiet --mode=compile --tag=CC $(CC) -MMD $(CFLAGS) -c $< -o $@
@sed -e "s:\.il2c.c:\.il:" -i -i $*.il2c.d
%: %.o
@echo " LD $@"
@${LIBTOOL} --quiet --mode=link --tag=CC $(LINK.o) $(filter %.o,$^) $(LOADLIBS) $(LDLIBS) $($@_LDFLAGS) -o $@
%.lo: %.c
@echo "LT CC $<"
@${LIBTOOL} --quiet --mode=compile --tag=CC $(CC) -MMD $(CFLAGS) -c $< -o $@
@cat $(dir $*).libs/$(*F).d | sed -e "s:\.libs/::" -e "s:\.o:\.lo:" > $*.d
%.lo: %.il2c.c
@echo "LT ilCC $<"
@${LIBTOOL} --quiet --mode=compile --tag=CC $(CC) -MMD $(CFLAGS) -c $<
@cat $(dir $*).libs/$(*F).d | sed -e "s:\.libs/::" -e "s:\.o:\.lo:" -e "s:\.il2c.c:\.il:" > $*.d
define LIB_LINK
@echo "LT LD $@"
@${LIBTOOL} --quiet --mode=link --tag=CC $(CC) $(filter %.lo,$^) -o $@ $(LDFLAGS) $($@_LDFLAGS) -static-libtool-libs -rpath $(abspath $(@D))
@echo "LT INST $@"
@${LIBTOOL} --quiet --mode=install install $@ $(abspath $(@D))
@sed -i -i s\|=$(CURDIR)\|$(CURDIR)\|g $@
endef
%.so:
@echo "LT soLD $@"
@${LIBTOOL} --quiet --mode=link --tag=CC $(CC) $(filter %.lo,$^) -o $@ $(LDFLAGS) $($@_LDFLAGS)
(%): %
@echo " AR $^ in $@"
@$(AR) $(ARFLAGS) $@ $^
%.tab.c %.tab.h: %.y
@echo "BISON $<"
@bison --defines=$*.tab.h $< -o $*.tab.c
%.yy.c %.yy.h: %.l %.tab.h
@echo " FLEX $<"
@$(FLEX) --header-file=$*.yy.h -o $*.yy.c $<
# il2c: instruction list 2 c 'compiler'
%.il2c.c: %.il
@echo " IL2C $<"
@$(IL2C) $<
# dot -> pdf
%.pdf: %.dot
@echo " DOT $<"
@dot $< -o $@ -Tpdf
%.dtb: %.dts
@echo " DTC $<"
@dtc -I dts $< -O dtb -o $@
%.dtbo: %.dts
@echo " DTCo $<"
@dtc -I dts $< -O dtb -o $@

+ 6
- 0
dtmf.h View File

@ -0,0 +1,6 @@
#ifndef _INCLUDE_DTMF_H_
#define _INCLUDE_DTMF_H_
int dtmf_decode(short *samples, int nr, void (*cb)(char *));
#endif /* _INCLUDE_DTMF_H_ */

+ 238
- 0
dtmf_detect.c View File

@ -0,0 +1,238 @@
/*
* detect.c
* This program will detect MF tones and normal
* dtmf tones as well as some other common tones such
* as BUSY, DIALTONE and RING.
* The program uses a goertzel algorithm to detect
* the power of various frequency ranges.
*
* input is assumed to be 8 bit samples. The program
* can use either signed or unsigned samples according
* to a compile time option:
*
* cc -DUNSIGNED detect.c -o detect
*
* for unsigned input (soundblaster) and:
*
* cc detect.c -o detect
*
* for signed input (amiga samples)
* if you dont want flushes, -DNOFLUSH
*
* Tim N.
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "dtmf_detect.h"
#include "dtmf.h"
/* translation of above codes into text */
static char *dtran[] = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"*", "#", "A", "B", "C", "D",
"+C11 ", "+C12 ", " KP1+", " KP2+", "+ST ",
" 2400 ", " 2600 ", " 2400+2600 ",
" DIALTONE ", " RING ", " BUSY ","" };
/*
* calculate the power of each tone according
* to a modified goertzel algorithm described in
* _digital signal processing applications using the
* ADSP-2100 family_ by Analog Devices
*
* input is 'data', N sample values
*
* ouput is 'power', NUMTONES values
* corresponding to the power of each tone
*/
static int calc_power(short *data, float *power)
{
float u0[NUMTONES],u1[NUMTONES],t,in;
int i,j;
for(j=0; j<NUMTONES; j++) {
u0[j] = 0.0;
u1[j] = 0.0;
}
for(i=0; i<N; i++) { /* feedback */
in = ((int)data[i] - 32768) / 32768.0;
for(j=0; j<NUMTONES; j++) {
t = u0[j];
u0[j] = in + coef[j] * u0[j] - u1[j];
u1[j] = t;
}
}
for(j=0; j<NUMTONES; j++) /* feedforward */
power[j] = u0[j] * u0[j] + u1[j] * u1[j] - coef[j] * u0[j] * u1[j];
return(0);
}
/*
* detect which signals are present.
*
* return values defined in the include file
* note: DTMF 3 and MF 7 conflict. To resolve
* this the program only reports MF 7 between
* a KP and an ST, otherwise DTMF 3 is returned
*/
static int decode(short *data)
{
float power[NUMTONES],thresh,maxpower;
int on[NUMTONES],on_count;
int bcount, rcount, ccount;
int row, col, b1, b2, i;
int r[4],c[4],b[8];
static int MFmode=0;
calc_power(data,power);
for(i=0, maxpower=0.0; i<NUMTONES;i++)
if(power[i] > maxpower)
maxpower = power[i];
/*
for(i=0;i<NUMTONES;i++)
printf("%f, ",power[i]);
printf("\n");
*/
if(maxpower < THRESH) /* silence? */
return(DSIL);
thresh = RANGE * maxpower; /* allowable range of powers */
for(i=0, on_count=0; i<NUMTONES; i++) {
if(power[i] > thresh) {
on[i] = 1;
on_count ++;
} else
on[i] = 0;
}
/*
printf("%4d: ",on_count);
for(i=0;i<NUMTONES;i++)
putchar('0' + on[i]);
printf("\n");
*/
if(on_count == 1) {
if(on[B7])
return(D24);
if(on[B8])
return(D26);
return(-1);
}
if(on_count == 2) {
if(on[X1] && on[X2])
return(DDT);
if(on[X2] && on[X3])
return(DRING);
if(on[X3] && on[X4])
return(DBUSY);
b[0]= on[B1]; b[1]= on[B2]; b[2]= on[B3]; b[3]= on[B4];
b[4]= on[B5]; b[5]= on[B6]; b[6]= on[B7]; b[7]= on[B8];
c[0]= on[C1]; c[1]= on[C2]; c[2]= on[C3]; c[3]= on[C4];
r[0]= on[R1]; r[1]= on[R2]; r[2]= on[R3]; r[3]= on[R4];
for(i=0, bcount=0; i<8; i++) {
if(b[i]) {
bcount++;
b2 = b1;
b1 = i;
}
}
for(i=0, rcount=0; i<4; i++) {
if(r[i]) {
rcount++;
row = i;
}
}
for(i=0, ccount=0; i<4; i++) {
if(c[i]) {
ccount++;
col = i;
}
}
if(rcount==1 && ccount==1) { /* DTMF */
if(col == 3) /* A,B,C,D */
return(DA + row);
else {
if(row == 3 && col == 0 )
return(DSTAR);
if(row == 3 && col == 2 )
return(DPND);
if(row == 3)
return(D0);
if(row == 0 && col == 2) { /* DTMF 3 conflicts with MF 7 */
if(!MFmode)
return(D3);
} else
return(D1 + col + row*3);
}
}
if(bcount == 2) { /* MF */
/* b1 has upper number, b2 has lower */
switch(b1) {
case 7: return( (b2==6)? D2426: -1);
case 6: return(-1);
case 5: if(b2==2 || b2==3) /* KP */
MFmode=1;
if(b2==4) /* ST */
MFmode=0;
return(DC11 + b2);
/* MF 7 conflicts with DTMF 3, but if we made it
* here then DTMF 3 was already tested for
*/
case 4: return( (b2==3)? D0: D7 + b2);
case 3: return(D4 + b2);
case 2: return(D2 + b2);
case 1: return(D1);
}
}
return(-1);
}
if(on_count == 0)
return(DSIL);
return(-1);
}
static short dtmf_data[N];
static int dtmf_data_len;
static int prev_dec = DSIL;
int dtmf_decode(short *samples, int nr, void (*cb)(char *))
{
while (nr) {
int copy = N - dtmf_data_len;
if (copy > nr)
copy = nr;
memcpy(dtmf_data + dtmf_data_len, samples, copy);
samples += copy;
dtmf_data_len += copy;
nr -= copy;
if (dtmf_data_len == N) {
int dec = decode(dtmf_data);
if (dec >= 0 && dec != DSIL) {
if (prev_dec != dec) {
cb(dtran[dec]);
printf("DTMF: %s\n", dtran[dec]);
}
} else {
// printf("%d\n", dec);
}
prev_dec = dec;
dtmf_data_len = 0;
}
}
return 0;
}

+ 143
- 0
dtmf_detect.h View File

@ -0,0 +1,143 @@
/*
*
* goertzel aglorithm, find the power of different
* frequencies in an N point DFT.
*
* ftone/fsample = k/N
* k and N are integers. fsample is 8000 (8khz)
* this means the *maximum* frequency resolution
* is fsample/N (each step in k corresponds to a
* step of fsample/N hz in ftone)
*
* N was chosen to minimize the sum of the K errors for
* all the tones detected... here are the results :
*
* Best N is 240, with the sum of all errors = 3.030002
* freq freq actual k kactual kerr
* ---- ------------ ------ ------- -----
* 350 (366.66667) 10.500 (11) 0.500
* 440 (433.33333) 13.200 (13) 0.200
* 480 (466.66667) 14.400 (14) 0.400
* 620 (633.33333) 18.600 (19) 0.400
* 697 (700.00000) 20.910 (21) 0.090
* 700 (700.00000) 21.000 (21) 0.000
* 770 (766.66667) 23.100 (23) 0.100
* 852 (866.66667) 25.560 (26) 0.440
* 900 (900.00000) 27.000 (27) 0.000
* 941 (933.33333) 28.230 (28) 0.230
* 1100 (1100.00000) 33.000 (33) 0.000
* 1209 (1200.00000) 36.270 (36) 0.270
* 1300 (1300.00000) 39.000 (39) 0.000
* 1336 (1333.33333) 40.080 (40) 0.080
**** I took out 1477.. too close to 1500
* 1477 (1466.66667) 44.310 (44) 0.310
****
* 1500 (1500.00000) 45.000 (45) 0.000
* 1633 (1633.33333) 48.990 (49) 0.010
* 1700 (1700.00000) 51.000 (51) 0.000
* 2400 (2400.00000) 72.000 (72) 0.000
* 2600 (2600.00000) 78.000 (78) 0.000
*
* notice, 697 and 700hz are indestinguishable (same K)
* all other tones have a seperate k value.
* these two tones must be treated as identical for our
* analysis.
*
* The worst tones to detect are 350 (error = 0.5,
* detet 367 hz) and 852 (error = 0.44, detect 867hz).
* all others are very close.
*
*/
#ifndef _INCLUDE_DTMF_DETECT_H_
#define _INCLUDE_DTMF_DETECT_H_
#define FSAMPLE 8000
#define N 240
int k[] = { 11, 13, 14, 19, 21, 23, 26, 27, 28, 33, 36, 39, 40,
/*44,*/ 45, 49, 51, 72, 78, };
/* coefficients for above k's as:
* 2 * cos( 2*pi* k/N )
*/
float coef[] = {
1.917639, 1.885283, 1.867161, 1.757634,
1.705280, 1.648252, 1.554292, 1.520812, 1.486290,
1.298896, 1.175571, 1.044997, 1.000000, /* 0.813473,*/
0.765367, 0.568031, 0.466891, -0.618034, -0.907981, };
#define X1 0 /* 350 dialtone */
#define X2 1 /* 440 ring, dialtone */
#define X3 2 /* 480 ring, busy */
#define X4 3 /* 620 busy */
#define R1 4 /* 697, dtmf row 1 */
#define R2 5 /* 770, dtmf row 2 */
#define R3 6 /* 852, dtmf row 3 */
#define R4 8 /* 941, dtmf row 4 */
#define C1 10 /* 1209, dtmf col 1 */
#define C2 12 /* 1336, dtmf col 2 */
#define C3 13 /* 1477, dtmf col 3 */
#define C4 14 /* 1633, dtmf col 4 */
#define B1 4 /* 700, blue box 1 */
#define B2 7 /* 900, bb 2 */
#define B3 9 /* 1100, bb 3 */
#define B4 11 /* 1300, bb4 */
#define B5 13 /* 1500, bb5 */
#define B6 15 /* 1700, bb6 */
#define B7 16 /* 2400, bb7 */
#define B8 17 /* 2600, bb8 */
#define NUMTONES 18
/* values returned by detect
* 0-9 DTMF 0 through 9 or MF 0-9
* 10-11 DTMF *, #
* 12-15 DTMF A,B,C,D
* 16-20 MF last column: C11, C12, KP1, KP2, ST
* 21 2400
* 22 2600
* 23 2400 + 2600
* 24 DIALTONE
* 25 RING
* 26 BUSY
* 27 silence
* -1 invalid
*/
#define D0 0
#define D1 1
#define D2 2
#define D3 3
#define D4 4
#define D5 5
#define D6 6
#define D7 7
#define D8 8
#define D9 9
#define DSTAR 10
#define DPND 11
#define DA 12
#define DB 13
#define DC 14
#define DD 15
#define DC11 16
#define DC12 17
#define DKP1 18
#define DKP2 19
#define DST 20
#define D24 21
#define D26 22
#define D2426 23
#define DDT 24
#define DRING 25
#define DBUSY 26
#define DSIL 27
#define RANGE 0.1 /* any thing higher than RANGE*peak is "on" */
#define THRESH 100.0 /* minimum level for the loudest tone */
#define FLUSH_TIME 100 /* 100 frames = 3 seconds */
#endif /* _INCLUDE_DTMF_DETECT_H_ */

+ 148
- 0
dtmf_gen.c View File

@ -0,0 +1,148 @@
/* -------- local defines (if we had more.. seperate file) ----- */
#define FSAMPLE 8000 /* sampling rate, 8KHz */
/*
* FLOAT_TO_SAMPLE converts a float in the range -1.0 to 1.0
* into a format valid to be written out in a sound file
* or to a sound device
*/
#ifdef SIGNED
# define FLOAT_TO_SAMPLE(x) ((char)((x) * 127.0))
#else
# define FLOAT_TO_SAMPLE(x) ((char)((x + 1.0) * 127.0))
#endif
typedef char sample;
/* --------------------------------------------------------------- */
/*
* take the sine of x, where x is 0 to 65535 (for 0 to 360 degrees)
*/
float mysine(in)
short in;
{
static float coef[] = {
3.140625, 0.02026367, -5.325196, 0.5446778, 1.800293 };
float x,y,res;
int sign,i;
if(in < 0) { /* force positive */
sign = -1;
in = -in;
} else
sign = 1;
if(in >= 0x4000) /* 90 degrees */
in = 0x8000 - in; /* 180 degrees - in */
x = in * (1/32768.0);
y = x; /* y holds x^i) */
res = 0;
for(i=0; i<5; i++) {
res += y * coef[i];
y *= x;
}
return(res * sign);
}
/*
* play tone1 and tone2 (in Hz)
* for 'length' milliseconds
* outputs samples to sound_out
*/
void two_tones(sound_out,tone1,tone2,length)
int sound_out;
unsigned int tone1,tone2,length;
{
#define BLEN 128
sample cout[BLEN] __attribute((unused));
float out;
unsigned int ad1,ad2;
short c1,c2;
int i,l,x;
ad1 = (tone1 << 16) / FSAMPLE;
ad2 = (tone2 << 16) / FSAMPLE;
l = (length * FSAMPLE) / 1000;
x = 0;
for( c1=0, c2=0, i=0 ;
i < l;
i++, c1+= ad1, c2+= ad2 ) {
out = (mysine(c1) + mysine(c2)) * 0.5;
cout[x++] = FLOAT_TO_SAMPLE(out);
if (x==BLEN) {
// write(sound_out, cout, x * sizeof(sample));
x=0;
}
}
// write(sound_out, cout, x);
}
/*
* silence on 'sound_out'
* for length milliseconds
*/
void silence(sound_out,length)
int sound_out;
unsigned int length;
{
int l,i,x;
static sample c0 = FLOAT_TO_SAMPLE(0.0);
sample cout[BLEN] __attribute((unused));
x = 0;
l = (length * FSAMPLE) / 1000;
for(i=0; i < l; i++) {
cout[x++] = c0;
if (x==BLEN) {
// write(sound_out, cout, x * sizeof(sample));
x=0;
}
}
// write(sound_out, cout, x);
}
/*
* play a single dtmf tone
* for a length of time,
* input is 0-9 for digit, 10 for * 11 for #
*/
void dtmf(sound_fd, digit, length)
int sound_fd;
int digit, length;
{
/* Freqs for 0-9, *, # */
static int row[] = {
941, 697, 697, 697, 770, 770, 770, 852, 852, 852, 941, 941 };
static int col[] = {
1336, 1209, 1336, 1477, 1209, 1336, 1477, 1209, 1336, 1447,
1209, 1477 };
two_tones(sound_fd, row[digit], col[digit], length);
}
/*
* take a string and output as dtmf
* valid characters, 0-9, *, #
* all others play as 50ms silence
*/
void dial(sound_fd, number)
int sound_fd;
char *number;
{
int i,x;
char c;
for(i=0;number[i];i++) {
c = number[i];
x = -1;
if(c >= '0' && c <= '9')
x = c - '0';
else if(c == '*')
x = 10;
else if(c == '#')
x = 11;
if(x >= 0)
dtmf(sound_fd, x, 50);
silence(sound_fd,50);
}
}

+ 109
- 0
eth_ar.c View File

@ -0,0 +1,109 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2015
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "eth_ar.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
/*
8 character callsign, 4 bit ssid
|call(42-40) ssid(3-0) lm|
|call(39-32) |
|call(31-24) |
|call(23-16) |
|call(15-8) |
|call(7-0) |
*/
static char alnum2code[37] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
0
};
int eth_ar_call2mac(uint8_t mac[6], char *callsign, int ssid, bool multicast)
{
uint64_t add = 0;
int i;
if (ssid > 15 || ssid < 0)
return -1;
for (i = 7; i >= 0; i--) {
char c;
if (i >= strlen(callsign)) {
c = 0;
} else {
c = toupper(callsign[i]);
}
int j;
for (j = 0; j < sizeof(alnum2code); j++) {
if (alnum2code[j] == c)
break;
}
if (j == sizeof(alnum2code))
return -1;
add *= 37;
add += j;
}
mac[0] = ((add >> (40 - 6)) & 0xc0) | (ssid << 2) | 0x02 | multicast;
mac[1] = (add >> 32) & 0xff;
mac[2] = (add >> 24) & 0xff;
mac[3] = (add >> 16) & 0xff;
mac[4] = (add >> 8) & 0xff;
mac[5] = add & 0xff;
return 0;
}
int eth_ar_mac2call(char *callsign, int *ssid, bool *multicast, uint8_t mac[6])
{
uint64_t add;
int i;
if (!memcmp(mac, (uint8_t[6]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 6)) {
*ssid = 0;
strcpy(callsign, "*");
return 0;
}
*multicast = mac[0] & 0x01;
*ssid = (mac[0] & 0x3c) >> 2;
add = (uint64_t)(mac[0] & 0xc0) << (40 - 6);
add |= (uint64_t)mac[1] << 32;
add |= (uint64_t)mac[2] << 24;
add |= (uint64_t)mac[3] << 16;
add |= (uint64_t)mac[4] << 8;
add |= (uint64_t)mac[5];
for (i = 0; i < 8; i++) {
int c = add % 37;
callsign[i] = alnum2code[c];
add /= 37;
}
callsign[i] = 0;
return 0;
}

+ 41
- 0
eth_ar.h View File

@ -0,0 +1,41 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2015
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _INCLUDE_ETH_AR_H_
#define _INCLUDE_ETH_AR_H_
#include <stdint.h>
#include <stdbool.h>
#define ETH_P_CODEC2_3200 0x7300
#define ETH_P_CODEC2_2400 0x7301
#define ETH_P_CODEC2_1600 0x7302
#define ETH_P_CODEC2_1400 0x7303
#define ETH_P_CODEC2_1300 0x7304
#define ETH_P_CODEC2_1200 0x7305
#define ETH_P_CODEC2_700 0x7306
#define ETH_P_CODEC2_700B 0x7307
#define ETH_AR_CALL_LEN_MAX 8
#define ETH_AR_CALL_SIZE 9
int eth_ar_call2mac(uint8_t mac[6], char *callsign, int ssid, bool multicast);
int eth_ar_mac2call(char *callsign, int *ssid, bool *multicast, uint8_t mac[6]);
#endif /* _INCLUDE_ETH_AR_H_ */

+ 119
- 0
interface.c View File

@ -0,0 +1,119 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "interface.h"
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_tun.h>
static int fd;
static uint16_t eth_mac[6];
int interface_rx(uint8_t *data, size_t len, uint16_t eth_type)
{
uint8_t packet[len + 14];
memset(packet, 0xff, 6);
memcpy(packet + 6, eth_mac, 6);
packet[12] = eth_type >> 8;
packet[13] = eth_type & 0xff;
memcpy(packet + 14, data, len);
// printf("Packet to interface %zd\n", sizeof(packet));
write(fd, packet, sizeof(packet));
return 0;
}
int interface_tx(int (*cb)(uint8_t *data, size_t len, uint16_t eth_type))
{
uint8_t data[2048];
size_t len;
len = read(fd, data, 2048);
if (len > 14) {
// int i;
uint16_t eth_type = (data[12] << 8) | data[13];
if ((data[12] == (eth_type >> 8)) &&
(data[13] == (eth_type & 0xff))) {
printf("ETH packet of %zd bytes\n", len);
return cb(data + 14, len - 14, eth_type);
}
}
return 0;
}
static int tap_alloc(char *dev, uint8_t mac[6])
{
struct ifreq ifr = { };
int fd;
char *tundev = "/dev/net/tun";
/* open the tun device */
if((fd = open(tundev, O_RDWR)) < 0 ) {
return -1;
}
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (*dev) {
/* if a device name was specified, put it in the structure; otherwise,
* the kernel will try to allocate the "next" device of the
* specified type */
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
/* try to create the device */
if(ioctl(fd, TUNSETIFF, &ifr) < 0 ) {
printf("Creating tap device failed\n");
close(fd);
return -1;
}
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);
if (ioctl(fd, SIOCSIFHWADDR, &ifr) < 0) {
printf("Setting HWADDR failed\n");
close(fd);
return -1;
}
return fd;
}
int interface_init(char *name, uint8_t mac[6])
{
if (name == NULL)
name = "freedv";
fd = tap_alloc(name, mac);
memcpy(eth_mac, mac, 6);
return fd;
}

+ 28
- 0
interface.h View File

@ -0,0 +1,28 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _INCLUDE_INTERFACE_H_
#define _INCLUDE_INTERFACE_H_
#include <stdlib.h>
#include <stdint.h>
int interface_rx(uint8_t *data, size_t len, uint16_t eth_type);
int interface_tx(int (*cb)(uint8_t *data, size_t len, uint16_t eth_type));
int interface_init(char *name, uint8_t mac[6]);
#endif /* _INCLUDE_INTERFACE_H_ */

+ 182
- 0
sound.c View File

@ -0,0 +1,182 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sound.h"
#include <endian.h>
#include <alsa/asoundlib.h>
/* Our device handle */
static snd_pcm_t *pcm_handle_tx = NULL;
static snd_pcm_t *pcm_handle_rx = NULL;
static void (*sound_in_cb)(int16_t *samples, int nr);
int sound_out(int16_t *samples, int nr)
{
int r;
r = snd_pcm_writei (pcm_handle_tx, samples, nr);
// printf("alsa: %d\n", r);
if (r < 0) {
printf("recover output\n");
snd_pcm_recover(pcm_handle_tx, r, 1);
snd_pcm_writei (pcm_handle_tx, samples, nr);
}
return 0;
}
int sound_poll_count_tx(void)
{
return snd_pcm_poll_descriptors_count(pcm_handle_tx);
}
int sound_poll_fill_tx(struct pollfd *fds, int count)
{
if (snd_pcm_poll_descriptors(pcm_handle_tx, fds, count) >= 0)
return 0;
return -1;
}
bool sound_poll_out_tx(struct pollfd *fds, int count)
{
unsigned short revents;
snd_pcm_poll_descriptors_revents(pcm_handle_tx, fds, count, &revents);
if (revents & POLLOUT)
return true;
else
return false;
}
int sound_poll_count_rx(void)
{
return snd_pcm_poll_descriptors_count(pcm_handle_rx);
}
int sound_poll_fill_rx(struct pollfd *fds, int count)
{
if (snd_pcm_poll_descriptors(pcm_handle_rx, fds, count) >= 0)
return 0;
return -1;
}
bool sound_poll_in_rx(struct pollfd *fds, int count)
{
unsigned short revents;
snd_pcm_poll_descriptors_revents(pcm_handle_rx, fds, count, &revents);
if (revents & POLLIN)
return true;
else
return false;
}
static int nr;
int sound_rx(void)
{
int r;
int16_t samples[nr];
r = snd_pcm_readi(pcm_handle_rx, samples, nr);
if (r > 0) {
sound_in_cb(samples, r);
} else {
printf("recover input (nr=%d, r=%d)\n", nr, r);
snd_pcm_recover(pcm_handle_rx, r, 0);
snd_pcm_start(pcm_handle_rx);
}
return 0;
}
int sound_param(snd_pcm_t *pcm_handle)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_hw_params_malloc (&hw_params);
snd_pcm_hw_params_any(pcm_handle, hw_params);
snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (htole16(0x1234) == 0x1234)
snd_pcm_hw_params_set_format (pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE);
else
snd_pcm_hw_params_set_format (pcm_handle, hw_params, SND_PCM_FORMAT_S16_BE);
unsigned int rrate = 8000;
snd_pcm_hw_params_set_rate_near (pcm_handle, hw_params, &rrate, NULL);
snd_pcm_hw_params_set_channels (pcm_handle, hw_params, 1);
snd_pcm_uframes_t buffer_size = nr * 10 * 100;
snd_pcm_uframes_t period_size = nr * 10;
snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size);
snd_pcm_hw_params_set_period_size_near (pcm_handle, hw_params, &period_size, NULL);
snd_pcm_hw_params (pcm_handle, hw_params);
snd_pcm_hw_params_free (hw_params);
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_malloc (&sw_params);
snd_pcm_sw_params_current (pcm_handle, sw_params);
snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, buffer_size - period_size);
snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period_size);
snd_pcm_sw_params(pcm_handle, sw_params);
return 0;
}
int sound_init(char *device, void (*in_cb)(int16_t *samples, int nr), int inr)
{
int err;
/* The device name */
const char *device_name;
if (device)
device_name = device;
else
device_name = "default";
sound_in_cb = in_cb;
nr = inr;
/* Open the device */
err = snd_pcm_open (&pcm_handle_tx, device_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0)
return -1;
sound_param(pcm_handle_tx);
err = snd_pcm_open (&pcm_handle_rx, device_name, SND_PCM_STREAM_CAPTURE, 0);
if (err < 0)
return -1;
sound_param(pcm_handle_rx);
snd_pcm_prepare(pcm_handle_tx);
snd_pcm_start(pcm_handle_rx);
return 0;
}

+ 35
- 0
sound.h View File

@ -0,0 +1,35 @@
/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2016
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _INCLUDE_SOUND_H_
#define _INCLUDE_SOUND_H_
#include <stdint.h>
#include <poll.h>
#include <stdbool.h>
int sound_out(int16_t *samples, int nr);
int sound_init(char *device, void (*in_cb)(int16_t *samples, int nr), int nr);
int sound_poll_count_tx(void);
int sound_poll_fill_tx(struct pollfd *fds, int count);
bool sound_poll_out_tx(struct pollfd *fds, int count);
int sound_poll_count_rx(void);
int sound_poll_fill_rx(struct pollfd *fds, int count);
bool sound_poll_in_rx(struct pollfd *fds, int count);
int sound_rx(void);
#endif /* _INCLUDE_SOUND_H_ */

Loading…
Cancel
Save