Add SID 16420 #6
|
@ -62,8 +62,8 @@ src/nmt/nmt
|
|||
src/amps/libusatone.a
|
||||
src/amps/libamps.a
|
||||
src/amps/amps
|
||||
src/tacs/tacs
|
||||
src/jtacs/jtacs
|
||||
src/amps/tacs
|
||||
src/amps/jtacs
|
||||
src/r2000/radiocom2000
|
||||
src/imts/imts
|
||||
src/imts/imts-dialer
|
||||
|
@ -71,6 +71,7 @@ src/mpt1327/mpt1327
|
|||
src/jolly/jollycom
|
||||
src/eurosignal/eurosignal
|
||||
src/pocsag/pocsag
|
||||
src/golay/golay
|
||||
src/fuenf/5-ton-folge
|
||||
src/tv/osmotv
|
||||
src/radio/osmoradio
|
||||
|
|
13
README
13
README
|
@ -24,10 +24,13 @@ Additionally the following communication services are implemented:
|
|||
|
||||
* TV Transmitter with test Images
|
||||
* Radio transmitter / receiver
|
||||
* Analog Modem Emulation (AM7911)
|
||||
* Analog Modem Emulation 'Datenklo' (AM7911)
|
||||
* German classic 'Zeitansage' (talking clock)
|
||||
* POCSAG transmitter / receiver
|
||||
* DCF77 time signal transmitter and receiver
|
||||
* Golay/GSC transmitter / receiver
|
||||
* DCF77 time signal transmitter and receiver with weather info
|
||||
* C-Netz SIM emulator
|
||||
* C-Netz magnetic card emulator
|
||||
|
||||
USE AT YOUR OWN RISK!
|
||||
|
||||
|
@ -66,10 +69,10 @@ providing memory cards to be programmed for older C-Netz phone.
|
|||
|
||||
Dieter Spaar providing TACS recordings to verify and debug TACS support.
|
||||
|
||||
Hans Wigger providing Radiocom 2000 recordings, to reverse-enigeer the standard,
|
||||
which seems not to exist anymore...
|
||||
Hans Wigger providing Radiocom 2000 recordings, to reverse-enigineer the
|
||||
standard, which seems not to exist anymore...
|
||||
|
||||
Peter, Peter and Friedhelm and Stephan for providing documentation and hardware
|
||||
Peter, Peter, Friedhelm and Stephan for providing documentation and hardware
|
||||
for C-Netz Base Station and other C-Netz documents.
|
||||
|
||||
Carsten Wollesen for donating MPT1327 radios and programming tools.
|
||||
|
|
|
@ -98,14 +98,13 @@ AC_OUTPUT(
|
|||
src/cnetz/Makefile
|
||||
src/nmt/Makefile
|
||||
src/amps/Makefile
|
||||
src/tacs/Makefile
|
||||
src/jtacs/Makefile
|
||||
src/r2000/Makefile
|
||||
src/imts/Makefile
|
||||
src/mpt1327/Makefile
|
||||
src/jolly/Makefile
|
||||
src/eurosignal/Makefile
|
||||
src/pocsag/Makefile
|
||||
src/golay/Makefile
|
||||
src/fuenf/Makefile
|
||||
src/tv/Makefile
|
||||
src/radio/Makefile
|
||||
|
|
|
@ -129,8 +129,10 @@ Additional features:
|
|||
<li><a href="magnetic.html">C-Netz Magnetic Card</a></li>
|
||||
<li>Zeitansage (German talking clock)</li>
|
||||
<li>C-Netz FuVSt (MSC to control a real base station)</li>
|
||||
<li>POCSAG</li>
|
||||
<li>POCSAG (paging system)</li>
|
||||
<li>Golay / GSC (paging system)</li>
|
||||
<li>5-Ton-Ruf (firefighter's pagers and siren control)</li>
|
||||
<li>DCF77 The German longwave time signal transmitter/receiver</li>
|
||||
</ul>
|
||||
</td></tr></table></center>
|
||||
|
||||
|
|
|
@ -48,14 +48,13 @@ SUBDIRS += \
|
|||
cnetz \
|
||||
nmt \
|
||||
amps \
|
||||
tacs \
|
||||
jtacs \
|
||||
r2000 \
|
||||
imts \
|
||||
mpt1327 \
|
||||
jolly \
|
||||
eurosignal \
|
||||
pocsag \
|
||||
golay \
|
||||
fuenf \
|
||||
tv \
|
||||
radio \
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
amps
|
||||
amps tacs jtacs
|
||||
|
||||
noinst_LIBRARIES = libamps.a libusatone.a
|
||||
|
||||
libamps_a_SOURCES = \
|
||||
amps_tacs_main.c \
|
||||
main_common.c \
|
||||
amps.c \
|
||||
transaction.c \
|
||||
frame.c \
|
||||
|
@ -14,16 +14,16 @@ libamps_a_SOURCES = \
|
|||
sysinfo.c
|
||||
|
||||
libusatone_a_SOURCES = \
|
||||
tones.c \
|
||||
noanswer.c \
|
||||
outoforder.c \
|
||||
invalidnumber.c \
|
||||
congestion.c
|
||||
usa_tones.c \
|
||||
usa_noanswer.c \
|
||||
usa_outoforder.c \
|
||||
usa_invalidnumber.c \
|
||||
usa_congestion.c
|
||||
|
||||
amps_SOURCES = \
|
||||
stations.c \
|
||||
image.c \
|
||||
main.c
|
||||
amps_stations.c \
|
||||
amps_image.c \
|
||||
amps_main.c
|
||||
|
||||
amps_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
|
@ -63,3 +63,90 @@ amps_LDADD += \
|
|||
$(SOAPY_LIBS)
|
||||
endif
|
||||
|
||||
tacs_SOURCES = \
|
||||
tacs_tones.c \
|
||||
tacs_outoforder.c \
|
||||
tacs_stations.c \
|
||||
tacs_image.c \
|
||||
tacs_main.c
|
||||
|
||||
tacs_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
libamps.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libosmocc/libosmocc.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libcompandor/libcompandor.a \
|
||||
$(top_builddir)/src/libgoertzel/libgoertzel.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
$(top_builddir)/src/libg711/libg711.a \
|
||||
$(top_builddir)/src/libaaimage/libaaimage.a \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA
|
||||
tacs_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
tacs_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libam/libam.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif
|
||||
|
||||
jtacs_SOURCES = \
|
||||
jtacs_tones.c \
|
||||
jtacs_stations.c \
|
||||
jtacs_image.c \
|
||||
jtacs_main.c
|
||||
|
||||
jtacs_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
libamps.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libosmocc/libosmocc.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libcompandor/libcompandor.a \
|
||||
$(top_builddir)/src/libgoertzel/libgoertzel.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
$(top_builddir)/src/libg711/libg711.a \
|
||||
$(top_builddir)/src/libaaimage/libaaimage.a \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA
|
||||
jtacs_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
jtacs_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libam/libam.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif
|
||||
|
||||
|
|
180
src/amps/amps.c
180
src/amps/amps.c
|
@ -61,7 +61,9 @@
|
|||
#define PAGE_TRIES 2 /* how many times to page the phone */
|
||||
#define PAGE_TO1 8.0 /* max time to wait for paging reply */
|
||||
#define PAGE_TO2 4.0 /* max time to wait for last paging reply */
|
||||
#define ALERT_TO 60.0 /* max time to wait for answer */
|
||||
#define ALERT_TRIES 3 /* how many times to alert the phone */
|
||||
#define ALERT_TO 0.6 /* max time to wait for alert confirm */
|
||||
#define ANSWER_TO 60.0 /* max time to wait for answer */
|
||||
#define RELEASE_TIMER 5.0 /* max time to send release messages */
|
||||
|
||||
/* Convert channel number to frequency number of base station.
|
||||
|
@ -103,7 +105,7 @@ double amps_channel2freq(int channel, int uplink)
|
|||
/* JTACS */
|
||||
/* see "ARIB_STD-T64-C.S0057-0v1.0.pdf" */
|
||||
if (uplink == 2)
|
||||
return -55.000 * 1e6;
|
||||
return 55.000 * 1e6;
|
||||
|
||||
/* 799 channels */
|
||||
if (channel >= 1 && channel <= 799)
|
||||
|
@ -128,13 +130,19 @@ double amps_channel2freq(int channel, int uplink)
|
|||
enum amps_chan_type amps_channel2type(int channel)
|
||||
{
|
||||
if (!tacs) {
|
||||
/* AMPS */
|
||||
if (channel >= 313 && channel <= 354)
|
||||
return CHAN_TYPE_CC;
|
||||
} else {
|
||||
} else if (!jtacs) {
|
||||
/* TACS */
|
||||
if (channel >= 23 && channel <= 43)
|
||||
return CHAN_TYPE_CC;
|
||||
if (channel >= 323 && channel <= 343)
|
||||
return CHAN_TYPE_CC;
|
||||
} else {
|
||||
/* JTACS */
|
||||
if (channel >= 418 && channel <= 456)
|
||||
return CHAN_TYPE_CC;
|
||||
}
|
||||
|
||||
return CHAN_TYPE_VC;
|
||||
|
@ -143,6 +151,7 @@ enum amps_chan_type amps_channel2type(int channel)
|
|||
const char *amps_channel2band(int channel)
|
||||
{
|
||||
if (!tacs) {
|
||||
/* AMPS */
|
||||
if (channel >= 991 && channel <= 1023)
|
||||
return "A''";
|
||||
if (channel >= 1 && channel <= 333)
|
||||
|
@ -153,11 +162,15 @@ const char *amps_channel2band(int channel)
|
|||
return "A'";
|
||||
if (channel >= 717 && channel <= 799)
|
||||
return "B'";
|
||||
} else {
|
||||
} else if (!jtacs) {
|
||||
/* TACS */
|
||||
if (channel >= 1 && channel <= 300)
|
||||
return "A";
|
||||
if (channel >= 301 && channel <= 600)
|
||||
return "B";
|
||||
} else {
|
||||
/* JTACS */
|
||||
return "A";
|
||||
}
|
||||
|
||||
return "<invalid>";
|
||||
|
@ -211,12 +224,12 @@ void amps_number2min(const char *number, uint32_t *min1, uint16_t *min2)
|
|||
}
|
||||
|
||||
if (!tacs) {
|
||||
/* MIN1 */
|
||||
/* MIN1 (amps) */
|
||||
*min1 = ((uint32_t)(digit2binary(number[0]) * 100 + digit2binary(number[1]) * 10 + digit2binary(number[2]) - 111)) << 14;
|
||||
*min1 |= digit2binary(number[3]) << 10;
|
||||
*min1 |= digit2binary(number[4]) * 100 + digit2binary(number[5]) * 10 + digit2binary(number[6]) - 111;
|
||||
} else {
|
||||
/* MIN1 */
|
||||
/* MIN1 (tacs/jtacs) */
|
||||
*min1 = digit2binary(number[0]) << 20;
|
||||
*min1 |= (digit2binary(number[1]) * 100 + digit2binary(number[2]) * 10 + digit2binary(number[3]) - 111) << 10;
|
||||
*min1 |= digit2binary(number[4]) * 100 + digit2binary(number[5]) * 10 + digit2binary(number[6]) - 111;
|
||||
|
@ -227,6 +240,8 @@ void amps_number2min(const char *number, uint32_t *min1, uint16_t *min2)
|
|||
*/
|
||||
/* TACS: convert MIN1 and MIN2 to AREA-XXXXXXX
|
||||
*/
|
||||
/* JTACS: convert MIN1 and MIN2 to NET-XXXXXXX (NET = mobile network code, always 440)
|
||||
*/
|
||||
const char *amps_min22number(uint16_t min2)
|
||||
{
|
||||
static char number[4];
|
||||
|
@ -249,7 +264,7 @@ const char *amps_min12number(uint32_t min1)
|
|||
static char number[8];
|
||||
|
||||
if (!tacs) {
|
||||
/* MIN1 */
|
||||
/* MIN1 (amps) */
|
||||
if ((min1 >> 14) > 999)
|
||||
strcpy(number, "???");
|
||||
else {
|
||||
|
@ -269,7 +284,7 @@ const char *amps_min12number(uint32_t min1)
|
|||
number[6] = binary2digit(((min1 & 0x3ff) % 10) + 1);
|
||||
}
|
||||
} else {
|
||||
/* MIN1 */
|
||||
/* MIN1 (tacs/jtacs) */
|
||||
if ((min1 >> 20) < 1 || (min1 >> 20) > 10)
|
||||
number[0] = '?';
|
||||
else
|
||||
|
@ -502,7 +517,7 @@ static amps_t *search_pc(void)
|
|||
}
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, amps_si *si, uint16_t sid, uint8_t sat, int polarity, int tolerant, int loopback)
|
||||
int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, amps_si *si, uint16_t sid, uint8_t sat, int polarity, int send_callerid, int tolerant, int loopback)
|
||||
{
|
||||
sender_t *sender;
|
||||
amps_t *amps;
|
||||
|
@ -513,6 +528,8 @@ int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *de
|
|||
/* check for channel number */
|
||||
if (amps_channel2freq(atoi(kanal), 0) == 0) {
|
||||
PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %s invalid.\n", kanal);
|
||||
if (jtacs)
|
||||
PDEBUG(DAMPS, DEBUG_ERROR, "Try an even channel number, like 440.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -542,6 +559,11 @@ int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *de
|
|||
PDEBUG(DAMPS, DEBUG_ERROR, "Channel number %s belongs to a voice channel, but your channel type '%s' requires to be on a control channel number. Please use correct channel.\n", kanal, chan_type_long_name(chan_type));
|
||||
return -EINVAL;
|
||||
}
|
||||
/* only even channels */
|
||||
if (jtacs && chan_type != CHAN_TYPE_VC && (atoi(kanal) & 1)) {
|
||||
PDEBUG(DAMPS, DEBUG_ERROR, "Control channel on JTACS system seem not to work with odd channel numbers. Please use even channel number.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* check if sid machtes channel band */
|
||||
band = amps_channel2band(atoi(kanal));
|
||||
|
@ -582,12 +604,12 @@ int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *de
|
|||
amps->chan_type = chan_type;
|
||||
memcpy(&s->si, si, sizeof(amps->si));
|
||||
amps->sat = sat;
|
||||
|
||||
amps->send_callerid = send_callerid;
|
||||
if (polarity < 0)
|
||||
amps->flip_polarity = 1;
|
||||
|
||||
amps->pre_emphasis = pre_emphasis;
|
||||
amps->de_emphasis = de_emphasis;
|
||||
|
||||
/* the AMPS uses a frequency rage of 300..3000 Hz, but we still use the default low pass filter, which is not too far above */
|
||||
rc = init_emphasis(&s->estate, samplerate, CUT_OFF_EMPHASIS_DEFAULT, CUT_OFF_HIGHPASS_DEFAULT, CUT_OFF_LOWPASS_DEFAULT);
|
||||
if (rc < 0)
|
||||
|
@ -688,14 +710,13 @@ static void amps_release(transaction_t *trans, uint8_t cause)
|
|||
trans->callref = 0;
|
||||
}
|
||||
/* change DSP mode to transmit release */
|
||||
if (amps->dsp_mode == DSP_MODE_AUDIO_RX_AUDIO_TX || amps->dsp_mode == DSP_MODE_OFF)
|
||||
if (amps->dsp_mode == DSP_MODE_AUDIO_RX_AUDIO_TX || amps->dsp_mode == DSP_MODE_AUDIO_RX_SILENCE_TX || amps->dsp_mode == DSP_MODE_OFF)
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_FRAME_TX, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* receive signaling
|
||||
*/
|
||||
|
||||
void amps_rx_signaling_tone(amps_t *amps, int tone, double quality)
|
||||
{
|
||||
transaction_t *trans = amps->trans_list;
|
||||
|
@ -710,6 +731,7 @@ void amps_rx_signaling_tone(amps_t *amps, int tone, double quality)
|
|||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Lost Signaling Tone signal\n");
|
||||
|
||||
switch (trans->state) {
|
||||
case TRANS_CALL_MO_ASSIGN_CONFIRM: // should not happen
|
||||
case TRANS_CALL:
|
||||
if (!tone)
|
||||
break;
|
||||
|
@ -723,16 +745,19 @@ void amps_rx_signaling_tone(amps_t *amps, int tone, double quality)
|
|||
destroy_transaction(trans);
|
||||
amps_go_idle(amps);
|
||||
break;
|
||||
case TRANS_CALL_MT_ALERT:
|
||||
case TRANS_CALL_MT_ASSIGN_CONFIRM: // should not happen
|
||||
case TRANS_CALL_MT_ALERT: // should not happen
|
||||
case TRANS_CALL_MT_ALERT_SEND: // should not happen
|
||||
case TRANS_CALL_MT_ALERT_CONFIRM:
|
||||
if (tone) {
|
||||
timer_stop(&trans->timer);
|
||||
call_up_alerting(trans->callref);
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_AUDIO_TX, 0);
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT_SEND);
|
||||
timer_start(&trans->timer, ALERT_TO);
|
||||
trans_new_state(trans, TRANS_CALL_MT_ANSWER_WAIT);
|
||||
timer_start(&trans->timer, ANSWER_TO);
|
||||
}
|
||||
break;
|
||||
case TRANS_CALL_MT_ALERT_SEND:
|
||||
case TRANS_CALL_MT_ANSWER_WAIT:
|
||||
if (!tone) {
|
||||
timer_stop(&trans->timer);
|
||||
if (!trans->sat_detected)
|
||||
|
@ -753,13 +778,20 @@ void amps_rx_sat(amps_t *amps, int tone, double quality)
|
|||
PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT signal without transaction, please fix!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* irgnoring SAT loss on release */
|
||||
if (trans->state == TRANS_CALL_RELEASE
|
||||
|| trans->state == TRANS_CALL_RELEASE_SEND)
|
||||
return;
|
||||
if (trans->state != TRANS_CALL
|
||||
|
||||
/* only SAT with these states */
|
||||
if (trans->state != TRANS_CALL_MO_ASSIGN_CONFIRM
|
||||
&& trans->state != TRANS_CALL_MT_ASSIGN_CONFIRM
|
||||
&& trans->state != TRANS_CALL_MT_ALERT
|
||||
&& trans->state != TRANS_CALL_MT_ALERT_SEND) {
|
||||
&& trans->state != TRANS_CALL_MT_ALERT_SEND
|
||||
&& trans->state != TRANS_CALL_MT_ALERT_CONFIRM
|
||||
&& trans->state != TRANS_CALL_MT_ANSWER_WAIT
|
||||
&& trans->state != TRANS_CALL) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT signal without active call, please fix!\n");
|
||||
return;
|
||||
}
|
||||
|
@ -772,10 +804,20 @@ void amps_rx_sat(amps_t *amps, int tone, double quality)
|
|||
trans->sat_detected = 0;
|
||||
}
|
||||
|
||||
/* no SAT during alerting */
|
||||
if (trans->state == TRANS_CALL_MT_ALERT
|
||||
|| trans->state == TRANS_CALL_MT_ALERT_SEND)
|
||||
return;
|
||||
/* initial SAT received */
|
||||
if (tone && trans->state == TRANS_CALL_MO_ASSIGN_CONFIRM) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Confirm from mobile (SAT) received\n");
|
||||
timer_stop(&trans->timer);
|
||||
trans_new_state(trans, TRANS_CALL);
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_AUDIO_TX, 0);
|
||||
}
|
||||
if (tone && trans->state == TRANS_CALL_MT_ASSIGN_CONFIRM) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Confirm from mobile (SAT) received\n");
|
||||
timer_stop(&trans->timer);
|
||||
trans->alert_retry = 1;
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT);
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_FRAME_TX, 0);
|
||||
}
|
||||
|
||||
if (tone) {
|
||||
timer_stop(&trans->timer);
|
||||
|
@ -790,20 +832,6 @@ void amps_rx_sat(amps_t *amps, int tone, double quality)
|
|||
return;
|
||||
}
|
||||
|
||||
static void timeout_sat(amps_t *amps, double duration)
|
||||
{
|
||||
if (!amps->trans_list) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_ERROR, "SAT timeout, but no transaction, please fix!\n");
|
||||
return;
|
||||
}
|
||||
if (duration == SAT_TO1)
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds not receiving SAT signal.\n", duration);
|
||||
else
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds loosing SAT signal.\n", duration);
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call towards network.\n");
|
||||
amps_release(amps->trans_list, CAUSE_TEMPFAIL);
|
||||
}
|
||||
|
||||
/* receive message from phone on RECC */
|
||||
void amps_rx_recc(amps_t *amps, uint8_t scm, uint8_t mpci, uint32_t esn, uint32_t min1, uint16_t min2, uint8_t msg_type, uint8_t ordq, uint8_t order, const char *dialing)
|
||||
{
|
||||
|
@ -950,6 +978,11 @@ int call_down_setup(int callref, const char __attribute__((unused)) *caller_id,
|
|||
}
|
||||
trans->callref = callref;
|
||||
trans->page_retry = 1;
|
||||
if (caller_type == TYPE_INTERNATIONAL) {
|
||||
trans->caller_id[0] = '+';
|
||||
strncpy(trans->caller_id + 1, caller_id, sizeof(trans->caller_id) - 2);
|
||||
} else
|
||||
strncpy(trans->caller_id, caller_id, sizeof(trans->caller_id) - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -988,8 +1021,11 @@ void call_down_disconnect(int callref, int cause)
|
|||
switch (amps->dsp_mode) {
|
||||
case DSP_MODE_AUDIO_RX_AUDIO_TX:
|
||||
case DSP_MODE_AUDIO_RX_FRAME_TX:
|
||||
if (trans->state == TRANS_CALL_MT_ALERT
|
||||
|| trans->state == TRANS_CALL_MT_ALERT_SEND) {
|
||||
if (trans->state == TRANS_CALL_MT_ASSIGN_CONFIRM
|
||||
|| trans->state == TRANS_CALL_MT_ALERT
|
||||
|| trans->state == TRANS_CALL_MT_ALERT_SEND
|
||||
|| trans->state == TRANS_CALL_MT_ALERT_CONFIRM
|
||||
|| trans->state == TRANS_CALL_MT_ANSWER_WAIT) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control disconnect on voice channel while alerting, releasing towards mobile station.\n");
|
||||
amps_release(trans, cause);
|
||||
}
|
||||
|
@ -1028,6 +1064,7 @@ void call_down_release(int callref, int cause)
|
|||
trans->callref = 0;
|
||||
|
||||
switch (amps->dsp_mode) {
|
||||
case DSP_MODE_AUDIO_RX_SILENCE_TX:
|
||||
case DSP_MODE_AUDIO_RX_AUDIO_TX:
|
||||
case DSP_MODE_AUDIO_RX_FRAME_TX:
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Call control releases on voice channel, releasing towards mobile station.\n");
|
||||
|
@ -1041,7 +1078,7 @@ void call_down_release(int callref, int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
amps_t *amps;
|
||||
|
@ -1055,10 +1092,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
return;
|
||||
|
||||
if (amps->dsp_mode == DSP_MODE_AUDIO_RX_AUDIO_TX) {
|
||||
sample_t up[(int)((double)count * amps->sender.srstate.factor + 0.5) + 10];
|
||||
compress_audio(&s->cstate, samples, count);
|
||||
count = samplerate_upsample(&s->sender.srstate, samples, count, up);
|
||||
jitter_save(&s->sender.dejitter, up, count);
|
||||
jitter_save(&s->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1071,8 +1106,16 @@ void transaction_timeout(struct timer *timer)
|
|||
amps_t *amps = trans->amps;
|
||||
|
||||
switch (trans->state) {
|
||||
case TRANS_CALL_MO_ASSIGN_CONFIRM:
|
||||
case TRANS_CALL_MT_ASSIGN_CONFIRM:
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds not receiving initial SAT signal.\n", timer->duration);
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call towards network.\n");
|
||||
amps_release(amps->trans_list, CAUSE_TEMPFAIL);
|
||||
break;
|
||||
case TRANS_CALL:
|
||||
timeout_sat(amps, timer->duration);
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Timeout after %.0f seconds loosing SAT signal.\n", timer->duration);
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call towards network.\n");
|
||||
amps_release(amps->trans_list, CAUSE_TEMPFAIL);
|
||||
break;
|
||||
case TRANS_CALL_RELEASE:
|
||||
case TRANS_CALL_RELEASE_SEND:
|
||||
|
@ -1080,10 +1123,18 @@ void transaction_timeout(struct timer *timer)
|
|||
destroy_transaction(trans);
|
||||
amps_go_idle(amps);
|
||||
break;
|
||||
case TRANS_CALL_MT_ALERT:
|
||||
amps_release(trans, CAUSE_TEMPFAIL);
|
||||
break;
|
||||
case TRANS_CALL_MT_ALERT_SEND:
|
||||
case TRANS_CALL_MT_ALERT_CONFIRM:
|
||||
if (trans->alert_retry++ == ALERT_TRIES) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Phone does not respond to alert order, destroying transaction\n");
|
||||
amps_release(trans, CAUSE_TEMPFAIL);
|
||||
} else {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Phone does not respond to alert order, retrying\n");
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT);
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_FRAME_TX, 0);
|
||||
}
|
||||
break;
|
||||
case TRANS_CALL_MT_ANSWER_WAIT:
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_NOTICE, "Alerting timeout, destroying transaction\n");
|
||||
amps_release(trans, CAUSE_NOANSWER);
|
||||
break;
|
||||
|
@ -1173,8 +1224,9 @@ again:
|
|||
vc = assign_voice_channel(trans);
|
||||
if (vc) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assignment complete, voice connected\n");
|
||||
trans_new_state(trans, TRANS_CALL);
|
||||
amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_AUDIO_TX, 0);
|
||||
/* timer and other things are processed at assign_voice_channel() */
|
||||
trans_new_state(trans, TRANS_CALL_MO_ASSIGN_CONFIRM);
|
||||
amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_SILENCE_TX, 0);
|
||||
}
|
||||
return NULL;
|
||||
case TRANS_CALL_MT_ASSIGN:
|
||||
|
@ -1184,13 +1236,10 @@ again:
|
|||
case TRANS_CALL_MT_ASSIGN_SEND:
|
||||
vc = assign_voice_channel(trans);
|
||||
if (vc) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assignment complete, next: sending alerting on VC\n");
|
||||
trans->chan = 0;
|
||||
trans->msg_type = 0;
|
||||
trans->ordq = 0;
|
||||
trans->order = 1;
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT);
|
||||
amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_FRAME_TX, 0);
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Assignment complete, waiting for SAT on VC\n");
|
||||
/* timer and other things are processed at assign_voice_channel() */
|
||||
trans_new_state(trans, TRANS_CALL_MT_ASSIGN_CONFIRM);
|
||||
amps_set_dsp_mode(vc, DSP_MODE_AUDIO_RX_SILENCE_TX, 0);
|
||||
}
|
||||
return NULL;
|
||||
case TRANS_PAGE:
|
||||
|
@ -1223,8 +1272,25 @@ transaction_t *amps_tx_frame_fvc(amps_t *amps)
|
|||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Release call was sent, continue sending release\n");
|
||||
return trans;
|
||||
case TRANS_CALL_MT_ALERT:
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Sending alerting\n");
|
||||
trans->chan = 0;
|
||||
trans->msg_type = 0;
|
||||
trans->ordq = 0;
|
||||
// "Alert with caller ID" causes older phones to interrupt the connection for some reason, therefore we don't use order 17 when no caller ID is set
|
||||
if (amps->send_callerid && trans->alert_retry == 1 && trans->caller_id[0]) {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Sending alerting with caller ID\n");
|
||||
trans->order = 17;
|
||||
} else {
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Sending alerting\n");
|
||||
trans->order = 1;
|
||||
}
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT_SEND);
|
||||
return trans;
|
||||
case TRANS_CALL_MT_ALERT_SEND:
|
||||
PDEBUG_CHAN(DAMPS, DEBUG_INFO, "Alerting was sent, continue waiting for ST or timeout\n");
|
||||
timer_start(&trans->timer, ALERT_TO);
|
||||
amps_set_dsp_mode(amps, DSP_MODE_AUDIO_RX_SILENCE_TX, 0);
|
||||
trans_new_state(trans, TRANS_CALL_MT_ALERT_CONFIRM);
|
||||
return NULL;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ enum dsp_mode {
|
|||
DSP_MODE_OFF, /* channel not active (VC) */
|
||||
DSP_MODE_AUDIO_RX_AUDIO_TX, /* stream audio */
|
||||
DSP_MODE_AUDIO_RX_FRAME_TX, /* stream audio, send frames */
|
||||
DSP_MODE_AUDIO_RX_SILENCE_TX, /* stream audio, send silence */
|
||||
DSP_MODE_FRAME_RX_FRAME_TX, /* send and decode frames */
|
||||
};
|
||||
|
||||
|
@ -56,6 +57,7 @@ struct amps {
|
|||
|
||||
/* system info */
|
||||
amps_si si;
|
||||
int send_callerid; /* if set, caller ID is transmitted */
|
||||
|
||||
/* cell nr selection */
|
||||
int cell_auto; /* if set, cell_nr is selected automatically */
|
||||
|
@ -140,6 +142,12 @@ struct amps {
|
|||
uint8_t tx_fvc_msg_type; /* message (3 values) */
|
||||
uint8_t tx_fvc_ordq;
|
||||
uint8_t tx_fvc_order;
|
||||
char tx_fvc_callerid[34]; /* caller ID */
|
||||
int tx_fvc_callerid_present;/* presentation of caller ID */
|
||||
int tx_fvc_callerid_screen; /* screening of caller ID */
|
||||
int tx_fvc_callerid_signal; /* signal to send in conjunction with caller ID */
|
||||
int tx_fvc_word_count; /* counts transmitted words in a muli word message */
|
||||
int tx_fvc_word_repeat; /* counts repeats of mulit word message */
|
||||
/* SAT tone */
|
||||
int sat; /* use SAT tone 0..2 */
|
||||
int sat_samples; /* number of samples in buffer for supervisory detection */
|
||||
|
@ -175,7 +183,7 @@ const char *amps_min12number(uint32_t min1);
|
|||
void amps_number2min(const char *number, uint32_t *min1, uint16_t *min2);
|
||||
const char *amps_min2number(uint32_t min1, uint16_t min2);
|
||||
const char *amps_scm(uint8_t scm);
|
||||
int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, amps_si *si, uint16_t sid, uint8_t sat, int polarity, int tolerant, int loopback);
|
||||
int amps_create(const char *kanal, enum amps_chan_type chan_type, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, amps_si *si, uint16_t sid, uint8_t sat, int polarity, int send_callerid, int tolerant, int loopback);
|
||||
void amps_destroy(sender_t *sender);
|
||||
void amps_go_idle(amps_t *amps);
|
||||
void amps_rx_signaling_tone(amps_t *amps, int tone, double quality);
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
#include "outoforder.h"
|
||||
#include "invalidnumber.h"
|
||||
#include "congestion.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
|
||||
const int tacs = 0;
|
||||
const int jtacs = 0;
|
||||
|
||||
const struct number_lengths number_lengths[] = {
|
||||
{ 10, "AMPS number (NPA-XXX-XXXX)" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const char *number_prefixes[] = {
|
||||
"1xxxxxxxxxx",
|
||||
"+1xxxxxxxxxx",
|
|
@ -679,6 +679,7 @@ static struct amps_stations {
|
|||
{ 16413, 0, "Saskatoon", "SK", "Cantel, Inc.", "403 266 1300", "GTEDS" },
|
||||
{ 16415, 0, "St. Catharines", "ON", "Cantel, Inc.", "416 440 1300", "GTEDS" },
|
||||
{ 16419, 0, "Sudbury", "ON", "Cantel, Inc.", "416 440 1300", "GTEDS" },
|
||||
{ 16420, 0, "Toronto", "ON", "Cantel, Inc.", "416", ":GTEDS" },
|
||||
{ 16423, 0, "Newmarket", "ON", "Cantel, Inc.", "416 440 1300", "GTEDS" },
|
||||
{ 16423, 0, "Toronto", "ON", "Cantel, Inc.", "416", ":GTEDS" },
|
||||
{ 16425, 0, "Abbotsford", "BC", "Cantel, Inc.", "604 687 1440", "GTEDS" },
|
|
@ -125,7 +125,7 @@
|
|||
#define SIG_LOST_COUNT 4 /* number of measures to loose Signaling Tone */
|
||||
#define CUT_OFF_HIGHPASS 300.0 /* cut off frequency for high pass filter to remove dc level from sound card / sample */
|
||||
#define BEST_QUALITY 0.68 /* Best possible RX quality */
|
||||
#define COMFORT_NOISE 0.02 /* audio level of comfort noise (relative to ISDN level) */
|
||||
#define COMFORT_NOISE 0.02 /* audio level of comfort noise (relative to speech level) */
|
||||
|
||||
static sample_t ramp_up[256], ramp_down[256];
|
||||
|
||||
|
@ -253,6 +253,7 @@ int dsp_init_sender(amps_t *amps, int tolerant)
|
|||
PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Sat detection interval is %d ms.\n", amps->sat_samples * 1000 / amps->sender.samplerate);
|
||||
amps->sat_filter_spl = spl;
|
||||
|
||||
/* count SAT tones */
|
||||
|
@ -399,8 +400,8 @@ again:
|
|||
rc = amps_encode_frame_fvc(amps, amps->fsk_tx_frame);
|
||||
else
|
||||
rc = amps_encode_frame_focc(amps, amps->fsk_tx_frame);
|
||||
/* check if we have not bit string (change to tx audio)
|
||||
* we may not store fsk_tx_buffer_pos, because is was reset on a mode achange */
|
||||
/* check if we have no bit string (change to tx audio / silence)
|
||||
* we may not store fsk_tx_buffer_pos, because is was reset on a mode change */
|
||||
if (rc)
|
||||
return count;
|
||||
amps->fsk_tx_frame_pos = 0;
|
||||
|
@ -473,7 +474,7 @@ static void sat_encode(amps_t *amps, sample_t *samples, int length)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
amps_t *amps = (amps_t *) sender;
|
||||
int count;
|
||||
int count, input_num;
|
||||
|
||||
again:
|
||||
switch (amps->dsp_mode) {
|
||||
|
@ -483,11 +484,19 @@ again:
|
|||
break;
|
||||
case DSP_MODE_AUDIO_RX_AUDIO_TX:
|
||||
memset(power, 1, length);
|
||||
jitter_load(&s->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
/* pre-emphasis */
|
||||
if (amps->pre_emphasis)
|
||||
pre_emphasis(&s->estate, samples, length);
|
||||
/* encode sat */
|
||||
/* encode SAT during call */
|
||||
sat_encode(amps, samples, length);
|
||||
break;
|
||||
case DSP_MODE_AUDIO_RX_SILENCE_TX:
|
||||
memset(power, 1, length);
|
||||
memset(samples, 0, sizeof(*samples) * length);
|
||||
/* encode SAT while waiting for alert response or answer */
|
||||
sat_encode(amps, samples, length);
|
||||
break;
|
||||
case DSP_MODE_AUDIO_RX_FRAME_TX:
|
||||
|
@ -496,6 +505,7 @@ again:
|
|||
* stopped, process again for rest of stream. */
|
||||
count = fsk_frame(amps, samples, length);
|
||||
memset(power, 1, count);
|
||||
// no SAT during frame transmission, according to specs
|
||||
samples += count;
|
||||
power += count;
|
||||
length -= count;
|
||||
|
@ -831,7 +841,7 @@ static void sat_decode(amps_t *amps, sample_t *samples, int length)
|
|||
static void sender_receive_audio(amps_t *amps, sample_t *samples, int length)
|
||||
{
|
||||
transaction_t *trans = amps->trans_list;
|
||||
sample_t *spl;
|
||||
sample_t *spl, s;
|
||||
int max, pos;
|
||||
int i;
|
||||
|
||||
|
@ -840,7 +850,10 @@ static void sender_receive_audio(amps_t *amps, sample_t *samples, int length)
|
|||
spl = amps->sat_filter_spl;
|
||||
pos = amps->sat_filter_pos;
|
||||
for (i = 0; i < length; i++) {
|
||||
/* unmute: use buffer, to delay audio, so we do not miss that chunk when SAT is detected */
|
||||
s = spl[pos];
|
||||
spl[pos++] = samples[i];
|
||||
samples[i] = s;
|
||||
if (pos == max) {
|
||||
pos = 0;
|
||||
sat_decode(amps, spl, max);
|
||||
|
@ -894,6 +907,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double __at
|
|||
break;
|
||||
case DSP_MODE_AUDIO_RX_AUDIO_TX:
|
||||
case DSP_MODE_AUDIO_RX_FRAME_TX:
|
||||
case DSP_MODE_AUDIO_RX_SILENCE_TX:
|
||||
sender_receive_audio(amps, samples, length);
|
||||
break;
|
||||
}
|
||||
|
@ -923,13 +937,13 @@ void amps_set_dsp_mode(amps_t *amps, enum dsp_mode mode, int frame_length)
|
|||
amps->tx_focc_debugged = 0;
|
||||
}
|
||||
if (amps->dsp_mode == DSP_MODE_FRAME_RX_FRAME_TX
|
||||
&& (mode == DSP_MODE_AUDIO_RX_AUDIO_TX || mode == DSP_MODE_AUDIO_RX_FRAME_TX)) {
|
||||
&& (mode == DSP_MODE_AUDIO_RX_AUDIO_TX || mode == DSP_MODE_AUDIO_RX_FRAME_TX || mode == DSP_MODE_AUDIO_RX_SILENCE_TX)) {
|
||||
/* reset SAT detection */
|
||||
sat_reset(amps, "Change from FOCC to FVC");
|
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Change mode from FOCC to FVC\n");
|
||||
}
|
||||
if (amps->dsp_mode == DSP_MODE_OFF
|
||||
&& (mode == DSP_MODE_AUDIO_RX_AUDIO_TX || mode == DSP_MODE_AUDIO_RX_FRAME_TX)) {
|
||||
&& (mode == DSP_MODE_AUDIO_RX_AUDIO_TX || mode == DSP_MODE_AUDIO_RX_FRAME_TX || mode == DSP_MODE_AUDIO_RX_SILENCE_TX)) {
|
||||
/* reset SAT detection */
|
||||
sat_reset(amps, "Enable FVC");
|
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Change mode from OFF to FVC\n");
|
||||
|
@ -940,6 +954,8 @@ void amps_set_dsp_mode(amps_t *amps, enum dsp_mode mode, int frame_length)
|
|||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Change mode from FVC to OFF\n");
|
||||
}
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Reset FSK frame transmitter, due to setting dsp mode.\n");
|
||||
|
||||
amps->dsp_mode = mode;
|
||||
if (frame_length)
|
||||
amps->fsk_rx_frame_length = frame_length;
|
||||
|
|
|
@ -3020,6 +3020,37 @@ static uint64_t amps_encode_mobile_station_control_message_word1_b(uint8_t scc,
|
|||
return amps_encode_word(&frame, &mobile_station_control_message_word1_b, 1);
|
||||
}
|
||||
|
||||
static uint64_t amps_encode_word2_first_alert_with_info_word(uint8_t rl_w, uint8_t signal, uint8_t cpn_rl, uint8_t pi, uint8_t si)
|
||||
{
|
||||
frame_t frame;
|
||||
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
frame.ie[AMPS_IE_T1T2] = 1;
|
||||
frame.ie[AMPS_IE_RL_W] = rl_w;
|
||||
frame.ie[AMPS_IE_SIGNAL] = signal;
|
||||
frame.ie[AMPS_IE_CPN_RL] = cpn_rl;
|
||||
frame.ie[AMPS_IE_PI] = pi;
|
||||
frame.ie[AMPS_IE_SI] = si;
|
||||
return amps_encode_word(&frame, &word2_first_alert_with_info_word, 1);
|
||||
}
|
||||
|
||||
static uint64_t amps_encode_wordn_n_minus_1th_alert_with_info_word(const char *character)
|
||||
{
|
||||
frame_t frame;
|
||||
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
frame.ie[AMPS_IE_T1T2] = 1;
|
||||
if (character[0]) {
|
||||
frame.ie[AMPS_IE_CHARACTER_1] = character[0];
|
||||
if (character[1]) {
|
||||
frame.ie[AMPS_IE_CHARACTER_2] = character[1];
|
||||
if (character[2])
|
||||
frame.ie[AMPS_IE_CHARACTER_3] = character[2];
|
||||
}
|
||||
}
|
||||
return amps_encode_word(&frame, &wordn_n_minus_1th_alert_with_info_word, 1);
|
||||
}
|
||||
|
||||
/* decoder function of a word */
|
||||
static frame_t *amps_decode_word(uint64_t word, struct def_word *w)
|
||||
{
|
||||
|
@ -3346,7 +3377,7 @@ static void amps_encode_focc_bits(uint64_t word_a, uint64_t word_b, char *bits)
|
|||
|
||||
memcpy(bits + 0, dotting, 10);
|
||||
bits[10] = 'i';
|
||||
strcpy(bits + 11, sync_word);
|
||||
memcpy(bits + 11, sync_word, 11);
|
||||
bits[22] = 'i';
|
||||
k = 23;
|
||||
for (i = 0; i < 5; i++) {
|
||||
|
@ -3366,22 +3397,22 @@ static void amps_encode_focc_bits(uint64_t word_a, uint64_t word_b, char *bits)
|
|||
|
||||
if (k != 463)
|
||||
abort();
|
||||
bits[463] = '\0';
|
||||
bits[k] = '\0';
|
||||
|
||||
#ifdef BIT_DEBUGGING
|
||||
if (debuglevel == DEBUG_DEBUG) {
|
||||
char text[64];
|
||||
|
||||
strncpy(text, bits, 23);
|
||||
text[23] = '\0';
|
||||
#ifdef BIT_DEBUGGING
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "TX FOCC: %s\n", text);
|
||||
for (i = 0; i < 10; i++) {
|
||||
strncpy(text, bits + 23 + i * 44, 44);
|
||||
text[44] = '\0';
|
||||
PDEBUG(DFRAME, DEBUG_DEBUG, " word %c - %s\n", (i & 1) ? 'b' : 'a', text);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void amps_encode_fvc_bits(uint64_t word_a, char *bits)
|
||||
|
@ -3398,15 +3429,15 @@ static void amps_encode_fvc_bits(uint64_t word_a, char *bits)
|
|||
memcpy(bits + k, dotting, 37);
|
||||
k += 37;
|
||||
}
|
||||
strcpy(bits + k, sync_word);
|
||||
memcpy(bits + k, sync_word, 11);
|
||||
k += 11;
|
||||
for (j = 39; j >= 0; j--)
|
||||
bits[k++] = ((word_a >> j) & 1) + '0';
|
||||
}
|
||||
|
||||
if (k != 1032)
|
||||
abort();
|
||||
|
||||
bits[1032] = '\0';
|
||||
bits[k] = '\0';
|
||||
|
||||
#ifdef BIT_DEBUGGING
|
||||
if (debuglevel == DEBUG_DEBUG) {
|
||||
|
@ -3502,7 +3533,16 @@ int amps_encode_frame_fvc(amps_t *amps, char *bits)
|
|||
amps->tx_fvc_ordq = trans->ordq;
|
||||
amps->tx_fvc_order = trans->order;
|
||||
amps->tx_fvc_chan = trans->chan;
|
||||
strncpy(amps->tx_fvc_callerid, trans->caller_id, sizeof(amps->tx_fvc_callerid) - 1);
|
||||
amps->tx_fvc_callerid_signal = 1;
|
||||
amps->tx_fvc_callerid_screen = 3;
|
||||
if (trans->caller_id[0])
|
||||
amps->tx_fvc_callerid_present = 0;
|
||||
else
|
||||
amps->tx_fvc_callerid_signal = 1;
|
||||
amps->tx_fvc_send = 1;
|
||||
amps->tx_fvc_word_count = 0;
|
||||
amps->tx_fvc_word_repeat = 0;
|
||||
}
|
||||
/* on change of dsp mode */
|
||||
if (amps->dsp_mode != DSP_MODE_AUDIO_RX_FRAME_TX)
|
||||
|
@ -3511,11 +3551,32 @@ int amps_encode_frame_fvc(amps_t *amps, char *bits)
|
|||
|
||||
/* send scheduled mobile control message */
|
||||
if (amps->tx_fvc_send) {
|
||||
amps->tx_fvc_send = 0;
|
||||
if (amps->tx_fvc_chan)
|
||||
word = amps_encode_mobile_station_control_message_word1_b(amps->sat, amps->sat, (amps->si.word2.dtx) ? 1 : 0, 0, 0, amps->si.vmac, amps->tx_fvc_chan);
|
||||
else
|
||||
word = amps_encode_mobile_station_control_message_word1_a(amps->sat, amps->tx_fvc_msg_type, amps->tx_fvc_ordq, amps->tx_fvc_order);
|
||||
if (amps->tx_fvc_word_count == 0) {
|
||||
if (amps->tx_fvc_chan)
|
||||
word = amps_encode_mobile_station_control_message_word1_b(amps->sat, amps->sat, (amps->si.word2.dtx) ? 1 : 0, 0, 0, amps->si.vmac, amps->tx_fvc_chan);
|
||||
else
|
||||
word = amps_encode_mobile_station_control_message_word1_a(amps->sat, amps->tx_fvc_msg_type, amps->tx_fvc_ordq, amps->tx_fvc_order);
|
||||
/* done, if we don't have ALERTING with info */
|
||||
if (amps->tx_fvc_order != 17)
|
||||
amps->tx_fvc_send = 0;
|
||||
} else if (amps->tx_fvc_word_count == 1) {
|
||||
int cpn_rl, rl_w;
|
||||
/* number of characters */
|
||||
cpn_rl = strlen(amps->tx_fvc_callerid);
|
||||
/* number of frames that are required to hold number of characters */
|
||||
rl_w = (cpn_rl + 2) / 3;
|
||||
word = amps_encode_word2_first_alert_with_info_word(rl_w, amps->tx_fvc_callerid_signal, cpn_rl, amps->tx_fvc_callerid_present, amps->tx_fvc_callerid_screen);
|
||||
if (cpn_rl == 0)
|
||||
amps->tx_fvc_send = 0;
|
||||
} else {
|
||||
const char *callerid;
|
||||
/* chunk of caller ID */
|
||||
callerid = amps->tx_fvc_callerid + (amps->tx_fvc_word_count - 2) * 3;
|
||||
word = amps_encode_wordn_n_minus_1th_alert_with_info_word(callerid);
|
||||
if (strlen(callerid) <= 3)
|
||||
amps->tx_fvc_send = 0;
|
||||
}
|
||||
amps->tx_fvc_word_count++;
|
||||
} else
|
||||
return 1;
|
||||
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
#include "../amps/main.h"
|
||||
#include "../amps/tones.h"
|
||||
#include "../amps/outoforder.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
|
||||
const int tacs = 1;
|
||||
const int jtacs = 1;
|
||||
|
||||
const struct number_lengths number_lengths[] = {
|
||||
{ 10, "JTACS number (440-XXXXXXX)" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const char *number_prefixes[] = { NULL };
|
||||
|
||||
int main(int argc, char *argv[])
|
|
@ -38,7 +38,10 @@
|
|||
int num_chan_type = 0;
|
||||
enum amps_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_PC_VC };
|
||||
const char *flip_polarity = "";
|
||||
int ms_power = 4, dtx = 0, dcc = 0, scc = 0, sid = 0, regh = 1, regr = 1, pureg = 0, pdreg = 0, locaid = -1, regincr = 300, bis = 0;
|
||||
int ms_power = 4;
|
||||
int dtx = 0;
|
||||
int send_callerid = 0;
|
||||
int dcc = 0, scc = 0, sid = 0, regh = 1, regr = 1, pureg = 0, pdreg = 0, locaid = -1, regincr = 300, bis = 0;
|
||||
int tolerant = 0;
|
||||
|
||||
void print_help(const char *arg0)
|
||||
|
@ -57,12 +60,24 @@ void print_help(const char *arg0)
|
|||
printf(" If the phone shows 'NoSrv', try the other way.\n");
|
||||
printf(" -P --ms-power <power level>\n");
|
||||
printf(" Give power level of the mobile station 0..7. (default = '%d')\n", ms_power);
|
||||
printf(" 0 = %2d W; 1 = 1.6 W; 2 = 630 mW; 3 = 250 mW;\n", (tacs) ? 10 : 4);
|
||||
printf(" 4 = 100 mW; 5 = 40 mW; 6 = 16 mW; 7 = 6.3 mW\n");
|
||||
if (!tacs) {
|
||||
printf(" 0 = 4 W; 1 = 1.6 W; 2 = 630 mW; 3 = 250 mW;\n");
|
||||
printf(" 4 = 100 mW; 5 = 40 mW; 6 = 16 mW; 7 = 6.3 mW\n");
|
||||
} else {
|
||||
/* tacs, not jtacs: https://www.academia.edu/8265916/Total_Access_Communication_System?email_work_card=view-paper */
|
||||
printf(" 0 = 2.28 W; 1 = 1.12 W; 2 = 447 mW; 3 = 178 mW;\n");
|
||||
printf(" 4 = 70.8 mW; 5 = 28.2 mW; 6 = 11.2 mW; 7 = 4.5 mW\n");
|
||||
}
|
||||
printf(" -D --dtx <parameter>\n");
|
||||
printf(" Give DTX parameter for Discontinuous Transmission. (default = '%d')\n", dtx);
|
||||
printf(" 0 = disable DTX; 1 = reserved;\n");
|
||||
printf(" 2 = 8 dB attenuation in low state; 3 = transmitter off\n");
|
||||
printf(" -I --caller-id 1 | 0\n");
|
||||
printf(" If set, the caller ID is sent while ringing the phone. (default = '%d')\n", send_callerid);
|
||||
printf(" Note that this does not work as documented in the specs. If the phone\n");
|
||||
printf(" does not support caller ID, it will abort connection on receiving\n");
|
||||
printf(" caller ID for some unknown reason. Therefore use caller ID only with\n");
|
||||
printf(" phones that support it.\n");
|
||||
if (!tacs) {
|
||||
printf(" -S --sysinfo sid=<System ID> | sid=list\n");
|
||||
printf(" Give system ID of cell broadcast\n");
|
||||
|
@ -110,6 +125,7 @@ static void add_options(void)
|
|||
option_add('F', "flip-polarity", 1);
|
||||
option_add('P', "ms-power", 1);
|
||||
option_add('D', "dtx", 1);
|
||||
option_add('I', "caller-id", 1);
|
||||
option_add('S', "sysinfo", 1);
|
||||
option_add('O', "tolerant", 0);
|
||||
}
|
||||
|
@ -156,6 +172,9 @@ static int handle_options(int short_option, int argi, char **argv)
|
|||
if (dtx < 0)
|
||||
dtx = 0;
|
||||
break;
|
||||
case 'I':
|
||||
send_callerid = atoi(argv[argi]);
|
||||
break;
|
||||
case 'S':
|
||||
p = strchr(argv[argi], '=');
|
||||
if (!p) {
|
||||
|
@ -226,10 +245,7 @@ static int handle_options(int short_option, int argi, char **argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static const struct number_lengths number_lengths[] = {
|
||||
{ 10, "AMPS number" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
extern const struct number_lengths number_lengths[];
|
||||
|
||||
int main_amps_tacs(const char *name, int argc, char *argv[])
|
||||
{
|
||||
|
@ -238,6 +254,10 @@ int main_amps_tacs(const char *name, int argc, char *argv[])
|
|||
int polarity;
|
||||
int i;
|
||||
|
||||
/* jtacs has only system A, so there are only odd AIDs */
|
||||
if (jtacs)
|
||||
sid = 1;
|
||||
|
||||
/* override default */
|
||||
dsp_samplerate = 96000;
|
||||
|
||||
|
@ -267,7 +287,7 @@ int main_amps_tacs(const char *name, int argc, char *argv[])
|
|||
}
|
||||
|
||||
if (!num_kanal) {
|
||||
printf("No channel (\"Kanal\") is specified, I suggest channel %d.\n\n", (!tacs) ? 333 : 323);
|
||||
printf("No channel (\"Kanal\") is specified, I suggest channel %d.\n\n", (!tacs) ? 333 : ((!jtacs) ? 323 : 418));
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
@ -384,7 +404,7 @@ int main_amps_tacs(const char *name, int argc, char *argv[])
|
|||
amps_si si;
|
||||
|
||||
init_sysinfo(&si, ms_power, ms_power, dtx, dcc, sid >> 1, regh, regr, pureg, pdreg, locaid, regincr, bis);
|
||||
rc = amps_create(kanal[i], chan_type[i], dsp_device[i], use_sdr, dsp_samplerate, rx_gain, tx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, &si, sid, scc, polarity, tolerant, loopback);
|
||||
rc = amps_create(kanal[i], chan_type[i], dsp_device[i], use_sdr, dsp_samplerate, rx_gain, tx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, &si, sid, scc, polarity, send_callerid, tolerant, loopback);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||
goto fail;
|
|
@ -2,10 +2,16 @@
|
|||
#include "../amps/main.h"
|
||||
#include "../amps/tones.h"
|
||||
#include "../amps/outoforder.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
|
||||
const int tacs = 1;
|
||||
const int jtacs = 0;
|
||||
|
||||
const struct number_lengths number_lengths[] = {
|
||||
{ 10, "TACS number (AREA-XXXXXXX)" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const char *number_prefixes[] = {
|
||||
"0xxxxxxxxxx",
|
||||
"+44xxxxxxxxxx",
|
|
@ -37,17 +37,25 @@ static const char *trans_state_name(int state)
|
|||
case TRANS_REGISTER_ACK_SEND:
|
||||
return "REGISTER ACK SEND";
|
||||
case TRANS_CALL_MO_ASSIGN:
|
||||
return "CALL ASSIGN MOBILE ORIGINATING";
|
||||
return "MO CALL ASSIGNMENT";
|
||||
case TRANS_CALL_MO_ASSIGN_SEND:
|
||||
return "CALL ASSIGN MOBILE ORIGINATING SEND";
|
||||
return "MO CALL ASSIGNMENT SENDING";
|
||||
case TRANS_CALL_MO_ASSIGN_CONFIRM:
|
||||
return "MO CALL ASSIGNMENT WAIT CONFIRM";
|
||||
case TRANS_CALL_MT_ASSIGN:
|
||||
return "CALL ASSIGN MOBILE TERMINATING";
|
||||
return "MT CALL ASSIGNMENT";
|
||||
case TRANS_CALL_MT_ASSIGN_SEND:
|
||||
return "CALL ASSIGN MOBILE TERMINATING SEND";
|
||||
return "MT CALL ASSIGNMENT SENDING";
|
||||
case TRANS_CALL_MT_ASSIGN_CONFIRM:
|
||||
return "MT CALL ASSIGNMENT WAIT CONFIRM";
|
||||
case TRANS_CALL_MT_ALERT:
|
||||
return "CALL ALERT MOBILE TERMINATING";
|
||||
return "MT CALL ALERT";
|
||||
case TRANS_CALL_MT_ALERT_SEND:
|
||||
return "CALL ALERT MOBILE TERMINATING SEND";
|
||||
return "MT CALL ALERT SENDING";
|
||||
case TRANS_CALL_MT_ALERT_CONFIRM:
|
||||
return "MT CALL ALERT WAIT CONFIRM";
|
||||
case TRANS_CALL_MT_ANSWER_WAIT:
|
||||
return "MT CALL ANSWER WAIT";
|
||||
case TRANS_CALL_REJECT:
|
||||
return "CALL REJECT";
|
||||
case TRANS_CALL_REJECT_SEND:
|
||||
|
@ -79,11 +87,15 @@ const char *trans_short_state_name(int state)
|
|||
return "REGISTER";
|
||||
case TRANS_CALL_MO_ASSIGN:
|
||||
case TRANS_CALL_MO_ASSIGN_SEND:
|
||||
case TRANS_CALL_MO_ASSIGN_CONFIRM:
|
||||
case TRANS_CALL_MT_ASSIGN:
|
||||
case TRANS_CALL_MT_ASSIGN_SEND:
|
||||
case TRANS_CALL_MT_ASSIGN_CONFIRM:
|
||||
return "ASSIGN";
|
||||
case TRANS_CALL_MT_ALERT:
|
||||
case TRANS_CALL_MT_ALERT_SEND:
|
||||
case TRANS_CALL_MT_ALERT_CONFIRM:
|
||||
case TRANS_CALL_MT_ANSWER_WAIT:
|
||||
return "ALERT";
|
||||
case TRANS_CALL_REJECT:
|
||||
case TRANS_CALL_REJECT_SEND:
|
||||
|
|
|
@ -5,10 +5,14 @@ enum amps_trans_state {
|
|||
TRANS_REGISTER_ACK_SEND, /* attach request received, sending ack */
|
||||
TRANS_CALL_MO_ASSIGN, /* assigning channel, waiting to send */
|
||||
TRANS_CALL_MO_ASSIGN_SEND, /* assigning channel, sending assignment */
|
||||
TRANS_CALL_MO_ASSIGN_CONFIRM, /* assignment sent, waiting for confirm (SAT) */
|
||||
TRANS_CALL_MT_ASSIGN, /* assigning channel, waiting to send */
|
||||
TRANS_CALL_MT_ASSIGN_SEND, /* assigning channel, sending assignment */
|
||||
TRANS_CALL_MT_ALERT, /* ringing the phone, sending alert order until signaling tone is received */
|
||||
TRANS_CALL_MT_ALERT_SEND, /* ringing the phone, signaling tone is received */
|
||||
TRANS_CALL_MT_ASSIGN_CONFIRM, /* assignment sent, waiting for confirm (SAT) */
|
||||
TRANS_CALL_MT_ALERT, /* ringing the phone, waiting to send alert */
|
||||
TRANS_CALL_MT_ALERT_SEND, /* ringing the phone, sending alert */
|
||||
TRANS_CALL_MT_ALERT_CONFIRM, /* ringing the phone, signaling tone is received */
|
||||
TRANS_CALL_MT_ANSWER_WAIT, /* ringing the phone, waiting for the phone to answer */
|
||||
TRANS_CALL_REJECT, /* rejecting channel, waiting to send */
|
||||
TRANS_CALL_REJECT_SEND, /* rejecting channel, sending reject */
|
||||
TRANS_CALL, /* active call */
|
||||
|
@ -31,6 +35,8 @@ typedef struct transaction {
|
|||
uint8_t ordq;
|
||||
uint8_t order;
|
||||
uint16_t chan; /* channel to assign */
|
||||
int alert_retry; /* current number of alter order (re)try */
|
||||
char caller_id[33]; /* id of calling phone */
|
||||
char dialing[33]; /* number dialed by the phone */
|
||||
enum amps_trans_state state; /* state of transaction */
|
||||
struct timer timer; /* for varous timeouts */
|
||||
|
|
|
@ -513,7 +513,7 @@ void call_down_release(int callref, __attribute__((unused)) int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
anetz_t *anetz;
|
||||
|
@ -526,11 +526,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
if (!sender)
|
||||
return;
|
||||
|
||||
if (anetz->dsp_mode == DSP_MODE_AUDIO) {
|
||||
sample_t up[(int)((double)count * anetz->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&anetz->sender.srstate, samples, count, up);
|
||||
jitter_save(&anetz->sender.dejitter, up, count);
|
||||
}
|
||||
if (anetz->dsp_mode == DSP_MODE_AUDIO)
|
||||
jitter_save(&anetz->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
|
||||
void call_down_clock(void) {}
|
||||
|
|
|
@ -357,6 +357,7 @@ static void fsk_tone(anetz_t *anetz, sample_t *samples, int length)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
anetz_t *anetz = (anetz_t *) sender;
|
||||
int input_num;
|
||||
|
||||
memset(power, 1, length);
|
||||
|
||||
|
@ -365,7 +366,9 @@ void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length
|
|||
memset(samples, 0, length * sizeof(*samples));
|
||||
break;
|
||||
case DSP_MODE_AUDIO:
|
||||
jitter_load(&anetz->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
break;
|
||||
case DSP_MODE_TONE:
|
||||
fsk_tone(anetz, samples, length);
|
||||
|
|
|
@ -816,7 +816,7 @@ void call_down_release(int callref, int __attribute__((unused)) cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
@ -831,9 +831,7 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO
|
||||
|| bnetz->dsp_mode == DSP_MODE_AUDIO_METER) {
|
||||
sample_t up[(int)((double)count * bnetz->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&bnetz->sender.srstate, samples, count, up);
|
||||
jitter_save(&bnetz->sender.dejitter, up, count);
|
||||
jitter_save(&bnetz->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -361,7 +361,7 @@ static void metering_tone(bnetz_t *bnetz, sample_t *samples, int length)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
int count;
|
||||
int count, input_num;
|
||||
|
||||
memset(power, 1, length);
|
||||
|
||||
|
@ -372,7 +372,9 @@ again:
|
|||
break;
|
||||
case DSP_MODE_AUDIO:
|
||||
case DSP_MODE_AUDIO_METER:
|
||||
jitter_load(&bnetz->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO_METER)
|
||||
metering_tone(bnetz, samples, length);
|
||||
break;
|
||||
|
@ -418,7 +420,7 @@ void bnetz_set_dsp_mode(bnetz_t *bnetz, enum dsp_mode mode)
|
|||
/* reset telegramm */
|
||||
if (mode == DSP_MODE_TELEGRAMM && bnetz->dsp_mode != mode) {
|
||||
bnetz->tx_telegramm = 0;
|
||||
fsk_mod_tx_reset(&bnetz->fsk_mod);
|
||||
fsk_mod_reset(&bnetz->fsk_mod);
|
||||
}
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", bnetz_dsp_mode_name(bnetz->dsp_mode), bnetz_dsp_mode_name(mode));
|
||||
|
|
|
@ -576,7 +576,7 @@ static void cnetz_release(transaction_t *trans, uint8_t cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
cnetz_t *cnetz;
|
||||
|
@ -591,7 +591,7 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
|
||||
if (cnetz->dsp_mode == DSP_MODE_SPK_V) {
|
||||
/* store as is, since we convert rate when processing FSK frames */
|
||||
jitter_save(&cnetz->sender.dejitter, samples, count);
|
||||
jitter_save(&cnetz->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -158,12 +158,6 @@ int dsp_init_sender(cnetz_t *cnetz, int measure_speed, double clock_speed[2], en
|
|||
scrambler_setup(&cnetz->scrambler_tx, (double)cnetz->sender.samplerate / 1.1);
|
||||
scrambler_setup(&cnetz->scrambler_rx, (double)cnetz->sender.samplerate / 1.1);
|
||||
|
||||
/* reinit jitter buffer for 8000 kHz */
|
||||
jitter_destroy(&cnetz->sender.dejitter);
|
||||
rc = jitter_create(&cnetz->sender.dejitter, 8000 / 5);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
/* init compandor, according to C-Netz specs, attack and recovery time
|
||||
* shall not exceed according to ITU G.162 */
|
||||
init_compandor(&cnetz->cstate, 8000, 5.0, 22.5);
|
||||
|
@ -172,7 +166,7 @@ int dsp_init_sender(cnetz_t *cnetz, int measure_speed, double clock_speed[2], en
|
|||
cnetz->offset_range = ceil(cnetz->fsk_bitduration);
|
||||
|
||||
#ifdef TEST_SCRAMBLE
|
||||
rc = jitter_create(&scrambler_test_jb, cnetz->sender.samplerate / 5);
|
||||
rc = jitter_create(&scrambler_test_jb, "scramble", cnetz->sender.samplerate, sizeof(sample_t), JITTER_AUDIO);
|
||||
if (rc < 0) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Failed to init jitter buffer for scrambler test!\n");
|
||||
exit(0);
|
||||
|
@ -577,7 +571,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_l
|
|||
#ifdef TEST_UNSCRAMBLE
|
||||
scrambler(&scrambler_test_scrambler1, samples, length);
|
||||
#endif
|
||||
jitter_save(&scrambler_test_jb, samples, length);
|
||||
jitter_save(&scrambler_test_jb, samples, length, 0, 0, 0, 0);
|
||||
return;
|
||||
#endif
|
||||
|
||||
|
@ -598,7 +592,8 @@ static int shrink_speech(cnetz_t *cnetz, sample_t *speech_buffer)
|
|||
/* 1. compress dynamics */
|
||||
compress_audio(&cnetz->cstate, speech_buffer, 100);
|
||||
/* 2. upsample */
|
||||
speech_length = samplerate_upsample(&cnetz->sender.srstate, speech_buffer, 100, speech_buffer);
|
||||
speech_length = samplerate_upsample_output_num(&cnetz->sender.srstate, 100);
|
||||
samplerate_upsample(&cnetz->sender.srstate, speech_buffer, 100, speech_buffer, speech_length);
|
||||
/* 3. scramble */
|
||||
if (cnetz->scrambler)
|
||||
scrambler(&cnetz->scrambler_tx, speech_buffer, speech_length);
|
||||
|
|
|
@ -314,7 +314,7 @@ int send_sto(am791x_t *am791x, sample_t *sample, int length)
|
|||
|
||||
/* modulate STO */
|
||||
phaseshift = am791x->sto_phaseshift65536;
|
||||
while (count < length && fsk->tx_bitpos < 1.0) {
|
||||
while (count < length) {
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
phase += phaseshift;
|
||||
if (phase >= 65536.0)
|
||||
|
|
|
@ -1001,7 +1001,7 @@ static ssize_t dk_ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufs
|
|||
break;
|
||||
case TCOON:
|
||||
PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns on output.\n");
|
||||
datenklo->output_off = 1;
|
||||
datenklo->output_off = 0;
|
||||
break;
|
||||
case TCIOFF:
|
||||
PDEBUG(DDATENKLO, DEBUG_DEBUG, "Terminal turns off input.\n");
|
||||
|
|
|
@ -6,6 +6,9 @@ bin_PROGRAMS = \
|
|||
|
||||
dcf77_SOURCES = \
|
||||
dcf77.c \
|
||||
weather.c \
|
||||
cities.c \
|
||||
image.c \
|
||||
main.c
|
||||
dcf77_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
|
|
|
@ -0,0 +1,633 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "cities.h"
|
||||
|
||||
static struct city_list {
|
||||
int region;
|
||||
const char *name;
|
||||
} city_list[] = {
|
||||
{ 0, "AGEN" },
|
||||
{ 0, "AUCH" },
|
||||
{ 0, "BORDEAUX" },
|
||||
{ 0, "BRIVE LA GAILLARDE" },
|
||||
{ 0, "CAHORS" },
|
||||
{ 0, "MONTAUBAN" },
|
||||
{ 0, "MONT MARSAN" },
|
||||
{ 0, "PAU" },
|
||||
{ 0, "PERIGUEUX" },
|
||||
{ 0, "TARBES" },
|
||||
{ 0, "TOULOUSE" },
|
||||
{ 1, "ANGOULEME" },
|
||||
{ 1, "LA ROCHELL" },
|
||||
{ 1, "LA ROCHE S" },
|
||||
{ 1, "LIMOGES" },
|
||||
{ 1, "NIORT" },
|
||||
{ 1, "POITIERS" },
|
||||
{ 2, "ALENCON" },
|
||||
{ 2, "AUXERRE" },
|
||||
{ 2, "BAR LE DUC" },
|
||||
{ 2, "BLOIS" },
|
||||
{ 2, "BOBIGNY" },
|
||||
{ 2, "BOURGES" },
|
||||
{ 2, "CERGY PONT" },
|
||||
{ 2, "CHARTRES" },
|
||||
{ 2, "CRETEIL" },
|
||||
{ 2, "EVRY" },
|
||||
{ 2, "LE MANS" },
|
||||
{ 2, "MELUN" },
|
||||
{ 2, "NANTERRE" },
|
||||
{ 2, "NEVERS" },
|
||||
{ 2, "ORLEANS" },
|
||||
{ 2, "PARIS" },
|
||||
{ 2, "REIMS" },
|
||||
{ 2, "TOURS" },
|
||||
{ 2, "TROYES" },
|
||||
{ 2, "VERSAILLES" },
|
||||
{ 3, "ANGERS" },
|
||||
{ 3, "BREST" },
|
||||
{ 3, "CHERBOURG" },
|
||||
{ 3, "JERSEY" },
|
||||
{ 3, "LAVAL" },
|
||||
{ 3, "LORIENT" },
|
||||
{ 3, "NANTES" },
|
||||
{ 3, "RENNES" },
|
||||
{ 3, "ST BRIEUC" },
|
||||
{ 4, "AURILLAC" },
|
||||
{ 4, "CLERMON FERRAND" },
|
||||
{ 4, "FLORAC" },
|
||||
{ 4, "GUERET" },
|
||||
{ 4, "MENDE" },
|
||||
{ 4, "MILLAU" },
|
||||
{ 4, "MONTLUCON" },
|
||||
{ 4, "PUY VELAY" },
|
||||
{ 4, "RODEZ" },
|
||||
{ 4, "ST-ETIENNE" },
|
||||
{ 4, "ST FLOUR" },
|
||||
{ 5, "ALBI" },
|
||||
{ 5, "BEZIERS" },
|
||||
{ 5, "CARCASSONN" },
|
||||
{ 5, "FOIX" },
|
||||
{ 5, "MONTPELLIER" },
|
||||
{ 5, "PERPIGNAN" },
|
||||
{ 6, "AALST" },
|
||||
{ 6, "ANTWERPEN" },
|
||||
{ 6, "BOULOGNE" },
|
||||
{ 6, "BRUGGE" },
|
||||
{ 6, "BRUSSEL" },
|
||||
{ 6, "CHARLEROI" },
|
||||
{ 6, "GENK" },
|
||||
{ 6, "GENT" },
|
||||
{ 6, "HALLE" },
|
||||
{ 6, "HASSELT" },
|
||||
{ 6, "IXELLES" },
|
||||
{ 6, "KNOKKE-HEIST" },
|
||||
{ 6, "KORTRIJK" },
|
||||
{ 6, "LEUVEN" },
|
||||
{ 6, "LIEGE" },
|
||||
{ 6, "LILLE" },
|
||||
{ 6, "LOKEREN" },
|
||||
{ 6, "MAASTRICHT" },
|
||||
{ 6, "MECHELEN" },
|
||||
{ 6, "MIDDELBURG" },
|
||||
{ 6, "MONS" },
|
||||
{ 6, "MOUSCRON" },
|
||||
{ 6, "NAMUR" },
|
||||
{ 6, "OOSTENDE" },
|
||||
{ 6, "ROESELARE" },
|
||||
{ 6, "SCHAERBEEK" },
|
||||
{ 6, "TERNEUZEN" },
|
||||
{ 6, "TOURNAI" },
|
||||
{ 7, "CHAUMONT" },
|
||||
{ 7, "DIJON" },
|
||||
{ 7, "EPINAL" },
|
||||
{ 7, "LONS LE S" },
|
||||
{ 7, "METZ" },
|
||||
{ 7, "NANCY" },
|
||||
{ 7, "VESOUL" },
|
||||
{ 8, "ALES" },
|
||||
{ 8, "AVIGNON" },
|
||||
{ 8, "MARSEILLE" },
|
||||
{ 8, "MONTELIMAR" },
|
||||
{ 8, "NIMES" },
|
||||
{ 8, "PRIVAS" },
|
||||
{ 8, "ST TROPEZ" },
|
||||
{ 8, "TOULON" },
|
||||
{ 9, "BOURG EN B" },
|
||||
{ 9, "LYON" },
|
||||
{ 9, "MACON" },
|
||||
{ 9, "VALENCE" },
|
||||
{ 10, "BRIANCON" },
|
||||
{ 10, "CHAMBERY" },
|
||||
{ 10, "DIGNE" },
|
||||
{ 10, "GAP" },
|
||||
{ 10, "GRENOBLE" },
|
||||
{ 11, "ANNECY" },
|
||||
{ 11, "BESANCON" },
|
||||
{ 11, "DELEMONT" },
|
||||
{ 11, "LA CHAUX-DE-FONDS" },
|
||||
{ 12, "ASCHAFFENBURG" },
|
||||
{ 12, "BAD HOMBURG" },
|
||||
{ 12, "BAD KREUZNACH" },
|
||||
{ 12, "DARMSTADT" },
|
||||
{ 12, "FRANKFURT AM MAIN" },
|
||||
{ 12, "HEIDELBERG" },
|
||||
{ 12, "KAISERSLAUTERN" },
|
||||
{ 12, "KARLSRUHE" },
|
||||
{ 12, "LANDAU IN DER PFALZ" },
|
||||
{ 12, "LUDWIGSHAFEN" },
|
||||
{ 12, "MAINZ" },
|
||||
{ 12, "MANNHEIM" },
|
||||
{ 12, "PIRMASENS" },
|
||||
{ 12, "WORMS" },
|
||||
{ 13, "BITBURG" },
|
||||
{ 13, "HAGEN" },
|
||||
{ 13, "ISERLOHN" },
|
||||
{ 13, "KOBLENZ" },
|
||||
{ 13, "LUEDENSCHEID" },
|
||||
{ 13, "LUXEMBOURG" },
|
||||
{ 13, "NEUWIED" },
|
||||
{ 13, "SAARBRUECKEN" },
|
||||
{ 13, "SEDAN" },
|
||||
{ 13, "SIEGEN" },
|
||||
{ 13, "TRIER" },
|
||||
{ 13, "VERVIERS" },
|
||||
{ 13, "WIESBADEN" },
|
||||
{ 14, "AACHEN" },
|
||||
{ 14, "BIELEFELD" },
|
||||
{ 14, "BOCHUM" },
|
||||
{ 14, "BONN" },
|
||||
{ 14, "DORTMUND" },
|
||||
{ 14, "DUEREN" },
|
||||
{ 14, "DUESSELDORF" },
|
||||
{ 14, "DUISBURG" },
|
||||
{ 14, "ESSEN" },
|
||||
{ 14, "GELSENKIRCHEN" },
|
||||
{ 14, "GUETERSLOH" },
|
||||
{ 14, "KOELN" },
|
||||
{ 14, "LINGEN" },
|
||||
{ 14, "LIPPSTADT" },
|
||||
{ 14, "MOENCHENGLADBACH" },
|
||||
{ 14, "MUELHEIM AN DER RUHR" },
|
||||
{ 14, "MUENSTER" },
|
||||
{ 14, "NORDHORN" },
|
||||
{ 14, "OBERHAUSEN" },
|
||||
{ 14, "OSNABRUECK" },
|
||||
{ 14, "RECKLINGHAUSEN" },
|
||||
{ 14, "RHEINE" },
|
||||
{ 14, "SOLINGEN" },
|
||||
{ 14, "WESEL" },
|
||||
{ 14, "WUPPERTAL" },
|
||||
{ 15, "BRISTOL" },
|
||||
{ 15, "CARDIFF" },
|
||||
{ 15, "EXETER" },
|
||||
{ 15, "HOLYHEAD" },
|
||||
{ 15, "PLYMOUTH" },
|
||||
{ 15, "ST DAVIDS" },
|
||||
{ 15, "SWANSEA" },
|
||||
{ 16, "BIRMINGHAM" },
|
||||
{ 16, "BLACKPOOL" },
|
||||
{ 16, "LEEDS" },
|
||||
{ 16, "LEICESTER" },
|
||||
{ 16, "LIVERPOOL" },
|
||||
{ 16, "MANCHESTER" },
|
||||
{ 16, "MIDDLESBROUGH" },
|
||||
{ 16, "NEWCASTLE" },
|
||||
{ 16, "NOTTINGHAM" },
|
||||
{ 16, "SHEFFIELD" },
|
||||
{ 17, "AMIENS" },
|
||||
{ 17, "BEAUVAIS" },
|
||||
{ 17, "CAEN" },
|
||||
{ 17, "EVREUX" },
|
||||
{ 17, "LAON" },
|
||||
{ 17, "LE HAVRE" },
|
||||
{ 17, "ROUEN" },
|
||||
{ 18, "BOURNEMOUT" },
|
||||
{ 18, "BRIGHTON" },
|
||||
{ 18, "CAMBRIDGE" },
|
||||
{ 18, "DOVER" },
|
||||
{ 18, "IPSWICH" },
|
||||
{ 18, "KINGSTON" },
|
||||
{ 18, "LONDON" },
|
||||
{ 18, "NORTHAMPTON" },
|
||||
{ 18, "NORWICH" },
|
||||
{ 18, "OXFORD" },
|
||||
{ 18, "PORTSMOUTH" },
|
||||
{ 18, "READING" },
|
||||
{ 18, "SOUTHAMPTON" },
|
||||
{ 19, "BORKUM" },
|
||||
{ 19, "BREMERHAVEN" },
|
||||
{ 19, "CUXHAVEN" },
|
||||
{ 19, "DEN HELDER" },
|
||||
{ 19, "ELMSHORN" },
|
||||
{ 19, "EMDEN" },
|
||||
{ 19, "GRONINGEN" },
|
||||
{ 19, "HAMBURG" },
|
||||
{ 19, "LEEUWARDEN" },
|
||||
{ 19, "NORDERTSTEDT" },
|
||||
{ 19, "SPIEKEROOG" },
|
||||
{ 19, "ST PETER ORDING" },
|
||||
{ 19, "SYLT" },
|
||||
{ 19, "TEXEL" },
|
||||
{ 19, "WILHELMSHAVEN" },
|
||||
{ 20, "ALBORG" },
|
||||
{ 20, "ESBJERG" },
|
||||
{ 20, "FREDERIKSHAVN" },
|
||||
{ 20, "HERNING" },
|
||||
{ 20, "HOLSTERBRO" },
|
||||
{ 20, "SKAGEN" },
|
||||
{ 20, "THISTED" },
|
||||
{ 20, "THYBOROEN" },
|
||||
{ 20, "VIBORG" },
|
||||
{ 21, "ARHUS" },
|
||||
{ 21, "FREDERICIA" },
|
||||
{ 21, "HORSENS" },
|
||||
{ 21, "KOLDING" },
|
||||
{ 21, "ODENSE" },
|
||||
{ 21, "RANDERS" },
|
||||
{ 21, "SILKEBORG" },
|
||||
{ 21, "VEJLE" },
|
||||
{ 22, "BRAUNSCHWEIG" },
|
||||
{ 22, "BREMEN" },
|
||||
{ 22, "CELLE" },
|
||||
{ 22, "GOSLAR" },
|
||||
{ 22, "HAMELN" },
|
||||
{ 22, "HANNOVER" },
|
||||
{ 22, "HERFORD" },
|
||||
{ 22, "HILDESHEIM" },
|
||||
{ 22, "LUENEBURG" },
|
||||
{ 22, "MAGDEBURG" },
|
||||
{ 22, "MINDEN" },
|
||||
{ 22, "OLDENBURG" },
|
||||
{ 22, "WOLFSBURG" },
|
||||
{ 23, "HELSINGOER" },
|
||||
{ 23, "KALUNDBORG" },
|
||||
{ 23, "KOEBENHAVN" },
|
||||
{ 23, "MALMOE" },
|
||||
{ 23, "NAESTVED" },
|
||||
{ 23, "ROSKILDE" },
|
||||
{ 23, "SLAGELSE" },
|
||||
{ 24, "FEHMARN" },
|
||||
{ 24, "FLENSBURG" },
|
||||
{ 24, "GREIFSWALD" },
|
||||
{ 24, "KIEL" },
|
||||
{ 24, "LUEBECK" },
|
||||
{ 24, "ROSTOCK" },
|
||||
{ 24, "RUEGEN" },
|
||||
{ 24, "SCHWERIN" },
|
||||
{ 24, "STRALSUND" },
|
||||
{ 24, "SZCZECIN" },
|
||||
{ 24, "WISMAR" },
|
||||
{ 25, "AUGSBURG" },
|
||||
{ 25, "DEGGENDORF" },
|
||||
{ 25, "INGOLSTADT" },
|
||||
{ 25, "LANDSHUT" },
|
||||
{ 25, "PASSAU" },
|
||||
{ 25, "REGENSBURG" },
|
||||
{ 25, "ULM" },
|
||||
{ 26, "BURGHAUSEN" },
|
||||
{ 26, "FRIEDRICHSHAFEN" },
|
||||
{ 26, "KEMPTEN" },
|
||||
{ 26, "LINZ" },
|
||||
{ 26, "MUENCHEN" },
|
||||
{ 26, "ROSENHEIM" },
|
||||
{ 26, "SIGMARINGEN" },
|
||||
{ 26, "WELS" },
|
||||
{ 27, "BOLZANO" },
|
||||
{ 27, "MERANO" },
|
||||
{ 27, "TRENTO" },
|
||||
{ 28, "ANSBACH" },
|
||||
{ 28, "BAMBERG" },
|
||||
{ 28, "BAYREUTH" },
|
||||
{ 28, "ERLANGEN" },
|
||||
{ 28, "FUERTH" },
|
||||
{ 28, "NUERNBERG" },
|
||||
{ 28, "SCHWEINFURT" },
|
||||
{ 28, "WEIDEN" },
|
||||
{ 28, "WERTHEIM" },
|
||||
{ 28, "WUERZBURG" },
|
||||
{ 29, "ALTENBURG" },
|
||||
{ 29, "BAUTZEN" },
|
||||
{ 29, "CHEMNITZ" },
|
||||
{ 29, "COTTBUS" },
|
||||
{ 29, "DESSAU" },
|
||||
{ 29, "DRESDEN" },
|
||||
{ 29, "EISENHUETTENSTADT" },
|
||||
{ 29, "GOERLITZ" },
|
||||
{ 29, "HALLE" },
|
||||
{ 29, "HOYERSWERDA" },
|
||||
{ 29, "LEIPZIG" },
|
||||
{ 29, "WITTENBERG" },
|
||||
{ 29, "WROCLAW" },
|
||||
{ 30, "EISENACH" },
|
||||
{ 30, "ERFURT" },
|
||||
{ 30, "GOTHA" },
|
||||
{ 30, "HOF" },
|
||||
{ 30, "JENA" },
|
||||
{ 30, "NORDHAUSEN" },
|
||||
{ 30, "PLAUEN" },
|
||||
{ 30, "SUHL" },
|
||||
{ 30, "WEIMAR" },
|
||||
{ 30, "ZWICKAU" },
|
||||
{ 31, "EVIAN" },
|
||||
{ 31, "FRIBOURG" },
|
||||
{ 31, "GENEVE" },
|
||||
{ 31, "LAUSANNE" },
|
||||
{ 31, "MONTREUX" },
|
||||
{ 31, "NEUCHATEL" },
|
||||
{ 32, "AARAU" },
|
||||
{ 32, "BERN" },
|
||||
{ 32, "BIENNE" },
|
||||
{ 32, "FRAUENFELD" },
|
||||
{ 32, "KONSTANZ" },
|
||||
{ 32, "LUZERN" },
|
||||
{ 32, "SCHAFFHAUSEN" },
|
||||
{ 32, "SOLOTHURN" },
|
||||
{ 32, "ZUERICH" },
|
||||
{ 32, "ZUG" },
|
||||
{ 33, "ADELBODEN" },
|
||||
{ 33, "GRINDELWALD" },
|
||||
{ 33, "INTERLAKEN" },
|
||||
{ 34, "BRIG" },
|
||||
{ 34, "MARTIGNY" },
|
||||
{ 34, "SION" },
|
||||
{ 35, "ALTDORF" },
|
||||
{ 35, "GLARUS" },
|
||||
{ 35, "SARNEN" },
|
||||
{ 35, "SCHWYZ" },
|
||||
{ 35, "STANS" },
|
||||
{ 35, "ST.GALLEN" },
|
||||
{ 36, "CHUR" },
|
||||
{ 36, "DAVOS" },
|
||||
{ 37, "FULDA" },
|
||||
{ 37, "GIESSEN" },
|
||||
{ 37, "GOETTINGEN" },
|
||||
{ 37, "KASSEL" },
|
||||
{ 37, "MARBURG" },
|
||||
{ 38, "BELLINZONA" },
|
||||
{ 38, "EDOLO" },
|
||||
{ 38, "LOCARNO" },
|
||||
{ 38, "LUGANO" },
|
||||
{ 39, "AOSTA" },
|
||||
{ 39, "SESTRIERE" },
|
||||
{ 40, "ALESSANDRIA" },
|
||||
{ 40, "BERGAMO" },
|
||||
{ 40, "BRESCIA" },
|
||||
{ 40, "MILANO" },
|
||||
{ 40, "PARMA" },
|
||||
{ 40, "PIACENZA" },
|
||||
{ 40, "TORINO" },
|
||||
{ 40, "VERONA" },
|
||||
{ 41, "FIRENZE" },
|
||||
{ 41, "PERUGIA" },
|
||||
{ 41, "PISA" },
|
||||
{ 41, "ROMA" },
|
||||
{ 41, "SIENA" },
|
||||
{ 41, "VATICANO" },
|
||||
{ 42, "AMSTERDAM" },
|
||||
{ 42, "ARNHEM" },
|
||||
{ 42, "ASSEN" },
|
||||
{ 42, "DEN HAAG" },
|
||||
{ 42, "EINDHOVEN" },
|
||||
{ 42, "HAARLEM" },
|
||||
{ 42, "LELYSTAD" },
|
||||
{ 42, "ROTTERDAM" },
|
||||
{ 42, "S.HERTOGENBOSCH" },
|
||||
{ 42, "UTRECHT" },
|
||||
{ 42, "ZWOLLE" },
|
||||
{ 43, "CANNES" },
|
||||
{ 43, "GENOVA" },
|
||||
{ 43, "LA SPEZIA" },
|
||||
{ 43, "MONACO" },
|
||||
{ 43, "NICE" },
|
||||
{ 43, "SAN REMO" },
|
||||
{ 44, "BOLOGNA" },
|
||||
{ 44, "NOVA GORIC" },
|
||||
{ 44, "RIJEKA" },
|
||||
{ 44, "RIMINI" },
|
||||
{ 44, "TRIESTE" },
|
||||
{ 44, "UDINE" },
|
||||
{ 44, "VENEZIA" },
|
||||
{ 45, "BASEL" },
|
||||
{ 45, "BELFORT" },
|
||||
{ 45, "COLMAR" },
|
||||
{ 45, "FREIBURG" },
|
||||
{ 45, "LIESTAL" },
|
||||
{ 45, "LOERRACH" },
|
||||
{ 45, "MULHOUSE" },
|
||||
{ 45, "OFFENBURG" },
|
||||
{ 45, "STRASBOURG" },
|
||||
{ 46, "GRAZ" },
|
||||
{ 46, "KLAGENFURT" },
|
||||
{ 46, "LIENZ" },
|
||||
{ 46, "LJUBLJANA" },
|
||||
{ 46, "MARIBOR" },
|
||||
{ 46, "SEMMERING" },
|
||||
{ 46, "VILLACH" },
|
||||
{ 46, "ZELTWEG" },
|
||||
{ 47, "BAD GASTEIN" },
|
||||
{ 47, "INNSBRUCK" },
|
||||
{ 47, "ISCHGL" },
|
||||
{ 47, "LANDECK" },
|
||||
{ 47, "SOELDEN" },
|
||||
{ 47, "TUXERTAL" },
|
||||
{ 48, "BAD TOELZ" },
|
||||
{ 48, "BERCHTESGADEN" },
|
||||
{ 48, "BISCHOFSHOFEN" },
|
||||
{ 48, "BREGENZ" },
|
||||
{ 48, "GARMISCH PARTENKIRCHEN" },
|
||||
{ 48, "KITZBUEHEL" },
|
||||
{ 48, "LINDAU" },
|
||||
{ 48, "SALZBURG" },
|
||||
{ 48, "SCHLADMING" },
|
||||
{ 48, "STEYR" },
|
||||
{ 48, "VADUZ" },
|
||||
{ 49, "BRATISLAVA" },
|
||||
{ 49, "EISENSTADT" },
|
||||
{ 49, "GYOER" },
|
||||
{ 49, "KLOSTERNEUBURG" },
|
||||
{ 49, "SOPRON" },
|
||||
{ 49, "SZOMBATHELY" },
|
||||
{ 49, "TRENCIN" },
|
||||
{ 49, "WIEN" },
|
||||
{ 49, "WIENER NEUSTADT" },
|
||||
{ 49, "ZALAEGERSZEG" },
|
||||
{ 50, "BRNO" },
|
||||
{ 50, "BUDEJOVICE" },
|
||||
{ 50, "CHEB" },
|
||||
{ 50, "HAVLICKAV BROD" },
|
||||
{ 50, "HRADEC/KRA" },
|
||||
{ 50, "JIHLAVA" },
|
||||
{ 50, "KARLOVY VARY" },
|
||||
{ 50, "KLADNO" },
|
||||
{ 50, "MOST" },
|
||||
{ 50, "OLOMOUC" },
|
||||
{ 50, "OSTRAVA" },
|
||||
{ 50, "PARDUBICE" },
|
||||
{ 50, "PLZEN" },
|
||||
{ 50, "PRAHA" },
|
||||
{ 50, "PREROV" },
|
||||
{ 50, "PROSTEJOV" },
|
||||
{ 50, "ST POELTEN" },
|
||||
{ 50, "ZWETTL" },
|
||||
{ 51, "CHOMUTOV" },
|
||||
{ 51, "DECIN" },
|
||||
{ 51, "LIBEREC" },
|
||||
{ 51, "OPAVA" },
|
||||
{ 51, "PIRNA" },
|
||||
{ 51, "SNIEZKA" },
|
||||
{ 51, "TEPLICE" },
|
||||
{ 51, "USTI NAD LABEM" },
|
||||
{ 51, "WALBRZYCH" },
|
||||
{ 52, "BERLIN" },
|
||||
{ 52, "BRANDENBURG" },
|
||||
{ 52, "FINOW" },
|
||||
{ 52, "FRANKFURT AN DER ODER" },
|
||||
{ 52, "GORZOW" },
|
||||
{ 52, "NEUBRANDENBURG" },
|
||||
{ 52, "POTSDAM" },
|
||||
{ 52, "POZNAN" },
|
||||
{ 52, "STENDAL" },
|
||||
{ 53, "GOETEBORG" },
|
||||
{ 53, "HALMSTAD" },
|
||||
{ 54, "GAEVLE" },
|
||||
{ 54, "NYKOPING" },
|
||||
{ 54, "STOCKHOLM" },
|
||||
{ 54, "UPPSALA" },
|
||||
{ 54, "VAESTERAS" },
|
||||
{ 55, "BORGHOLM" },
|
||||
{ 55, "BORNHOLM" },
|
||||
{ 55, "KALMAR" },
|
||||
{ 55, "LINKOEPING" },
|
||||
{ 55, "RONNE" },
|
||||
{ 55, "VISBY" },
|
||||
{ 56, "BORAS" },
|
||||
{ 56, "JOENKOEPING" },
|
||||
{ 56, "KARLSTAD" },
|
||||
{ 56, "OEREBRO" },
|
||||
{ 57, "BADEN-BADEN" },
|
||||
{ 57, "DONAUESCHINGEN" },
|
||||
{ 57, "FREUDENSTADT" },
|
||||
{ 57, "VILLINGEN-SCHWENNINGEN" },
|
||||
{ 58, "DRAMMEN" },
|
||||
{ 58, "FREDRIKSTADEN" },
|
||||
{ 58, "OSLO" },
|
||||
{ 58, "TOENSBERG" },
|
||||
{ 59, "AALEN" },
|
||||
{ 59, "GOEPPINGEN" },
|
||||
{ 59, "HEILBRONN" },
|
||||
{ 59, "PFORZHEIM" },
|
||||
{ 59, "REUTLINGEN" },
|
||||
{ 59, "SCHWAEBISCH GMUEND" },
|
||||
{ 59, "STUTTGART" },
|
||||
{ 59, "TUEBINGEN" },
|
||||
{ 60, "NAPOLI" },
|
||||
{ 61, "ANCONA" },
|
||||
{ 61, "PESCARA" },
|
||||
{ 61, "SAN MARINO" },
|
||||
{ 62, "BARI" },
|
||||
{ 62, "FOGGIA" },
|
||||
{ 62, "LECCE" },
|
||||
{ 63, "BEKESCSABA" },
|
||||
{ 63, "BRANSKA" },
|
||||
{ 63, "BUDAPEST" },
|
||||
{ 63, "DEBRECEN" },
|
||||
{ 63, "DUNAIJVAROS" },
|
||||
{ 63, "EGER" },
|
||||
{ 63, "ERD" },
|
||||
{ 63, "HODMEZOVASARHELY" },
|
||||
{ 63, "KAPOSVAR" },
|
||||
{ 63, "KECSKEMET" },
|
||||
{ 63, "KOSICE" },
|
||||
{ 63, "MISKOLC" },
|
||||
{ 63, "NAGYKANIZSA" },
|
||||
{ 63, "NYIREGYHAZA" },
|
||||
{ 63, "OZD" },
|
||||
{ 63, "PECS" },
|
||||
{ 63, "SALGOTARJAN" },
|
||||
{ 63, "SIOFOK" },
|
||||
{ 63, "SZEGED" },
|
||||
{ 63, "SZEKESFEEVAR" },
|
||||
{ 63, "SZOLNOK" },
|
||||
{ 63, "TATABANYA" },
|
||||
{ 63, "VESZPREM" },
|
||||
{ 64, "MADRID" },
|
||||
{ 65, "BILBAO" },
|
||||
{ 66, "CATANIA" },
|
||||
{ 66, "COSENZA" },
|
||||
{ 66, "MESSINA" },
|
||||
{ 66, "PALERMO" },
|
||||
{ 66, "REGGIO CALABRIA" },
|
||||
{ 67, "IBIZA" },
|
||||
{ 67, "MAHON" },
|
||||
{ 67, "PALMA DE MALLORCA" },
|
||||
{ 68, "VALENCIA" },
|
||||
{ 69, "BARCELONA" },
|
||||
{ 69, "FIGUERES" },
|
||||
{ 69, "GIRONA" },
|
||||
{ 69, "LLORET DE MAR" },
|
||||
{ 70, "ANDORRA LA VELLA" },
|
||||
{ 71, "SEVILLA" },
|
||||
{ 72, "LISBOA" },
|
||||
{ 73, "AJACCIO" },
|
||||
{ 73, "BASTIA" },
|
||||
{ 73, "CAGLIARI" },
|
||||
{ 73, "SASSARI" },
|
||||
{ 74, "GIJON" },
|
||||
{ 75, "CORK" },
|
||||
{ 75, "GALWAY" },
|
||||
{ 75, "LIMERICK" },
|
||||
{ 76, "BELFAST" },
|
||||
{ 76, "DUBLIN" },
|
||||
{ 77, "ABERDEEN" },
|
||||
{ 77, "EDINBURGH" },
|
||||
{ 77, "GLASGOW" },
|
||||
{ 77, "ISLE OF MAN" },
|
||||
{ 78, "BERGEN" },
|
||||
{ 78, "STAVANGER" },
|
||||
{ 79, "TRONDHEIM" },
|
||||
{ 80, "SUNDSVALL" },
|
||||
{ 81, "GDANSK" },
|
||||
{ 81, "OLSZTYN" },
|
||||
{ 81, "SLUPSK" },
|
||||
{ 82, "BIALYSTOK" },
|
||||
{ 82, "BYDGOSZCZ" },
|
||||
{ 82, "LODZ" },
|
||||
{ 82, "LUBLIN" },
|
||||
{ 82, "TORUN" },
|
||||
{ 82, "WARSZAWA" },
|
||||
{ 83, "BIELSKO" },
|
||||
{ 83, "KATOWICE" },
|
||||
{ 83, "KIELCE" },
|
||||
{ 83, "KRAKOW" },
|
||||
{ 83, "OPOLE" },
|
||||
{ 83, "RZESZOW" },
|
||||
{ 83, "ZAKOPANE" },
|
||||
{ 84, "UMEA" },
|
||||
{ 85, "FALUN" },
|
||||
{ 85, "OESTERSUND" },
|
||||
{ 86, "SAMEDAN" },
|
||||
{ 87, "OSIJEK" },
|
||||
{ 87, "ZAGREB" },
|
||||
{ 88, "ZERMATT" },
|
||||
{ 89, "SPLIT" },
|
||||
{ 24, "Doerphof" },
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
void display_city(const char *search)
|
||||
{
|
||||
int i;
|
||||
int found = 0;
|
||||
|
||||
for (i = 0; city_list[i].name; i++) {
|
||||
if (strcasestr(city_list[i].name, search)) {
|
||||
found++;
|
||||
printf("City %s is located in region %d.\n", city_list[i].name, city_list[i].region);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
printf("No city found for '%s', try larger city.\n", search);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
void display_city(const char *search);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
/* implementation of DCF77 transmitter and receiver
|
||||
/* implementation of DCF77 transmitter and receiver, including weather
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
|
@ -28,6 +28,7 @@
|
|||
#include <math.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "dcf77.h"
|
||||
#include "weather.h"
|
||||
|
||||
double get_time(void);
|
||||
|
||||
|
@ -35,7 +36,6 @@ double get_time(void);
|
|||
#define TEST_FREQUENCY 1000
|
||||
#define CARRIER_BANDWIDTH 10.0
|
||||
#define SAMPLE_CLOCK 1000
|
||||
#define CLOCK_1S 1.0
|
||||
#define CLOCK_BANDWIDTH 0.1
|
||||
#define REDUCTION_FACTOR 0.15
|
||||
#define REDUCTION_TH 0.575
|
||||
|
@ -43,6 +43,9 @@ double get_time(void);
|
|||
|
||||
#define level2db(level) (20 * log10(level))
|
||||
|
||||
/* uncomment to speed up transmission by factor 10 and feed the data to the receiver */
|
||||
//#define DEBUG_LOOP
|
||||
|
||||
static int fast_math = 0;
|
||||
static float *sin_tab = NULL, *cos_tab = NULL;
|
||||
|
||||
|
@ -50,6 +53,292 @@ const char *time_zone[4] = { "???", "CEST", "CET", "???" };
|
|||
const char *week_day[8] = { "???", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
|
||||
const char *month_name[13] = { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
|
||||
const char *datasets_0_59[8] = {
|
||||
"Maximum values 1st day (today)",
|
||||
"Minimum values 1st day (today)",
|
||||
"Maximum values 2nd day (tomorrow)",
|
||||
"Minimum values 2nd day (tomorrow)",
|
||||
"Maximum values 3rd day (in two days)",
|
||||
"Minimum values 3rd day (in two days)",
|
||||
"Maximum values 4th day (in three days)",
|
||||
"Wind and weather anomaly 4th day (in three days)",
|
||||
};
|
||||
|
||||
const char *datasets_60_89[2] = {
|
||||
"Maximum values 1st day (following day)",
|
||||
"Maximum values 2nd day (2nd following day)",
|
||||
};
|
||||
|
||||
const char *region[90] = {
|
||||
"F - Bordeaux, Aquitaine (Suedwestfrankreich)",
|
||||
"F - La Rochelle, Poitou-Charentes (Westkueste Frankreichs)",
|
||||
"F - Paris, Ile-de-France (Pariser Becken)",
|
||||
"F - Brest, Bretagne",
|
||||
"F - Clermont-Ferrand (Massif Central), Auvergne (Zentralmassiv)",
|
||||
"F - Beziers, Languedoc-Roussillon",
|
||||
"B - Bruxelles, Brussel (Benelux)",
|
||||
"F - Dijon (Bourgogne), Bourgogne (Ostfrankreich / Burgund)",
|
||||
"F - Marseille, Provence-Alpes-Côte d'Azur",
|
||||
"F - Lyon (Rhone-Alpes), Rhone-Alpes (Rhonetal)",
|
||||
"F - Grenoble (Savoie), Rhone-Alpes (Franz. Alpen)",
|
||||
"CH - La Chaux de Fond, Jura",
|
||||
"D - Frankfurt am Main, Hessen (Unterer Rheingraben)",
|
||||
"D - Trier, Westliches Mittelgebirge",
|
||||
"D - Duisburg, Nordrhein-Westfalen",
|
||||
"GB - Swansea, Wales (Westl. England / Wales)",
|
||||
"GB - Manchester, England (Noerdliches England)",
|
||||
"F - le Havre, Haute-Normandie (Normandie)",
|
||||
"GB - London, England (Suedostengland / London)",
|
||||
"D - Bremerhaven, Bremen (Nordseekueste)",
|
||||
"DK - Herning, Ringkobing (Nordwestliches Juetland)",
|
||||
"DK - Arhus, Arhus (Oestliches Juetland)",
|
||||
"D - Hannover, Niedersachsen (Norddeutschland)",
|
||||
"DK - Kobenhavn, Staden Kobenhaven (Seeland)",
|
||||
"D - Rostock, Mecklenburg-Vorpommern (Ostseekueste)",
|
||||
"D - Ingolstadt, Bayern (Donautal)",
|
||||
"D - Muenchen, Bayern (Suedbayern)",
|
||||
"I - Bolzano, Trentino-Alto Adige (Suedtirol)",
|
||||
"D - Nuernberg, Bayern (Nordbayern)",
|
||||
"D - Leipzig, Sachsen",
|
||||
"D - Erfurt, Thueringen",
|
||||
"CH - Lausanne, Genferseeregion (Westl. Schweizer Mitteland)",
|
||||
"CH - Zuerich (Oestl. Schweizer Mittelland)",
|
||||
"CH - Adelboden (Westl. Schweizer Alpennordhang)",
|
||||
"CH - Sion, Wallis",
|
||||
"CH - Glarus, Oestlicher Schweizer Alpennordhang",
|
||||
"CH - Davos, Graubuenden",
|
||||
"D - Kassel, Hessen (Mittelgebirge Ost)",
|
||||
"CH - Locarno, Tessin",
|
||||
"I - Sestriere, Piemont. Alpen",
|
||||
"I - Milano, Lombardia (Poebene)",
|
||||
"I - Roma, Lazio (Toskana)",
|
||||
"NL - Amsterdam, Noord-Holland (Holland)",
|
||||
"I - Genova, Liguria (Golf von Genua)",
|
||||
"I - Venezia, Veneto (Pomuendung)",
|
||||
"F - Strasbourg, Alsace (Oberer Rheingraben)",
|
||||
"A - Klagenfurt, Kaernten (Oesterreich. Alpensuedhang)",
|
||||
"A - Innsbruck, Tirol (Inneralpine Gebiete Oesterreichs)",
|
||||
"A - Salzburg, Bayr. / Oesterreich. Alpennordhang",
|
||||
"SK (Oesterreich / Slovakia) - Wien / Bratislava",
|
||||
"CZ - Praha, Prag (Tschechisches Becken)",
|
||||
"CZ - Decin, Severocesky (Erzgebirge)",
|
||||
"D - Berlin, Ostdeutschland",
|
||||
"S - Goeteborg, Goeteborgs och Bohus Laen (Westkueste Schweden)",
|
||||
"S - Stockholm, Stockholms Laen (Stockholm)",
|
||||
"S - Kalmar, Kalmar Laen (Schwedische Ostseekueste)",
|
||||
"S - Joenkoeping, Joenkoepings Laen (Suedschweden)",
|
||||
"D - Donaueschingen, Baden-Wuerttemberg (Schwarzwald / Schwaebische Alb)",
|
||||
"N - Oslo",
|
||||
"D - Stuttgart, Baden-Wuerttemberg (Noerdl. Baden Wuerttemberg)",
|
||||
"I - Napoli",
|
||||
"I - Ancona",
|
||||
"I - Bari",
|
||||
"HU - Budapest",
|
||||
"E - Madrid",
|
||||
"E - Bilbao",
|
||||
"I - Palermo",
|
||||
"E - Palma de Mallorca",
|
||||
"E - Valencia",
|
||||
"E - Barcelona",
|
||||
"AND - Andorra",
|
||||
"E - Sevilla",
|
||||
"P - Lissabon",
|
||||
"I - Sassari, (Sardinien / Korsika)",
|
||||
"E - Gijon",
|
||||
"IRL - Galway",
|
||||
"IRL - Dublin",
|
||||
"GB - Glasgow",
|
||||
"N - Stavanger",
|
||||
"N - Trondheim",
|
||||
"S - Sundsvall",
|
||||
"PL - Gdansk",
|
||||
"PL - Warszawa",
|
||||
"PL - Krakow",
|
||||
"S - Umea",
|
||||
"S - Oestersund",
|
||||
"CH - Samedan",
|
||||
"CR - Zagreb",
|
||||
"CH - Zermatt",
|
||||
"CR - Split",
|
||||
};
|
||||
|
||||
const char *weathers_day[16] = {
|
||||
"Reserved",
|
||||
"Sunny",
|
||||
"Partly clouded",
|
||||
"Mostly clouded",
|
||||
"Overcast",
|
||||
"Heat storms",
|
||||
"Heavy Rain",
|
||||
"Snow",
|
||||
"Fog",
|
||||
"Sleet",
|
||||
"Rain shower",
|
||||
"Light rain",
|
||||
"Snow showers",
|
||||
"Frontal storms",
|
||||
"Stratus cloud",
|
||||
"Sleet storms",
|
||||
};
|
||||
|
||||
const char *weathers_night[16] = {
|
||||
"Reserved",
|
||||
"Clear",
|
||||
"Partly clouded",
|
||||
"Mostly clouded",
|
||||
"Overcast",
|
||||
"Heat storms",
|
||||
"Heavy Rain",
|
||||
"Snow",
|
||||
"Fog",
|
||||
"Sleet",
|
||||
"Rain shower",
|
||||
"Light rain",
|
||||
"Snow showers",
|
||||
"Frontal storms",
|
||||
"Stratus cloud",
|
||||
"Sleet storms",
|
||||
};
|
||||
|
||||
const char *extremeweathers[16] = {
|
||||
"None",
|
||||
"Heavy Weather 24 hrs.",
|
||||
"Heavy weather Day",
|
||||
"Heavy weather Night",
|
||||
"Storm 24hrs.",
|
||||
"Storm Day",
|
||||
"Storm Night",
|
||||
"Wind gusts Day",
|
||||
"Wind gusts Night",
|
||||
"Icy rain morning",
|
||||
"Icy rain evening",
|
||||
"Icy rain night",
|
||||
"Fine dust",
|
||||
"Ozon",
|
||||
"Radiation",
|
||||
"High water",
|
||||
};
|
||||
|
||||
const char *probabilities[8] = {
|
||||
"0 %",
|
||||
"15 %",
|
||||
"30 %",
|
||||
"45 %",
|
||||
"60 %",
|
||||
"75 %",
|
||||
"90 %",
|
||||
"100 %",
|
||||
};
|
||||
|
||||
const char *winddirections[16] = {
|
||||
"North",
|
||||
"Northeast",
|
||||
"East",
|
||||
"Southeast",
|
||||
"South",
|
||||
"Southwest",
|
||||
"West",
|
||||
"Northwest",
|
||||
"Changeable",
|
||||
"Foen",
|
||||
"Biese N/O",
|
||||
"Mistral N",
|
||||
"Scirocco S",
|
||||
"Tramont W",
|
||||
"reserved",
|
||||
"reserved",
|
||||
};
|
||||
|
||||
const char *windstrengths[8] = {
|
||||
"0",
|
||||
"0-2",
|
||||
"3-4",
|
||||
"5-6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
">=10",
|
||||
};
|
||||
|
||||
const char *yesno[2] = {
|
||||
"No",
|
||||
"Yes",
|
||||
};
|
||||
const char *anomaly1[4] = {
|
||||
"Same Weather",
|
||||
"Jump 1",
|
||||
"Jump 2",
|
||||
"Jump 3",
|
||||
};
|
||||
const char *anomaly2[4] = {
|
||||
"0-2 hrs",
|
||||
"2-4 hrs",
|
||||
"5-6 hrs",
|
||||
"7-8 hrs",
|
||||
};
|
||||
|
||||
/* show a list of weather data values */
|
||||
void list_weather(void)
|
||||
{
|
||||
time_t timestamp, t;
|
||||
struct tm *tm;
|
||||
int i, j;
|
||||
|
||||
/* get time stamp of this day, but at 22:00 UTC */
|
||||
timestamp = floor(get_time());
|
||||
timestamp -= timestamp % 86400;
|
||||
timestamp += 79200;
|
||||
|
||||
printf("\n");
|
||||
printf("List of all regions\n");
|
||||
printf("-------------------\n");
|
||||
for (i = 0; i < 90; i++) {
|
||||
printf("Region: %2d = %s\n", i, region[i]);
|
||||
for (j = 0; j < 8; j++) {
|
||||
/* get local time where transmission starts */
|
||||
if (i < 60) {
|
||||
t = timestamp + 180 * i + 10800 * j;
|
||||
tm = localtime(&t);
|
||||
printf(" -> Transmission at %02d:%02d of %s\n", tm->tm_hour, tm->tm_min, datasets_0_59[j]);
|
||||
} else if (j < 2) {
|
||||
t = timestamp + 180 * (i - 60) + 10800 * 7 + 5400 * j;
|
||||
tm = localtime(&t);
|
||||
printf(" -> Transmission at %02d:%02d of %s\n", tm->tm_hour, tm->tm_min, datasets_60_89[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("List of all weathers\n");
|
||||
printf("--------------------\n");
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (i == 1)
|
||||
printf("Weather: %2d = %s (day) %s (night)\n", i, weathers_day[i], weathers_night[i]);
|
||||
else
|
||||
printf("Weather: %2d = %s (day and night)\n", i, weathers_day[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("List of all extreme weathers\n");
|
||||
printf("----------------------------\n");
|
||||
for (i = 1; i < 16; i++) {
|
||||
printf("Extreme: %2d = %s\n", i, extremeweathers[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static const char *show_bits(uint64_t value, int bits)
|
||||
{
|
||||
static char bit[128];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < bits; i++)
|
||||
bit[i] = '0' + ((value >> i) & 1);
|
||||
sprintf(bit + i, "(%" PRIu64 ")", value);
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
/* global init */
|
||||
int dcf77_init(int _fast_math)
|
||||
{
|
||||
|
@ -82,6 +371,7 @@ void dcf77_exit(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* instance creation */
|
||||
dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone)
|
||||
{
|
||||
dcf77_t *dcf77 = NULL;
|
||||
|
@ -150,6 +440,7 @@ dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone)
|
|||
dcf77->dmp_input_level = display_measurements_add(&dcf77->dispmeas, "Input Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY);
|
||||
dcf77->dmp_signal_level = display_measurements_add(&dcf77->dispmeas, "Signal Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY);
|
||||
dcf77->dmp_signal_quality = display_measurements_add(&dcf77->dispmeas, "Signal Qualtiy", "%.0f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, -INFINITY);
|
||||
dcf77->dmp_current_second = display_measurements_add(&dcf77->dispmeas, "Current Second", "%.0f", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 59.0, -INFINITY);
|
||||
}
|
||||
|
||||
if (tx->enable)
|
||||
|
@ -157,9 +448,17 @@ dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone)
|
|||
if (rx->enable)
|
||||
PDEBUG(DDCF77, DEBUG_INFO, "DCF77 receiver has been created.\n");
|
||||
|
||||
#if 0
|
||||
void rx_frame_test(dcf77_t *dcf77, const char *string);
|
||||
rx_frame_test(dcf77, "00001000011111100100101101001010000110000110011100001010000");
|
||||
rx_frame_test(dcf77, "00101010111111000100111101000010000110000110011100001010000");
|
||||
rx_frame_test(dcf77, "00011000101111000100100011000010000110000110011100001010000");
|
||||
#endif
|
||||
|
||||
return dcf77;
|
||||
}
|
||||
|
||||
/* instance destruction */
|
||||
void dcf77_destroy(dcf77_t *dcf77)
|
||||
{
|
||||
if (dcf77) {
|
||||
|
@ -171,33 +470,264 @@ void dcf77_destroy(dcf77_t *dcf77)
|
|||
PDEBUG(DDCF77, DEBUG_INFO, "DCF77 has been destroyed.\n");
|
||||
}
|
||||
|
||||
/* set inital time stamp at the moment the stream starts */
|
||||
void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp)
|
||||
static void display_weather_temperature(const char *desc, uint32_t weather)
|
||||
{
|
||||
int value;
|
||||
|
||||
value = (weather >> 16) & 0x3f;
|
||||
switch (value) {
|
||||
case 0:
|
||||
printf("%s%s = < -21 degrees C\n", desc, show_bits(value, 6));
|
||||
break;
|
||||
case 63:
|
||||
printf("%s%s = > 40 degrees C\n", desc, show_bits(value, 6));
|
||||
break;
|
||||
default:
|
||||
printf("%s%s = %d degrees C\n", desc, show_bits(value, 6), value - 22);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TX part
|
||||
*/
|
||||
|
||||
/* Adjust given time stamp to the time stamp when the weather of the given
|
||||
* region istransmitted. An offset is used to start several minutes before
|
||||
* the transmission of the region starts, so the receiver has time to sync
|
||||
* to the signal first, so it will not miss that weather info.
|
||||
*
|
||||
* Note that this will only set the start of transmitting weather of the
|
||||
* current day (and day temperature).
|
||||
*/
|
||||
time_t dcf77_start_weather(time_t timestamp, int region, int offset)
|
||||
{
|
||||
int hour, min;
|
||||
|
||||
/* hour+min at UTC */
|
||||
if (region < 60) {
|
||||
/* first dataset starts at 22:00 UTC */
|
||||
hour = (22 + region / 20) % 24;
|
||||
} else {
|
||||
/* seventh dataset starts at 19:00 UTC */
|
||||
hour = (19 + (region - 60) / 20) % 24;
|
||||
}
|
||||
min = (region % 20) * 3;
|
||||
PDEBUG(DDCF77, DEBUG_INFO, "Setting UTC time for region %d to %02d:%02d minutes.\n", region, hour, min);
|
||||
|
||||
/* reset to 0:00 UTC at same day */
|
||||
timestamp -= (timestamp % 86400);
|
||||
|
||||
/* add time to start */
|
||||
timestamp += hour * 3600 + min * 60;
|
||||
|
||||
/* substract offset */
|
||||
PDEBUG(DDCF77, DEBUG_INFO, "Setting timestamp offset to %d minutes.\n", offset);
|
||||
timestamp -= 60 * offset;
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/* set weather to transmit on all regions */
|
||||
void dcf77_set_weather(dcf77_t *dcf77, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night)
|
||||
{
|
||||
dcf77_tx_t *tx = &dcf77->tx;
|
||||
double now;
|
||||
time_t t;
|
||||
|
||||
/* get time stamp */
|
||||
if (timestamp < 0)
|
||||
now = get_time();
|
||||
tx->weather = 1;
|
||||
tx->weather_day = weather_day;
|
||||
tx->weather_night = weather_night;
|
||||
tx->rain = rain;
|
||||
tx->extreme = extreme;
|
||||
tx->wind_dir = wind_dir;
|
||||
tx->wind_bft = wind_bft;
|
||||
tx->temperature_day = temperature_day;
|
||||
tx->temperature_night = temperature_night;
|
||||
}
|
||||
|
||||
/* generate weather frame from weather data */
|
||||
static uint64_t generate_weather(time_t timestamp, int minute, int utc_hour, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night)
|
||||
{
|
||||
int dataset = ((utc_hour + 2) % 24) * 20 + (minute / 3); /* data sets since 22:00 UTC */
|
||||
struct tm *tm;
|
||||
uint64_t key;
|
||||
uint32_t weather;
|
||||
int value, temperature;
|
||||
int i;
|
||||
int best, diff;
|
||||
|
||||
/* generate key from time stamp of next minute */
|
||||
timestamp += 60;
|
||||
tm = localtime(×tamp);
|
||||
key = 0;
|
||||
key |= (uint64_t)(tm->tm_min % 10) << 0;
|
||||
key |= (uint64_t)(tm->tm_min / 10) << 4;
|
||||
key |= (uint64_t)(tm->tm_hour % 10) << 8;
|
||||
key |= (uint64_t)(tm->tm_hour / 10) << 12;
|
||||
key |= (uint64_t)(tm->tm_mday % 10) << 16;
|
||||
key |= (uint64_t)(tm->tm_mday / 10) << 20;
|
||||
key |= (uint64_t)((tm->tm_mon + 1) % 10) << 24;
|
||||
key |= (uint64_t)((tm->tm_mon + 1) / 10) << 28;
|
||||
if (tm->tm_wday > 0)
|
||||
key |= (uint64_t)(tm->tm_wday) << 29;
|
||||
else
|
||||
now = timestamp;
|
||||
t = floor(now);
|
||||
key |= (uint64_t)(7) << 29;
|
||||
key |= (uint64_t)(tm->tm_year % 10) << 32;
|
||||
key |= (uint64_t)((tm->tm_year / 10) % 10) << 36;
|
||||
|
||||
/* generate weather data */
|
||||
timestamp -= 120;
|
||||
weather = 0;
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Encoding weather for dataset %d/480\n", dataset);
|
||||
printf("Peparing Weather INFO\n");
|
||||
printf("---------------------\n");
|
||||
printf("Time (UTC): %02d:%02d\n", (int)(timestamp / 3600) % 24, (int)(timestamp / 60) % 60);
|
||||
/* dataset and region for 0..59 */
|
||||
printf("Dataset: %s\n", datasets_0_59[dataset / 60]);
|
||||
value = dataset % 60;
|
||||
printf("Region: %d = %s\n", value, region[value]);
|
||||
/* calc. weather of region */
|
||||
if (weather_day < 0 || weather_day > 15)
|
||||
weather_day = 1;
|
||||
weather |= weather_day << 0;
|
||||
if (weather_night < 0 || weather_night > 15)
|
||||
weather_night = 1;
|
||||
weather |= weather_night << 4;
|
||||
/* calc. temperature of region 0..59 (day/night) or region 60..89 (day) */
|
||||
if (((dataset / 60) & 1) == 0 || (dataset / 60) == 7)
|
||||
temperature = temperature_day + 22;
|
||||
else
|
||||
temperature = temperature_night + 22;
|
||||
if (temperature < 0)
|
||||
temperature = 0;
|
||||
if (temperature > 63)
|
||||
value = 63;
|
||||
weather |= temperature << 16;
|
||||
/* show weather of region 0..59 */
|
||||
if ((dataset / 60) < 7) {
|
||||
printf("Weather (day): %s = %s\n", show_bits(weather_day, 4), weathers_day[weather_day]);
|
||||
printf("Weather (night): %s = %s\n", show_bits(weather_night, 4), weathers_night[weather_night]);
|
||||
}
|
||||
/* show extreme/wind/rain of region 0..59 */
|
||||
if (((dataset / 60) & 1) == 0) {
|
||||
/* even datasets, this is 'Day' data */
|
||||
if (extreme < 0 || extreme > 15)
|
||||
value = 0;
|
||||
else
|
||||
value = extreme;
|
||||
printf("Extreme weather: %s = %s\n", show_bits(value, 4), extremeweathers[value]);
|
||||
weather |= value << 8;
|
||||
best = 0;
|
||||
for (i = 0; i < 8; i++) {
|
||||
diff = abs(atoi(probabilities[i]) - rain);
|
||||
if (i == 0 || diff < best) {
|
||||
best = diff;
|
||||
value = i;
|
||||
}
|
||||
}
|
||||
printf("Rain Probability: %s = %s (best match for %d)\n", show_bits(value, 3), probabilities[value], rain);
|
||||
weather |= value << 12;
|
||||
value = 0;
|
||||
printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]);
|
||||
weather |= value << 15;
|
||||
display_weather_temperature("Temperature (day): ", weather);
|
||||
} else {
|
||||
/* odd datasets, this is 'Night' data */
|
||||
if (wind_dir < 0 || wind_dir > 15)
|
||||
value = 8;
|
||||
else
|
||||
value = wind_dir;
|
||||
printf("Wind direction: %s = %s\n", show_bits(value, 4), winddirections[value]);
|
||||
weather |= value << 8;
|
||||
if (wind_bft < 1)
|
||||
value = 0;
|
||||
else
|
||||
if (wind_bft < 7)
|
||||
value = (wind_bft + 1) / 2;
|
||||
else
|
||||
if (wind_bft < 10)
|
||||
value = wind_bft - 3;
|
||||
else
|
||||
value = 7;
|
||||
printf("Wind strength: %s = %s (best match for %d)\n", show_bits(value, 3), windstrengths[value], wind_bft);
|
||||
weather |= value << 12;
|
||||
value = 0;
|
||||
printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]);
|
||||
weather |= value << 15;
|
||||
value = temperature_night + 22;
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 63)
|
||||
value = 63;
|
||||
weather |= value << 16;
|
||||
if ((dataset / 60) < 7)
|
||||
display_weather_temperature("Temperature (night): ", weather);
|
||||
}
|
||||
/* show weather and temperature of of region 60..89 */
|
||||
if ((dataset / 60) == 7) {
|
||||
printf("Dataset: %s\n", 60 + datasets_60_89[(dataset % 60) / 30]);
|
||||
value = 60 + (dataset % 30);
|
||||
printf("Region: %d = %s\n", value, region[value]);
|
||||
printf("Weather (day): %s = %s\n", show_bits(weather_day, 4), weathers_day[weather_day]);
|
||||
printf("Weather (night): %s = %s\n", show_bits(weather_night, 4), weathers_night[weather_night]);
|
||||
display_weather_temperature("Temperature: ", weather);
|
||||
}
|
||||
|
||||
/* the magic '10' bit string */
|
||||
weather |= 0x1 << 22;
|
||||
|
||||
/* encode */
|
||||
return weather_encode(weather, key);
|
||||
}
|
||||
|
||||
/* transmit chunk of weather data for each minute */
|
||||
static uint16_t tx_weather(dcf77_tx_t *tx, time_t timestamp, int minute, int hour, int zone)
|
||||
{
|
||||
int index = (minute + 2) % 3;
|
||||
int utc_hour;
|
||||
uint16_t chunk;
|
||||
|
||||
if (index == 0) {
|
||||
/* convert hour to UTC */
|
||||
utc_hour = hour - 1;
|
||||
if (zone & 1)
|
||||
utc_hour--;
|
||||
if (utc_hour < 0)
|
||||
utc_hour += 24;
|
||||
/* in index 0 we transmit minute + 1 (next minute), so we substract 1 */
|
||||
tx->weather_cipher = generate_weather(timestamp, (minute + 59) % 60, utc_hour, tx->weather_day, tx->weather_night, tx->extreme, tx->rain, tx->wind_dir, tx->wind_bft, tx->temperature_day, tx->temperature_night);
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Transmitting first chunk of weather info.\n");
|
||||
chunk = (tx->weather_cipher & 0x3f) << 1; /* bit 2-7 */
|
||||
chunk |= (tx->weather_cipher & 0x0fc0) << 2; /* bit 9-14 */
|
||||
tx->weather_cipher >>= 12;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Transmitting %s chunk of weather info.\n", (index == 1) ? "second" : "third");
|
||||
chunk = tx->weather_cipher & 0x3fff;
|
||||
tx->weather_cipher >>= 14;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/* set inital time stamp at the moment the stream starts */
|
||||
void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp, double sub_sec)
|
||||
{
|
||||
dcf77_tx_t *tx = &dcf77->tx;
|
||||
|
||||
/* current second within minute */
|
||||
tx->second = t % 60;
|
||||
tx->second = timestamp % 60;
|
||||
/* time stamp of next minute */
|
||||
tx->timestamp = t - tx->second + 60;
|
||||
tx->timestamp = timestamp - tx->second + 60;
|
||||
/* wave within current second */
|
||||
tx->wave = floor(fmod(now, 1.0) * (double)tx->waves_sec);
|
||||
tx->wave = sub_sec * (double)tx->waves_sec;
|
||||
/* silence until next second begins */
|
||||
tx->symbol = 'm'; tx->level = 0;
|
||||
}
|
||||
|
||||
/* transmit one symbol = one second */
|
||||
static char tx_symbol(dcf77_t *dcf77, time_t timestamp, int second)
|
||||
{
|
||||
dcf77_tx_t *tx = &dcf77->tx;
|
||||
char symbol;
|
||||
int i, j;
|
||||
|
||||
/* generate frame */
|
||||
if (second == 0 || !tx->data_frame) {
|
||||
|
@ -205,18 +735,19 @@ static char tx_symbol(dcf77_t *dcf77, time_t timestamp, int second)
|
|||
int isdst_next_hour, wday, zone;
|
||||
uint64_t frame = 0, p;
|
||||
|
||||
/* get DST next hour */
|
||||
timestamp += 3600;
|
||||
tm = localtime(×tamp);
|
||||
timestamp -= 3600;
|
||||
if (!tm) {
|
||||
error_tm:
|
||||
PDEBUG(DDCF77, DEBUG_ERROR, "Failed to get local time of time stamp!\n");
|
||||
return 'm';
|
||||
}
|
||||
isdst_next_hour = tm->tm_isdst;
|
||||
tm = localtime(×tamp);
|
||||
if (!tm)
|
||||
goto error_tm;
|
||||
|
||||
/* get weather data */
|
||||
if (tx->weather) {
|
||||
frame |= tx_weather(tx, timestamp, tm->tm_min, tm->tm_hour, (tm->tm_isdst > 0) ? 1 : 2) << 1;
|
||||
/* calling tx_weather() destroys tm, because it is a pointer to a global variable. now we fix it */
|
||||
tm = localtime(×tamp);
|
||||
}
|
||||
|
||||
if (tm->tm_wday > 0)
|
||||
wday = tm->tm_wday;
|
||||
|
@ -228,7 +759,7 @@ error_tm:
|
|||
else
|
||||
zone = 2;
|
||||
|
||||
PDEBUG(DDCF77, DEBUG_NOTICE, "The time transmitting: %s %s %d %02d:%02d:00 %s %02d\n", week_day[wday], month_name[tm->tm_mon + 1], tm->tm_mday, tm->tm_hour, tm->tm_min, time_zone[zone], tm->tm_year + 1900);
|
||||
PDEBUG(DDCF77, DEBUG_NOTICE, "The time transmitting: %s %s %d %02d:%02d:%02d %s %02d\n", week_day[wday], month_name[tm->tm_mon + 1], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, time_zone[zone], tm->tm_year + 1900);
|
||||
|
||||
if ((tm->tm_isdst > 0) != (isdst_next_hour > 0))
|
||||
frame |= (uint64_t)1 << 16;
|
||||
|
@ -270,19 +801,40 @@ error_tm:
|
|||
frame |= (uint64_t)(p & 1) << 58;
|
||||
|
||||
tx->data_frame = frame;
|
||||
|
||||
for (i = 0, j = 0; i < 59; i++) {
|
||||
if (i == 1 || i == 15 || i == 21 || i == 29 || i == 36 || i == 42 || i == 45 || i == 50)
|
||||
tx->data_string[j++] = ' ';
|
||||
tx->data_string[j++] = '0' + ((frame >> i) & 1);
|
||||
}
|
||||
tx->data_string[j] = '\0';
|
||||
PDEBUG(DDSP, DEBUG_INFO, "Start transmission of frame:\n");
|
||||
PDEBUG(DDSP, DEBUG_INFO, "0 Wetterdaten Info 1 Minute P StundeP Tag WoT Monat Jahr P\n");
|
||||
PDEBUG(DDSP, DEBUG_INFO, "%s\n", tx->data_string);
|
||||
}
|
||||
|
||||
if (second == 59)
|
||||
symbol = 'm';
|
||||
else symbol = ((tx->data_frame >> second) & 1) + '0';
|
||||
else
|
||||
symbol = ((tx->data_frame >> second) & 1) + '0';
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Trasmitting symbol '%c' (Bit %d)\n", symbol, second);
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
static void rx_symbol(dcf77_t *dcf77, char symbol);
|
||||
|
||||
/* encode one minute */
|
||||
void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length)
|
||||
{
|
||||
#ifdef DEBUG_LOOP
|
||||
/* mute and speed up by factor 20 */
|
||||
memset(samples, 0, sizeof(*samples) * length);
|
||||
sample_t test_sample[length * 20];
|
||||
samples = test_sample;
|
||||
length *= 20;
|
||||
#endif
|
||||
dcf77_tx_t *tx = &dcf77->tx;
|
||||
double carrier_phase, test_phase;
|
||||
int i;
|
||||
|
@ -310,6 +862,9 @@ void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length)
|
|||
tx->timestamp += 60;
|
||||
}
|
||||
tx->symbol = tx_symbol(dcf77, tx->timestamp, tx->second);
|
||||
#ifdef DEBUG_LOOP
|
||||
rx_symbol(dcf77, tx->symbol);
|
||||
#endif
|
||||
}
|
||||
switch (tx->symbol) {
|
||||
case '0':
|
||||
|
@ -345,7 +900,146 @@ void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length)
|
|||
tx->test_phase = test_phase;
|
||||
}
|
||||
|
||||
static void rx_frame(uint64_t frame)
|
||||
/*
|
||||
* RX part
|
||||
*/
|
||||
|
||||
/* display weather data from weather frame */
|
||||
static void display_weather(uint32_t weather, int minute, int utc_hour)
|
||||
{
|
||||
int dataset = ((utc_hour + 2) % 24) * 20 + (minute / 3); /* data sets since 22:00 UTC */
|
||||
int value;
|
||||
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Decoding weather for dataset %d/480\n", dataset);
|
||||
printf("Received Weather INFO\n");
|
||||
printf("---------------------\n");
|
||||
printf("Time (UTC): %02d:%02d\n", utc_hour, minute);
|
||||
printf("Dataset: %s\n", datasets_0_59[dataset / 60]);
|
||||
value = dataset % 60;
|
||||
printf("Region: %d = %s\n", value, region[value]);
|
||||
if ((dataset / 60) < 7) {
|
||||
value = (weather >> 0) & 0xf;
|
||||
printf("Weather (day): %s = %s\n", show_bits(value, 4), weathers_day[value]);
|
||||
value = (weather >> 4) & 0xf;
|
||||
printf("Weather (night): %s = %s\n", show_bits(value, 4), weathers_night[value]);
|
||||
}
|
||||
if (((dataset / 60) & 1) == 0) {
|
||||
/* even datasets, this is 'Day' data */
|
||||
if (((weather >> 15) & 1) == 0) {
|
||||
value = (weather >> 8) & 0xf;
|
||||
printf("Extreme weather: %s = %s\n", show_bits(value, 4), extremeweathers[value]);
|
||||
} else {
|
||||
value = (weather >> 8) & 0x3;
|
||||
printf("Relative weather: %s = %s\n", show_bits(value, 2), anomaly1[value]);
|
||||
value = (weather >> 10) & 0x3;
|
||||
printf("Sunshine: %s = %s\n", show_bits(value, 2), anomaly1[value]);
|
||||
}
|
||||
value = (weather >> 12) & 0x7;
|
||||
printf("Rain Probability: %s = %s\n", show_bits(value, 3), probabilities[value]);
|
||||
value = (weather >> 15) & 0x1;
|
||||
printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]);
|
||||
display_weather_temperature("Temperature (day): ", weather);
|
||||
} else {
|
||||
/* odd datasets, this is 'Night' data */
|
||||
if (((weather >> 15) & 1) == 0) {
|
||||
value = (weather >> 8) & 0xf;
|
||||
printf("Wind direction: %s = %s\n", show_bits(value, 4), winddirections[value]);
|
||||
} else {
|
||||
value = (weather >> 8) & 0x3;
|
||||
printf("Relative weather: %s = %s\n", show_bits(value, 2), anomaly1[value]);
|
||||
value = (weather >> 10) & 0x3;
|
||||
printf("Sunshine: %s = %s\n", show_bits(value, 2), anomaly1[value]);
|
||||
}
|
||||
value = (weather >> 12) & 0x7;
|
||||
printf("Wind strength: %s = %s\n", show_bits(value, 3), windstrengths[value]);
|
||||
value = (weather >> 15) & 0x1;
|
||||
printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]);
|
||||
if ((dataset / 60) < 7)
|
||||
display_weather_temperature("Temperature (night): ", weather);
|
||||
}
|
||||
if ((dataset / 60) == 7) {
|
||||
printf("Dataset: %s\n", 60 + datasets_60_89[(dataset % 60) / 30]);
|
||||
value = 60 + (dataset % 30);
|
||||
printf("Region: %d = %s\n", value, region[value]);
|
||||
value = (weather >> 0) & 0xf;
|
||||
printf("Weather (day): %s = %s\n", show_bits(value, 4), weathers_day[value]);
|
||||
value = (weather >> 4) & 0xf;
|
||||
printf("Weather (night): %s = %s\n", show_bits(value, 4), weathers_night[value]);
|
||||
display_weather_temperature("Temperature: ", weather);
|
||||
}
|
||||
}
|
||||
|
||||
/* reset weather frame */
|
||||
static void rx_weather_reset(dcf77_rx_t *rx)
|
||||
{
|
||||
rx->weather_index = 0;
|
||||
rx->weather_cipher = 0;
|
||||
rx->weather_key = 0;
|
||||
}
|
||||
|
||||
/* receive weather chunk */
|
||||
static void rx_weather(dcf77_rx_t *rx, int minute, int hour, int zone, uint64_t frame)
|
||||
{
|
||||
int index = (minute + 2) % 3;
|
||||
int utc_hour;
|
||||
int32_t weather;
|
||||
|
||||
if (rx->weather_index == 0 && index != 0) {
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Skipping weather info chunk, waiting for new start of weather info.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
rx_weather_reset(rx);
|
||||
rx->weather_cipher |= (frame >> 2) & 0x3f; /* bit 2-7 */
|
||||
rx->weather_cipher |= (frame >> 3) & 0x0fc0; /* bit 9-14 */
|
||||
rx->weather_index++;
|
||||
if (((frame & 0x0002)) || ((frame & 0x0100)) || !rx->weather_cipher) {
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "There is no weather info in this received minute.\n");
|
||||
rx_weather_reset(rx);
|
||||
return;
|
||||
}
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Got first chunk of weather info.\n");
|
||||
return;
|
||||
}
|
||||
if (rx->weather_index == 1 && index == 1) {
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Got second chunk of weather info.\n");
|
||||
rx->weather_cipher |= (frame << 11) & 0x3fff000; /* bit 1-14 */
|
||||
rx->weather_key |= (frame >> 21) & 0x7f;
|
||||
rx->weather_key |= ((frame >> 29) & 0x3f) << 8;
|
||||
rx->weather_key |= ((frame >> 36) & 0x3f) << 16;
|
||||
rx->weather_key |= ((frame >> 45) & 0x1f) << 24;
|
||||
rx->weather_key |= ((frame >> 42) & 0x07) << 29;
|
||||
rx->weather_key |= ((frame >> 50) & 0xff) << 32;
|
||||
rx->weather_index++;
|
||||
return;
|
||||
}
|
||||
if (rx->weather_index == 2 && index == 2) {
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Got third chunk of weather info.\n");
|
||||
rx->weather_cipher |= (frame << 25) & 0xfffc000000; /* bit 1-14 */
|
||||
weather = weather_decode(rx->weather_cipher, rx->weather_key);
|
||||
if (weather < 0)
|
||||
PDEBUG(DFRAME, DEBUG_NOTICE, "Failed to decrypt weather info, checksum error.\n");
|
||||
else {
|
||||
/* convert hour to UTC */
|
||||
utc_hour = hour - 1;
|
||||
if (zone & 1)
|
||||
utc_hour--;
|
||||
if (utc_hour < 0)
|
||||
utc_hour += 24;
|
||||
/* in index 2 we transmit minute + 3 (next minute), so we substract 3 */
|
||||
display_weather(weather, (minute + 57) % 60, utc_hour);
|
||||
}
|
||||
rx_weather_reset(rx);
|
||||
return;
|
||||
}
|
||||
|
||||
rx_weather_reset(rx);
|
||||
PDEBUG(DFRAME, DEBUG_INFO, "Got weather info chunk out of order, waiting for new start of weather info.\n");
|
||||
}
|
||||
|
||||
/* decode time from received data */
|
||||
static void rx_frame(dcf77_rx_t *rx, uint64_t frame)
|
||||
{
|
||||
int zone;
|
||||
int minute_one, minute_ten, minute = -1;
|
||||
|
@ -428,15 +1122,34 @@ static void rx_frame(uint64_t frame)
|
|||
PDEBUG(DFRAME, DEBUG_INFO, "Year : %02d\n", year);
|
||||
}
|
||||
|
||||
if (minute >= 0 && hour >= 0 && day >= 0 && wday >= 0 && month >= 0 && year >= 0)
|
||||
if (minute >= 0 && hour >= 0 && day >= 0 && wday >= 0 && month >= 0 && year >= 0) {
|
||||
PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is: %s %s %d %02d:%02d:00 %s 20%02d\n", week_day[wday], month_name[month], day, hour, minute, time_zone[zone], year);
|
||||
else
|
||||
rx_weather(rx, minute, hour, zone, frame);
|
||||
} else {
|
||||
PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is invalid!\n");
|
||||
rx_weather_reset(rx);
|
||||
}
|
||||
}
|
||||
|
||||
/* test routing for test data */
|
||||
void rx_frame_test(dcf77_t *dcf77, const char *string)
|
||||
{
|
||||
uint64_t frame = 0;
|
||||
int i;
|
||||
|
||||
puts(string);
|
||||
for (i = 0; i < 59; i++) {
|
||||
frame |= (uint64_t)(string[i] & 1) << i;
|
||||
}
|
||||
|
||||
rx_frame(&dcf77->rx, frame);
|
||||
}
|
||||
|
||||
/* receive one symbol = one second */
|
||||
static void rx_symbol(dcf77_t *dcf77, char symbol)
|
||||
{
|
||||
dcf77_rx_t *rx = &dcf77->rx;
|
||||
double second = -NAN;
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Received symbol '%c'\n", symbol);
|
||||
|
||||
|
@ -445,33 +1158,48 @@ static void rx_symbol(dcf77_t *dcf77, char symbol)
|
|||
PDEBUG(DDSP, DEBUG_INFO, "Reception of frame has started\n");
|
||||
rx->data_receive = 1;
|
||||
rx->data_index = 0;
|
||||
rx->string_index = 0;
|
||||
second = 0;
|
||||
}
|
||||
} else {
|
||||
if (symbol == 'm') {
|
||||
if (rx->data_index == 59) {
|
||||
rx->data_string[rx->data_index] = '\0';
|
||||
rx->data_string[rx->string_index] = '\0';
|
||||
rx->data_index = 0;
|
||||
PDEBUG(DDSP, DEBUG_INFO, "Received complete frame: %s (0x%016" PRIx64 ")\n", rx->data_string, rx->data_frame);
|
||||
rx_frame(rx->data_frame);
|
||||
rx->string_index = 0;
|
||||
PDEBUG(DDSP, DEBUG_INFO, "Received complete frame:\n");
|
||||
PDEBUG(DDSP, DEBUG_INFO, "0 Wetterdaten Info 1 Minute P StundeP Tag WoT Monat Jahr P\n");
|
||||
PDEBUG(DDSP, DEBUG_INFO, "%s\n", rx->data_string);
|
||||
rx_frame(rx, rx->data_frame);
|
||||
second = 0;
|
||||
} else {
|
||||
PDEBUG(DDSP, DEBUG_INFO, "Short read, frame too short\n");
|
||||
rx->data_index = 0;
|
||||
rx->string_index = 0;
|
||||
rx_weather_reset(rx);
|
||||
}
|
||||
} else {
|
||||
if (rx->data_index == 59) {
|
||||
PDEBUG(DDSP, DEBUG_INFO, "Long read, frame too long\n");
|
||||
rx->data_receive = 0;
|
||||
rx_weather_reset(rx);
|
||||
} else {
|
||||
rx->data_string[rx->data_index++] = symbol;
|
||||
if (rx->data_index == 1 || rx->data_index == 15 || rx->data_index == 21 || rx->data_index == 29 || rx->data_index == 36 || rx->data_index == 42 || rx->data_index == 45 || rx->data_index == 50)
|
||||
rx->data_string[rx->string_index++] = ' ';
|
||||
rx->data_string[rx->string_index++] = symbol;
|
||||
rx->data_index++;
|
||||
rx->data_frame >>= 1;
|
||||
rx->data_frame |= (uint64_t)(symbol & 1) << 58;
|
||||
second = rx->data_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
display_measurements_update(dcf77->dmp_current_second, second, 0.0);
|
||||
}
|
||||
|
||||
//#define DEBUG_SAMPLE
|
||||
|
||||
/* decode radio wave and extract each bit / second */
|
||||
void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length)
|
||||
{
|
||||
dcf77_rx_t *rx = &dcf77->rx;
|
||||
|
@ -481,6 +1209,9 @@ void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length)
|
|||
|
||||
display_wave(&dcf77->dispwav, samples, length, 1.0);
|
||||
|
||||
#ifdef DEBUG_LOOP
|
||||
return;
|
||||
#endif
|
||||
if (!rx->enable)
|
||||
return;
|
||||
|
||||
|
|
|
@ -15,7 +15,18 @@ typedef struct dcf77_tx {
|
|||
int second;
|
||||
char symbol;
|
||||
uint64_t data_frame;
|
||||
char data_string[100]; /* 60 digits + spaces + '\0' */
|
||||
int test_tone;
|
||||
int weather;
|
||||
int weather_day;
|
||||
int weather_night;
|
||||
int extreme;
|
||||
int rain;
|
||||
int wind_dir;
|
||||
int wind_bft;
|
||||
int temperature_day;
|
||||
int temperature_night;
|
||||
uint64_t weather_cipher;
|
||||
} dcf77_tx_t;
|
||||
|
||||
typedef struct dcf77_rx {
|
||||
|
@ -29,9 +40,12 @@ typedef struct dcf77_rx {
|
|||
int clock_count;
|
||||
double value_level, value_short, value_long; /* measured values */
|
||||
int data_receive, data_index;
|
||||
char data_string[60]; /* 59 digits + '\0' */
|
||||
char data_string[100]; /* 60 digits + spaces + '\0' */
|
||||
int string_index;
|
||||
uint64_t data_frame;
|
||||
iir_filter_t clock_lp[2]; /* filters received carrier signal */
|
||||
int weather_index;
|
||||
uint64_t weather_cipher;
|
||||
uint64_t weather_key;
|
||||
} dcf77_rx_t;
|
||||
|
||||
typedef struct dcf77 {
|
||||
|
@ -43,6 +57,7 @@ typedef struct dcf77 {
|
|||
dispmeasparam_t *dmp_input_level;
|
||||
dispmeasparam_t *dmp_signal_level;
|
||||
dispmeasparam_t *dmp_signal_quality;
|
||||
dispmeasparam_t *dmp_current_second;
|
||||
|
||||
/* wave */
|
||||
dispwav_t dispwav; /* display wave form */
|
||||
|
@ -52,6 +67,10 @@ int dcf77_init(int _fast_math);
|
|||
void dcf77_exit(void);
|
||||
dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone);
|
||||
void dcf77_destroy(dcf77_t *dcf77);
|
||||
void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp);
|
||||
void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp, double sub_sec);
|
||||
void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length);
|
||||
void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length);
|
||||
|
||||
void list_weather(void);
|
||||
time_t dcf77_start_weather(time_t timestamp, int region, int offset);
|
||||
void dcf77_set_weather(dcf77_t *dcf77, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
|
||||
const char *aaimage[] = {
|
||||
"",
|
||||
"@W * DCF77 with weather info *",
|
||||
"",
|
||||
" @Y\\__/",
|
||||
" @w_______@Y/ \\__",
|
||||
" @w/ ____\\_@Y/@w_",
|
||||
" \\___/@r10:08:33@w\\",
|
||||
" @B/ @w\\________/",
|
||||
" @B/ @W*@B/ @W*@B/ @W*@B/ @W*@B/",
|
||||
" @W*@B/ @W*@B/ @W*@B/ @W*@B/",
|
||||
"",
|
||||
NULL
|
||||
};
|
153
src/dcf77/main.c
153
src/dcf77/main.c
|
@ -27,11 +27,14 @@
|
|||
#include <termios.h>
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libsound/sound.h"
|
||||
#include "../libaaimage/aaimage.h"
|
||||
#include "dcf77.h"
|
||||
#include "cities.h"
|
||||
|
||||
int num_kanal = 1;
|
||||
dcf77_t *dcf77 = NULL;
|
||||
|
@ -40,7 +43,17 @@ static const char *dsp_device = "";
|
|||
static int dsp_samplerate = 192000;
|
||||
static int dsp_buffer = 50;
|
||||
static int rx = 0, tx = 0;
|
||||
static time_t timestamp = -1;
|
||||
static double timestamp = -1;
|
||||
static int weather = 0;
|
||||
static int weather_day;
|
||||
static int weather_night;
|
||||
static int extreme;
|
||||
static int rain;
|
||||
static int wind_dir;
|
||||
static int wind_bft;
|
||||
static int temperature_day;
|
||||
static int temperature_night;
|
||||
static int region = -1, region_advance;
|
||||
static int double_amplitude = 0;
|
||||
static int test_tone = 0;
|
||||
static int dsp_interval = 1; /* ms */
|
||||
|
@ -110,8 +123,6 @@ static time_t feierabend_time()
|
|||
|
||||
t = get_time();
|
||||
tm = localtime(&t);
|
||||
if (!tm)
|
||||
return -1;
|
||||
|
||||
tm->tm_hour = 17;
|
||||
tm->tm_min = 0;
|
||||
|
@ -148,7 +159,7 @@ void print_help(void)
|
|||
printf(" -R --rx\n");
|
||||
printf(" Receive time signal\n");
|
||||
printf(" -F --fake\n");
|
||||
printf(" Use given time stamp: <year> <month> <day> <hour> <min< <sec>.\n");
|
||||
printf(" Use given time stamp: <year> <month> <day> <hour> <min> <sec>.\n");
|
||||
printf(" All values have to be numerical. The year must have 4 digits.\n");
|
||||
printf(" --feierabend\n");
|
||||
printf(" --end-of-working-day\n");
|
||||
|
@ -156,6 +167,29 @@ void print_help(void)
|
|||
printf(" --geburtstag\n");
|
||||
printf(" --birthday\n");
|
||||
printf(" Use fake time stamp that equals birth of the author.\n");
|
||||
printf(" -W --weather <weather> <extreme> <rain> <wind dir> <wind bft> <temperature>\n");
|
||||
printf(" Send these weather info for all regions / all days.\n");
|
||||
printf(" See -L for infos on values.\n");
|
||||
printf(" weather = 1..15 for common day and night weather.\n");
|
||||
printf(" weather = 1..15,1..15 for specific day and night weather.\n");
|
||||
printf(" extreme = 0..15 for extreme weather conditions.\n");
|
||||
printf(" rain = 0..100 for rain/show probability. (closest is used)\n");
|
||||
printf(" wind dir = N | NE | E | SE | S | SW | W | NW | 0 for wind direction.\n");
|
||||
printf(" wind bft = <bft> for wind speed in bft. (closest is used)\n");
|
||||
printf(" temerature = <celsius> for common min and max temperature.\n");
|
||||
printf(" temerature = <celsius>,<celsius> for specific min and max temperature.\n");
|
||||
printf(" --beach-party\n");
|
||||
printf(" Beach weather, equivalent to -W 1 0 0 0 2 35,20\n");
|
||||
printf(" --santa-claus\n");
|
||||
printf(" --muenster-2005\n");
|
||||
printf(" Deep snow, equivalent to -W 7 1 100 E 3 1,-1\n");
|
||||
printf(" -A --at-region <region> <advance minutes>\n");
|
||||
printf(" Alter time, so that the weather of the given region is transmitted.\n");
|
||||
printf(" To allow the receiver to sync, give time to advance in minutes.\n");
|
||||
printf(" -L --list\n");
|
||||
printf(" List all regions / weather values.\n");
|
||||
printf(" -C --city <name fragment>\n");
|
||||
printf(" Search for city (case insensitive) and display its region code.\n");
|
||||
printf(" -D --double-amplitude\n");
|
||||
printf(" Transmit with double amplitude by using differential stereo output.\n");
|
||||
printf(" --test-tone\n");
|
||||
|
@ -173,8 +207,11 @@ void print_help(void)
|
|||
#define OPT_F2 1002
|
||||
#define OPT_G1 1003
|
||||
#define OPT_G2 1004
|
||||
#define OPT_TEST_TONE 1005
|
||||
#define OPT_FAST_MATH 1006
|
||||
#define OPT_BEACH 1005
|
||||
#define OPT_SANTA 1006
|
||||
#define OPT_MUENSTER 1007
|
||||
#define OPT_TEST_TONE 1008
|
||||
#define OPT_FAST_MATH 1009
|
||||
|
||||
static void add_options(void)
|
||||
{
|
||||
|
@ -190,15 +227,25 @@ static void add_options(void)
|
|||
option_add(OPT_F2, "end-of-working-day", 0);
|
||||
option_add(OPT_G1, "geburtstag", 0);
|
||||
option_add(OPT_G2, "birthday", 0);
|
||||
option_add(OPT_TEST_TONE, "test-tone", 0);
|
||||
option_add('W', "weather", 6);
|
||||
option_add(OPT_BEACH, "beach-party", 0);
|
||||
option_add(OPT_SANTA, "santa-claus", 0);
|
||||
option_add(OPT_MUENSTER, "muenster-2005", 0);
|
||||
option_add('A', "at-region", 2);
|
||||
option_add('L', "list", 0);
|
||||
option_add('C', "city", 1);
|
||||
option_add('D', "double-amplitude", 0);
|
||||
option_add(OPT_TEST_TONE, "test-tone", 0);
|
||||
option_add('r', "realtime", 1);
|
||||
option_add(OPT_FAST_MATH, "fast-math", 0);
|
||||
}
|
||||
|
||||
static const char *wind_dirs[8] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
|
||||
|
||||
static int handle_options(int short_option, int argi, char **argv)
|
||||
{
|
||||
int rc;
|
||||
char *string, *string1;
|
||||
int rc, i;
|
||||
|
||||
switch (short_option) {
|
||||
case 'h':
|
||||
|
@ -233,7 +280,6 @@ static int handle_options(int short_option, int argi, char **argv)
|
|||
break;
|
||||
case 'F':
|
||||
timestamp = parse_time(argv + argi);
|
||||
printf("%ld\n",timestamp);
|
||||
if (timestamp < 0) {
|
||||
fprintf(stderr, "Given time stamp is invalid, please use -h for help.\n");
|
||||
return -EINVAL;
|
||||
|
@ -247,6 +293,79 @@ static int handle_options(int short_option, int argi, char **argv)
|
|||
case OPT_G2:
|
||||
timestamp = 115099200 - 70;
|
||||
break;
|
||||
case 'W':
|
||||
if (weather) {
|
||||
no_multiple_weathers:
|
||||
fprintf(stderr, "You cannot define more than one weather situation.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
weather = 1;
|
||||
string = options_strdup(argv[argi++]);
|
||||
string1 = strsep(&string, ",");
|
||||
weather_day = atoi(string1);
|
||||
if (string)
|
||||
weather_night = atoi(string);
|
||||
else
|
||||
weather_night = weather_day;
|
||||
extreme = atoi(argv[argi++]);
|
||||
rain = atoi(argv[argi++]);
|
||||
/* if wind is not found, wind 8 (changable) is selected */
|
||||
string = options_strdup(argv[argi++]);
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (!strcasecmp(string, wind_dirs[i]))
|
||||
break;
|
||||
}
|
||||
wind_dir = i;
|
||||
wind_bft = atoi(argv[argi++]);
|
||||
string = options_strdup(argv[argi++]);
|
||||
string1 = strsep(&string, ",");
|
||||
temperature_day = atoi(string1);
|
||||
if (string)
|
||||
temperature_night = atoi(string);
|
||||
else
|
||||
temperature_night = temperature_day;
|
||||
break;
|
||||
case OPT_BEACH:
|
||||
if (weather)
|
||||
goto no_multiple_weathers;
|
||||
weather = 1;
|
||||
weather_day = 1;
|
||||
weather_night = 1;
|
||||
extreme = 0;
|
||||
rain = 0;
|
||||
wind_dir = 8;
|
||||
wind_bft = 2;
|
||||
temperature_day = 35;
|
||||
temperature_night = 20; /* tropical night >= 20 */
|
||||
break;
|
||||
case OPT_SANTA:
|
||||
case OPT_MUENSTER:
|
||||
if (weather)
|
||||
goto no_multiple_weathers;
|
||||
weather = 1;
|
||||
weather_day = 7;
|
||||
weather_night = 7;
|
||||
extreme = 0;
|
||||
rain = 100;
|
||||
wind_dir = 6;
|
||||
wind_bft = 3;
|
||||
temperature_day = 1;
|
||||
temperature_night = -1; /* freezing a little */
|
||||
break;
|
||||
case 'A':
|
||||
region = atoi(argv[argi++]);
|
||||
if (region < 0 || region > 89) {
|
||||
fprintf(stderr, "Given region number is is invalid, please use -L for list of valid regions.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
region_advance = atoi(argv[argi++]);
|
||||
break;
|
||||
case 'L':
|
||||
list_weather();
|
||||
return 0;
|
||||
case 'C':
|
||||
display_city(argv[argi++]);
|
||||
return 0;
|
||||
case OPT_TEST_TONE:
|
||||
test_tone = 1;
|
||||
break;
|
||||
|
@ -407,8 +526,20 @@ int main(int argc, char *argv[])
|
|||
fprintf(stderr, "Failed to create \"DCF77\" instance. Quitting!\n");
|
||||
goto error;
|
||||
}
|
||||
if (weather)
|
||||
dcf77_set_weather(dcf77, weather_day, weather_night, extreme, rain, wind_dir, wind_bft, temperature_day, temperature_night);
|
||||
|
||||
printf("\n");
|
||||
/* no time stamp given, so we use our clock */
|
||||
if (tx) {
|
||||
if (timestamp < 0 && region < 0)
|
||||
printf("No alternative time given, so you might not notice the difference between our transmission and the real DCF77 transmission.\n");
|
||||
if (timestamp < 0)
|
||||
timestamp = get_time();
|
||||
if (region >= 0 && weather)
|
||||
timestamp = dcf77_start_weather((time_t)timestamp, region, region_advance);
|
||||
}
|
||||
|
||||
print_aaimage();
|
||||
printf("DCF77 ready.\n");
|
||||
|
||||
/* prepare terminal */
|
||||
|
@ -437,7 +568,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
soundif_start();
|
||||
if (tx)
|
||||
dcf77_tx_start(dcf77, timestamp);
|
||||
dcf77_tx_start(dcf77, (time_t)timestamp, fmod(timestamp, 1.0));
|
||||
|
||||
while (!quit) {
|
||||
int w;
|
||||
|
|
|
@ -0,0 +1,582 @@
|
|||
|
||||
/* based on code found at:
|
||||
https://github.com/FroggySoft/AlarmClock/blob/master/dcf77.cpp
|
||||
https://github.com/tobozo/esp32-dcf77-weatherman/blob/master/dcf77.cpp
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <endian.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "weather.h"
|
||||
|
||||
/// Container zum Konvertieren zwischen 4 Bytes und Uint.
|
||||
union ByteUInt {
|
||||
struct {
|
||||
# if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
uint8_t Byte0;
|
||||
uint8_t Byte1;
|
||||
uint8_t Byte2;
|
||||
uint8_t Byte3;
|
||||
# elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint8_t Byte3;
|
||||
uint8_t Byte2;
|
||||
uint8_t Byte1;
|
||||
uint8_t Byte0;
|
||||
# else
|
||||
#error unsupported bitorder, please fix
|
||||
# endif
|
||||
} s;
|
||||
uint32_t FullUint;
|
||||
};
|
||||
|
||||
/// bit pattern for 0D,0E from 0B-0D
|
||||
static const uint32_t mUintArrBitPattern12[12] = {
|
||||
0x80000, //0b10000000000000000000 / 0D.3
|
||||
0x00010, //0b00000000000000010000 / 0B.4
|
||||
0x00008, //0b00000000000000001000 / 0B.3
|
||||
0x00100, //0b00000000000100000000 / 0C.0
|
||||
0x00080, //0b00000000000010000000 / 0B.7
|
||||
0x01000, //0b00000001000000000000 / 0C.4
|
||||
0x00800, //0b00000000100000000000 / 0C.3
|
||||
0x10000, //0b00010000000000000000 / 0D.0
|
||||
0x08000, //0b00001000000000000000 / 0C.7
|
||||
0x00001, //0b00000000000000000001 / 0B.0
|
||||
0x00000, //0b00000000000000000000 / xxxx
|
||||
0x00000 //0b00000000000000000000 / xxxx
|
||||
};
|
||||
|
||||
/// 12-15 from 16-19 (time)
|
||||
static const uint32_t mUintArrBitPattern30_1[30] = {
|
||||
0x00000200, //0b00000000000000000000001000000000 / 17.1
|
||||
0x00000020, //0b00000000000000000000000000100000 / 16.5
|
||||
0x02000000, //0b00000010000000000000000000000000 / 19.1
|
||||
0x00000000, //0b00000000000000000000000000000000 / 1A.3
|
||||
0x00000000, //0b00000000000000000000000000000000 / 1A.5
|
||||
0x00000080, //0b00000000000000000000000010000000 / 16.7
|
||||
0x40000000, //0b01000000000000000000000000000000 / 19.6
|
||||
0x01000000, //0b00000001000000000000000000000000 / 19.0
|
||||
|
||||
0x04000000, //0b00000100000000000000000000000000 / 19.2
|
||||
0x00000000, //0b00000000000000000000000000000000 / 1A.4
|
||||
0x00010000, //0b00000000000000010000000000000000 / 18.0
|
||||
0x00000000, //0b00000000000000000000000000000000 / 1A.2
|
||||
0x00400000, //0b00000000010000000000000000000000 / 18.6
|
||||
0x00000010, //0b00000000000000000000000000010000 / 16.4
|
||||
0x00200000, //0b00000000001000000000000000000000 / 18.5
|
||||
0x00080000, //0b00000000000010000000000000000000 / 18.3
|
||||
|
||||
0x00004000, //0b00000000000000000100000000000000 / 17.6
|
||||
0x00000000, //0b00000000000000000000000000000000 / 1A.6
|
||||
0x00020000, //0b00000000000000100000000000000000 / 18.1
|
||||
0x00100000, //0b00000000000100000000000000000000 / 18.4
|
||||
0x00008000, //0b00000000000000001000000000000000 / 17.7
|
||||
0x00000040, //0b00000000000000000000000001000000 / 16.6
|
||||
0x00001000, //0b00000000000000000001000000000000 / 17.4
|
||||
0x00000400, //0b00000000000000000000010000000000 / 17.2
|
||||
|
||||
0x00000001, //0b00000000000000000000000000000001 / 16.0
|
||||
0x80000000, //0b10000000000000000000000000000000 / 19.7
|
||||
0x00000008, //0b00000000000000000000000000001000 / 16.3
|
||||
0x00000002, //0b00000000000000000000000000000010 / 16.1
|
||||
0x00040000, //0b00000000000001000000000000000000 / 18.2
|
||||
0x10000000 //0b00010000000000000000000000000000 / 19.4
|
||||
};
|
||||
|
||||
/// bit pattern for 12-15 from 1A (time2)
|
||||
static const uint32_t mUintArrBitPattern30_2[30] = {
|
||||
0x00, //0b00000000, /* 17.1
|
||||
0x00, //0b00000000, /* 16.5
|
||||
0x00, //0b00000000, /* 19.1
|
||||
0x08, //0b00001000, /* 1A.3
|
||||
0x20, //0b00100000, /* 1A.5
|
||||
0x00, //0b00000000, /* 16.7
|
||||
0x00, //0b00000000, /* 19.6
|
||||
0x00, //0b00000000, /* 19.0
|
||||
|
||||
0x00, //0b00000000, /* 19.2
|
||||
0x10, //0b00010000, /* 1A.4
|
||||
0x00, //0b00000000, /* 18.0
|
||||
0x04, //0b00000100, /* 1A.2
|
||||
0x00, //0b00000000, /* 18.6
|
||||
0x00, //0b00000000, /* 16.4
|
||||
0x00, //0b00000000, /* 18.5
|
||||
0x00, //0b00000000, /* 18.3
|
||||
|
||||
0x00, //0b00000000, /* 17.6
|
||||
0x40, //0b01000000, /* 1A.6
|
||||
0x00, //0b00000000, /* 18.1
|
||||
0x00, //0b00000000, /* 18.4
|
||||
0x00, //0b00000000, /* 17.7
|
||||
0x00, //0b00000000, /* 16.6
|
||||
0x00, //0b00000000, /* 17.4
|
||||
0x00, //0b00000000, /* 17.2
|
||||
|
||||
0x00, //0b00000000, /* 16.0
|
||||
0x00, //0b00000000, /* 19.7
|
||||
0x00, //0b00000000, /* 16.3
|
||||
0x00, //0b00000000, /* 16.1
|
||||
0x00, //0b00000000, /* 18.2
|
||||
0x00 //0b00000000, /* 19.4
|
||||
};
|
||||
|
||||
/// 12-14 from 1C-1E (result from F)
|
||||
static const uint32_t mUintArrBitPattern20[20] = {
|
||||
0x000004, //0b000000000000000000000100 / 1C.2
|
||||
0x002000, //0b000000000010000000000000 / 1E.5
|
||||
0x008000, //0b000000001000000000000000 / 1E.7
|
||||
0x400000, //0b010000000000000000000000 / 1D.6
|
||||
0x000100, //0b000000000000000100000000 / 1E.0
|
||||
0x100000, //0b000100000000000000000000 / 1D.4
|
||||
0x000400, //0b000000000000010000000000 / 1E.2
|
||||
0x800000, //0b100000000000000000000000 / 1D.7
|
||||
|
||||
0x040000, //0b000001000000000000000000 / 1D.2
|
||||
0x020000, //0b000000100000000000000000 / 1D.1
|
||||
0x000008, //0b000000000000000000001000 / 1C.3
|
||||
0x000200, //0b000000000000001000000000 / 1E.1
|
||||
0x004000, //0b000000000100000000000000 / 1E.6
|
||||
0x000002, //0b000000000000000000000010 / 1C.1
|
||||
0x001000, //0b000000000001000000000000 / 1E.4
|
||||
0x080000, //0b000010000000000000000000 / 1D.3
|
||||
|
||||
0x000800, //0b000000000000100000000000 / 1E.3
|
||||
0x200000, //0b001000000000000000000000 / 1D.5
|
||||
0x010000, //0b000000010000000000000000 / 1D.0
|
||||
0x000001 //0b000000000000000000000001 / 1C.0
|
||||
};
|
||||
|
||||
/// bit pattern for 12-15 from 16-19 (1/3)
|
||||
static const uint64_t mByteArrLookupTable1C_1[8] = {
|
||||
0xBB0E22C573DFF76D, 0x90E9A1381C844A56,
|
||||
0x648D280BD1BA9352, 0x1CC5A7F0E97F364E,
|
||||
0xC1773DB3AAE00C6F, 0x1488F62BD2995E45,
|
||||
0x1F7096D3B30BFCEE, 0x8142CA34A5582967
|
||||
};
|
||||
|
||||
/// bit pattern for 12-15 from 16-19 (2/3)
|
||||
static const uint64_t mByteArrLookupTable1C_2[8] = {
|
||||
0xAB3DFC7465E60E4F, 0x9711D85983C2BA20,
|
||||
0xC51BD2584937017D, 0x93FAE02F66B4AC8E,
|
||||
0xB7CC43FF5866EB35, 0x822A99DD007114AE,
|
||||
0x4EB1F7701852AA9F, 0xD56BCC3D0483E926
|
||||
};
|
||||
|
||||
/// bit pattern for 12-15 from 16-19 (3/3)
|
||||
static const uint64_t mByteArrLookupTable1C_3[8] = {
|
||||
0x0A02000F06070D08, 0x030C0B050901040E,
|
||||
0x0209050D0C0E0F08, 0x06070B01000A0403,
|
||||
0x08000D0F010C0306, 0x0B0409050A07020E,
|
||||
0x030D000C09060F0B, 0x010E080A02070405
|
||||
};
|
||||
|
||||
/// Container, which contains all former global vars
|
||||
typedef struct DataContainer {
|
||||
/// Registers R12 to R15
|
||||
union ByteUInt mByteUint1;
|
||||
/// Registers R08 to R0A
|
||||
union ByteUInt mByteUint2;
|
||||
/// Registers R0B to R0E
|
||||
union ByteUInt mByteUint3;
|
||||
/// Registers R1C to R1E
|
||||
union ByteUInt mByteUint4;
|
||||
|
||||
uint8_t mByteUpperTime2;//, mByteR1B;
|
||||
uint32_t mUintLowerTime;
|
||||
} DataContainer_t;
|
||||
|
||||
static int32_t GetWeatherFromPlain(uint8_t *PlainBytes)
|
||||
{
|
||||
uint32_t result;
|
||||
uint32_t checkSum;
|
||||
|
||||
checkSum = PlainBytes[2] & 0x0f;
|
||||
checkSum <<= 8;
|
||||
checkSum |= PlainBytes[1];
|
||||
checkSum <<= 4;
|
||||
checkSum |= PlainBytes[0] >> 4;
|
||||
if (checkSum != 0x2501)
|
||||
return -1;
|
||||
|
||||
result = PlainBytes[0] & 0x0f;
|
||||
result <<= 8;
|
||||
result |= PlainBytes[4];
|
||||
result <<= 8;
|
||||
result |= PlainBytes[3];
|
||||
result <<= 4;
|
||||
result |= PlainBytes[2] >> 4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t *GetPlainFromWeather(uint32_t weather)
|
||||
{
|
||||
static uint8_t result[5];
|
||||
weather <<= 4;
|
||||
result[1] = 0x50;
|
||||
result[2] = (weather & 0xf0) | 0x02;
|
||||
weather >>= 8;
|
||||
result[3] = weather & 0xff;
|
||||
weather >>= 8;
|
||||
result[4] = weather & 0xff;
|
||||
weather >>= 8;
|
||||
result[0] = (weather & 0x0f) | 0x10;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CopyTimeToByteUint(uint8_t *data, uint8_t *key, DataContainer_t *container)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
container->mUintLowerTime <<= 8;
|
||||
container->mUintLowerTime |= key[3 - i];
|
||||
}
|
||||
container->mByteUpperTime2 = key[4];
|
||||
|
||||
// copy R
|
||||
container->mByteUint3.s.Byte0 = data[2];
|
||||
container->mByteUint3.s.Byte1 = data[3];
|
||||
container->mByteUint3.s.Byte2 = data[4];
|
||||
container->mByteUint3.FullUint >>= 4;
|
||||
|
||||
// copy L
|
||||
container->mByteUint2.s.Byte0 = data[0];
|
||||
container->mByteUint2.s.Byte1 = data[1];
|
||||
container->mByteUint2.s.Byte2 = (uint8_t)(data[2] & 0x0F);
|
||||
}
|
||||
|
||||
static void ShiftTimeRight(int round, DataContainer_t *container)
|
||||
{
|
||||
int count;
|
||||
uint8_t tmp;
|
||||
|
||||
if ((round == 16) || (round == 8) || (round == 7) || (round == 3))
|
||||
count = 2;
|
||||
else
|
||||
count = 1;
|
||||
|
||||
while (count-- != 0)
|
||||
{
|
||||
tmp = 0;
|
||||
if ((container->mUintLowerTime & 0x00100000) != 0) // save time bit 20
|
||||
tmp = 1;
|
||||
|
||||
container->mUintLowerTime &= 0xFFEFFFFF;
|
||||
if ((container->mUintLowerTime & 1) != 0)
|
||||
container->mUintLowerTime |= 0x00100000; // copy time bit 0 to time bit 19
|
||||
container->mUintLowerTime >>= 1; // time >>= 1
|
||||
|
||||
if ((container->mByteUpperTime2 & 1) != 0)
|
||||
container->mUintLowerTime |= 0x80000000;
|
||||
container->mByteUpperTime2 >>= 1;
|
||||
if (tmp != 0)
|
||||
container->mByteUpperTime2 |= 0x80; // insert time bit 20 to time bit 39
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void ShiftTimeLeft(int round, DataContainer_t *container)
|
||||
{
|
||||
int count;
|
||||
uint8_t tmp;
|
||||
|
||||
if ((round == 16) || (round == 8) || (round == 7) || (round == 3))
|
||||
count = 2;
|
||||
else
|
||||
count = 1;
|
||||
|
||||
while (count-- != 0)
|
||||
{
|
||||
tmp = 0;
|
||||
if ((container->mByteUpperTime2 & 0x80) != 0)
|
||||
tmp = 1;
|
||||
container->mByteUpperTime2 <<= 1;
|
||||
|
||||
if ((container->mUintLowerTime & 0x80000000) != 0)
|
||||
container->mByteUpperTime2 |= 1;
|
||||
|
||||
container->mUintLowerTime <<= 1;
|
||||
if ((container->mUintLowerTime & 0x00100000) != 0)
|
||||
container->mUintLowerTime |= 1;
|
||||
|
||||
container->mUintLowerTime &= 0xFFEFFFFF;
|
||||
if (tmp != 0)
|
||||
container->mUintLowerTime |= 0x00100000;
|
||||
}
|
||||
}
|
||||
|
||||
static void ExpandR(DataContainer_t *container)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int i;
|
||||
|
||||
container->mByteUint3.FullUint &= 0x000FFFFF; // clear 0D(4-7),0E
|
||||
tmp = 0x00100000; // and set bits form 0B-0D(0-3)
|
||||
for (i = 0; i < 12; i++)
|
||||
{
|
||||
if ((container->mByteUint3.FullUint & mUintArrBitPattern12[i]) != 0)
|
||||
container->mByteUint3.FullUint |= tmp;
|
||||
tmp <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void ExpandL(DataContainer_t *container)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int i;
|
||||
|
||||
container->mByteUint2.FullUint &= 0x000FFFFF; // clear 0D(4-7),0E
|
||||
tmp = 0x00100000; // and set bits form 0B-0D(0-3)
|
||||
for (i = 0; i < 12; i++)
|
||||
{
|
||||
if ((container->mByteUint2.FullUint & mUintArrBitPattern12[i]) != 0)
|
||||
container->mByteUint2.FullUint |= tmp;
|
||||
tmp <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void CompressKey(DataContainer_t *container)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int i;
|
||||
|
||||
container->mByteUint1.FullUint = 0; // clear 12-15
|
||||
tmp = 0x00000001; // and set bits from 16-1A (time)
|
||||
for (i = 0; i < 30; i++)
|
||||
{
|
||||
if ((container->mUintLowerTime & mUintArrBitPattern30_1[i]) != 0 || (container->mByteUpperTime2 & mUintArrBitPattern30_2[i]) != 0)
|
||||
container->mByteUint1.FullUint |= tmp;
|
||||
tmp <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void DoSbox(DataContainer_t *container)
|
||||
{
|
||||
uint8_t tmp, helper; //mByteR1B;
|
||||
int i;
|
||||
|
||||
helper = container->mByteUint1.s.Byte3; // R1B = R15;
|
||||
container->mByteUint1.s.Byte3 = container->mByteUint1.s.Byte2; // R15 = R14
|
||||
|
||||
// INNER LOOP
|
||||
for (i = 5; i > 0; i--)
|
||||
{
|
||||
if ((i & 1) == 0) // round 4,2
|
||||
{
|
||||
tmp = (uint8_t)(container->mByteUint1.s.Byte0 >> 4); // swap R12
|
||||
tmp |= (uint8_t)((container->mByteUint1.s.Byte0 & 0x0f) << 4);
|
||||
container->mByteUint1.s.Byte0 = tmp;
|
||||
}
|
||||
container->mByteUint1.s.Byte3 &= 0xF0; // set R1C
|
||||
tmp = (uint8_t)((container->mByteUint1.s.Byte0 & 0x0F) | container->mByteUint1.s.Byte3);
|
||||
|
||||
if ((i & 4) != 0)
|
||||
tmp = mByteArrLookupTable1C_1[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8);
|
||||
|
||||
if ((i & 2) != 0)
|
||||
tmp = mByteArrLookupTable1C_2[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8);
|
||||
|
||||
else if (i == 1)
|
||||
tmp = mByteArrLookupTable1C_3[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8);
|
||||
|
||||
if ((i & 1) != 0)
|
||||
container->mByteUint4.s.Byte0 = (uint8_t)(tmp & 0x0F);
|
||||
else
|
||||
container->mByteUint4.s.Byte0 |= (uint8_t)(tmp & 0xF0);
|
||||
|
||||
if ((i & 1) == 0) // copy 14->13->12, 1C->1E->1D
|
||||
{
|
||||
tmp = container->mByteUint1.s.Byte3;
|
||||
container->mByteUint1.FullUint >>= 8;
|
||||
container->mByteUint1.s.Byte3 = tmp;
|
||||
container->mByteUint4.FullUint <<= 8;
|
||||
}
|
||||
|
||||
container->mByteUint1.s.Byte3 >>= 1; // rotate R1B>R15 twice
|
||||
if ((helper & 1) != 0)
|
||||
container->mByteUint1.s.Byte3 |= 0x80;
|
||||
helper >>= 1;
|
||||
|
||||
container->mByteUint1.s.Byte3 >>= 1;
|
||||
if ((helper & 1) != 0)
|
||||
container->mByteUint1.s.Byte3 |= 0x80;
|
||||
helper >>= 1;
|
||||
} // end of inner loop
|
||||
}
|
||||
|
||||
static void DoPbox(DataContainer_t *container)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int i;
|
||||
|
||||
container->mByteUint1.FullUint = 0xFF000000; // clear 12-14
|
||||
tmp = 0x00000001; // and set bits from 1C-1E (result from F)
|
||||
for (i = 0; i < 20; i++)
|
||||
{
|
||||
if ((container->mByteUint4.FullUint & mUintArrBitPattern20[i]) != 0)
|
||||
container->mByteUint1.FullUint |= tmp;
|
||||
tmp <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* modified DES decrypt using strings */
|
||||
static uint8_t *Decrypt(uint8_t *cipher, uint8_t *key)
|
||||
{
|
||||
DataContainer_t container;
|
||||
int i;
|
||||
|
||||
static uint8_t plain[5];
|
||||
CopyTimeToByteUint(cipher, key, &container);
|
||||
|
||||
// OUTER LOOP 1
|
||||
for (i = 16; i > 0; i--)
|
||||
{
|
||||
ShiftTimeRight(i, &container);
|
||||
ExpandR(&container);
|
||||
CompressKey(&container);
|
||||
|
||||
// expR XOR compr.Key
|
||||
container.mByteUint1.FullUint ^= container.mByteUint3.FullUint; // 12-15 XOR 0B-0E
|
||||
container.mByteUint3.s.Byte2 &= 0x0F; // clear 0D(4-7)
|
||||
|
||||
DoSbox(&container);
|
||||
DoPbox(&container);
|
||||
|
||||
// L XOR P-Boxed Round-Key (L')
|
||||
container.mByteUint1.FullUint ^= container.mByteUint2.FullUint;
|
||||
|
||||
// L = R
|
||||
container.mByteUint2.FullUint = container.mByteUint3.FullUint & 0x00FFFFFF;
|
||||
|
||||
// R = L'
|
||||
container.mByteUint3.FullUint = container.mByteUint1.FullUint & 0x00FFFFFF;
|
||||
} // end of outer loop 1
|
||||
|
||||
container.mByteUint3.FullUint <<= 4;
|
||||
container.mByteUint2.s.Byte2 &= 0x0F;
|
||||
container.mByteUint2.s.Byte2 |= (uint8_t)(container.mByteUint3.s.Byte0 & 0xF0);
|
||||
|
||||
plain[0] = container.mByteUint2.s.Byte0;
|
||||
plain[1] = container.mByteUint2.s.Byte1;
|
||||
plain[2] = container.mByteUint2.s.Byte2;
|
||||
plain[3] = container.mByteUint3.s.Byte1;
|
||||
plain[4] = container.mByteUint3.s.Byte2;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
/* modified DES encrypt using strings */
|
||||
static uint8_t *Encrypt(uint8_t *plain, uint8_t *key)
|
||||
{
|
||||
static uint8_t cipher[5];
|
||||
DataContainer_t container;
|
||||
int i;
|
||||
|
||||
CopyTimeToByteUint(plain, key, &container);
|
||||
|
||||
// OUTER LOOP 1
|
||||
for (i = 1; i < 17; i++)
|
||||
{
|
||||
ExpandL(&container);
|
||||
CompressKey(&container);
|
||||
|
||||
// expR XOR compr.Key
|
||||
container.mByteUint1.FullUint ^= container.mByteUint2.FullUint; // L' XOR compr.Key
|
||||
container.mByteUint3.s.Byte2 &= 0x0F; // clear 0D(4-7)
|
||||
|
||||
DoSbox(&container);
|
||||
DoPbox(&container);
|
||||
|
||||
// L XOR P-Boxed Round-Key (L')
|
||||
container.mByteUint1.FullUint ^= container.mByteUint3.FullUint;
|
||||
// L = R
|
||||
container.mByteUint3.FullUint = container.mByteUint2.FullUint & 0x00FFFFFF;
|
||||
// R = L'
|
||||
container.mByteUint2.FullUint = container.mByteUint1.FullUint & 0x00FFFFFF;
|
||||
|
||||
ShiftTimeLeft(i, &container);
|
||||
} // end of outer loop 1
|
||||
|
||||
container.mByteUint3.FullUint <<= 4;
|
||||
container.mByteUint2.s.Byte2 &= 0x0F;
|
||||
container.mByteUint2.s.Byte2 |= (uint8_t)(container.mByteUint3.s.Byte0 & 0xF0);
|
||||
|
||||
cipher[0] = container.mByteUint2.s.Byte0;
|
||||
cipher[1] = container.mByteUint2.s.Byte1;
|
||||
cipher[2] = container.mByteUint2.s.Byte2;
|
||||
cipher[3] = container.mByteUint3.s.Byte1;
|
||||
cipher[4] = container.mByteUint3.s.Byte2;
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
//#define DEBUG_CIPER
|
||||
|
||||
/* decode given crypted frame and key
|
||||
* return the weather info or -1 on checksum error
|
||||
*/
|
||||
int32_t weather_decode(uint64_t cipher, uint64_t key)
|
||||
{
|
||||
uint8_t CipherBytes[5];
|
||||
uint8_t KeyBytes[5];
|
||||
uint8_t *PlainBytes;
|
||||
int32_t weather;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
CipherBytes[i] = cipher >> (i * 8);
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
KeyBytes[i] = key >> (i * 8);
|
||||
|
||||
PlainBytes = Decrypt(CipherBytes, KeyBytes);
|
||||
|
||||
weather = GetWeatherFromPlain(PlainBytes);
|
||||
|
||||
#ifdef DEBUG_CIPER
|
||||
printf("cipher=%s\n", debug_hex(CipherBytes, 5));
|
||||
printf("key =%s\n", debug_hex(KeyBytes, 5));
|
||||
printf("plain =%s\n", debug_hex(PlainBytes, 5));
|
||||
if (weather < 0)
|
||||
printf("weather=error\n");
|
||||
else
|
||||
printf("weather=%06x\n", weather);
|
||||
|
||||
weather_encode(weather, key);
|
||||
#endif
|
||||
|
||||
return weather;
|
||||
}
|
||||
|
||||
/* encode given weather info and key
|
||||
* return crypted frame
|
||||
*/
|
||||
uint64_t weather_encode(uint32_t weather, uint64_t key)
|
||||
{
|
||||
uint8_t KeyBytes[5];
|
||||
uint8_t *PlainBytes;
|
||||
uint8_t *CipherBytes;
|
||||
uint64_t cipher = 0;
|
||||
int i;
|
||||
|
||||
PlainBytes = GetPlainFromWeather(weather);
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
KeyBytes[i] = key >> (i * 8);
|
||||
|
||||
CipherBytes = Encrypt(PlainBytes, KeyBytes);
|
||||
|
||||
#ifdef DEBUG_CIPER
|
||||
printf("plain =%s\n", debug_hex(PlainBytes, 5));
|
||||
printf("key =%s\n", debug_hex(KeyBytes, 5));
|
||||
printf("cipher=%s\n", debug_hex(CipherBytes, 5));
|
||||
#endif
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
cipher |= (uint64_t)(CipherBytes[i]) << (i * 8);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
int32_t weather_decode(uint64_t cipher, uint64_t key);
|
||||
uint64_t weather_encode(uint32_t weather, uint64_t key);
|
|
@ -506,7 +506,7 @@ static void call_play_announcement(euro_call_t *call)
|
|||
else
|
||||
chunk[i] = 0.0;
|
||||
}
|
||||
int16_to_samples(spl, chunk, 160);
|
||||
int16_to_samples_speech(spl, chunk, 160);
|
||||
call_up_audio(call->callref, spl, 160);
|
||||
}
|
||||
|
||||
|
@ -760,7 +760,7 @@ void call_down_release(int callref, int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int __attribute__((unused)) callref, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count)
|
||||
void call_down_audio(int __attribute__((unused)) callref, uint16_t __attribute__((unused)) sequence, uint32_t __attribute__((unused)) timestamp, uint32_t __attribute__((unused)) ssrc, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -595,15 +595,17 @@ void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length
|
|||
fuenf_t *fuenf = (fuenf_t *) sender;
|
||||
sample_t *orig_samples = samples;
|
||||
int orig_length = length;
|
||||
int count;
|
||||
int count, input_num;
|
||||
sample_t *spl;
|
||||
int pos;
|
||||
int i;
|
||||
|
||||
/* speak through */
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE && fuenf->callref) {
|
||||
jitter_load(&fuenf->sender.dejitter, samples, length);
|
||||
memset(power, 1, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
} else {
|
||||
/* send if something has to be sent. else turn transmitter off */
|
||||
while ((count = encode(fuenf, samples, length))) {
|
||||
|
|
|
@ -390,7 +390,7 @@ void call_down_release(int callref, int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
fuenf_t *fuenf;
|
||||
|
@ -403,11 +403,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
if (!sender)
|
||||
return;
|
||||
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE) {
|
||||
sample_t up[(int)((double)count * fuenf->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&fuenf->sender.srstate, samples, count, up);
|
||||
jitter_save(&fuenf->sender.dejitter, up, count);
|
||||
}
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE)
|
||||
jitter_save(&fuenf->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
|
||||
void dump_info(void) {}
|
||||
|
|
|
@ -1240,13 +1240,17 @@ void fuvst_destroy(sender_t *sender)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
fuvst_t *fuvst = (fuvst_t *) sender;
|
||||
int input_num;
|
||||
|
||||
memset(power, 1, length);
|
||||
|
||||
if (fuvst->chan_type == CHAN_TYPE_ZZK)
|
||||
v27_modem_send(&fuvst->modem, samples, length);
|
||||
else
|
||||
jitter_load(&fuvst->sender.dejitter, samples, length);
|
||||
else {
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
}
|
||||
}
|
||||
|
||||
void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db)
|
||||
|
@ -1280,7 +1284,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double __at
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
fuvst_t *fuvst;
|
||||
|
@ -1293,11 +1297,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
if (!sender)
|
||||
return;
|
||||
|
||||
if (fuvst->callref) {
|
||||
sample_t up[(int)((double)count * fuvst->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&fuvst->sender.srstate, samples, count, up);
|
||||
jitter_save(&fuvst->sender.dejitter, up, count);
|
||||
}
|
||||
if (fuvst->callref)
|
||||
jitter_save(&fuvst->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
|
||||
void call_down_clock(void) {}
|
||||
|
|
|
@ -255,7 +255,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double __at
|
|||
v27_modem_receive(&sniffer->modem, samples, length);
|
||||
}
|
||||
|
||||
void call_down_audio(int __attribute__((unused)) callref, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count) { }
|
||||
void call_down_audio(int __attribute__((unused)) callref, uint16_t __attribute__((unused)) sequence, uint32_t __attribute__((unused)) timestamp, uint32_t __attribute__((unused)) ssrc, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count) { }
|
||||
|
||||
void call_down_clock(void) {}
|
||||
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
jtacs
|
||||
golay
|
||||
|
||||
jtacs_SOURCES = \
|
||||
tones.c \
|
||||
stations.c \
|
||||
golay_SOURCES = \
|
||||
golay.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
main.c
|
||||
|
||||
jtacs_LDADD = \
|
||||
golay_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../amps/libamps.a \
|
||||
../amps/libusatone.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libosmocc/libosmocc.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libcompandor/libcompandor.a \
|
||||
$(top_builddir)/src/libgoertzel/libgoertzel.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
|
@ -32,13 +29,14 @@ jtacs_LDADD = \
|
|||
-lm
|
||||
|
||||
if HAVE_ALSA
|
||||
jtacs_LDADD += \
|
||||
golay_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
jtacs_LDADD += \
|
||||
golay_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libam/libam.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
|
@ -46,3 +44,7 @@ jtacs_LDADD += \
|
|||
$(SOAPY_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_ALSA
|
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/* GSC signal processing
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#define CHAN gsc->sender.kanal
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "golay.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define MAX_DISPLAY 1.4 /* something above speech level, no emphasis */
|
||||
|
||||
static void dsp_init_ramp(gsc_t *gsc)
|
||||
{
|
||||
double c;
|
||||
int i;
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Generating cosine shaped ramp table.\n");
|
||||
for (i = 0; i < 256; i++) {
|
||||
/* This is mathematically incorrect... */
|
||||
if (i < 64)
|
||||
c = 1.0;
|
||||
else if (i >= 192)
|
||||
c = -1.0;
|
||||
else
|
||||
c = cos((double)(i - 64) / 128.0 * M_PI);
|
||||
gsc->fsk_ramp_down[i] = c * gsc->fsk_deviation * gsc->fsk_polarity;
|
||||
gsc->fsk_ramp_up[i] = -gsc->fsk_ramp_down[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Init transceiver instance. */
|
||||
int dsp_init_sender(gsc_t *gsc, int samplerate, double deviation, double polarity)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for transceiver.\n");
|
||||
|
||||
/* set modulation parameters */
|
||||
// NOTE: baudrate equals modulation, because we have a raised cosine ramp of beta = 0.5
|
||||
sender_set_fm(&gsc->sender, deviation, 600.0, deviation, MAX_DISPLAY);
|
||||
|
||||
gsc->fsk_bitduration = (double)samplerate / 600.0;
|
||||
gsc->fsk_bitstep = 1.0 / gsc->fsk_bitduration;
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Use %.4f samples for one bit duration @ %d.\n", gsc->fsk_bitduration, gsc->sender.samplerate);
|
||||
|
||||
gsc->fsk_tx_buffer_size = gsc->fsk_bitduration + 10; /* 1 bit, add some extra to prevent short buffer due to rounding */
|
||||
gsc->fsk_tx_buffer = calloc(sizeof(sample_t), gsc->fsk_tx_buffer_size);
|
||||
if (!gsc->fsk_tx_buffer) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "No memory!\n");
|
||||
rc = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* create deviation and ramp */
|
||||
gsc->fsk_deviation = 1.0; // equals what we st at sender_set_fm()
|
||||
gsc->fsk_polarity = polarity;
|
||||
dsp_init_ramp(gsc);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dsp_cleanup_sender(gsc);
|
||||
|
||||
return -rc;
|
||||
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void dsp_cleanup_sender(gsc_t *gsc)
|
||||
{
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for transceiver.\n");
|
||||
|
||||
if (gsc->fsk_tx_buffer) {
|
||||
free(gsc->fsk_tx_buffer);
|
||||
gsc->fsk_tx_buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* encode one bit into samples
|
||||
* input: bit
|
||||
* output: samples
|
||||
* return number of samples */
|
||||
static int fsk_bit_encode(gsc_t *gsc, uint8_t bit)
|
||||
{
|
||||
/* alloc samples, add 1 in case there is a rest */
|
||||
sample_t *spl;
|
||||
double phase, bitstep, devpol;
|
||||
int count;
|
||||
uint8_t lastbit;
|
||||
|
||||
devpol = gsc->fsk_deviation * gsc->fsk_polarity;
|
||||
spl = gsc->fsk_tx_buffer;
|
||||
phase = gsc->fsk_tx_phase;
|
||||
lastbit = gsc->fsk_tx_lastbit;
|
||||
bitstep = gsc->fsk_bitstep * 256.0;
|
||||
|
||||
if (lastbit) {
|
||||
if (bit) {
|
||||
/* stay up */
|
||||
do {
|
||||
*spl++ = devpol;
|
||||
phase += bitstep;
|
||||
} while (phase < 256.0);
|
||||
phase -= 256.0;
|
||||
} else {
|
||||
/* ramp down */
|
||||
do {
|
||||
*spl++ = gsc->fsk_ramp_down[(uint8_t)phase];
|
||||
phase += bitstep;
|
||||
} while (phase < 256.0);
|
||||
phase -= 256.0;
|
||||
lastbit = 0;
|
||||
}
|
||||
} else {
|
||||
if (bit) {
|
||||
/* ramp up */
|
||||
do {
|
||||
*spl++ = gsc->fsk_ramp_up[(uint8_t)phase];
|
||||
phase += bitstep;
|
||||
} while (phase < 256.0);
|
||||
phase -= 256.0;
|
||||
lastbit = 1;
|
||||
} else {
|
||||
/* stay down */
|
||||
do {
|
||||
*spl++ = -devpol;
|
||||
phase += bitstep;
|
||||
} while (phase < 256.0);
|
||||
phase -= 256.0;
|
||||
}
|
||||
}
|
||||
|
||||
/* depending on the number of samples, return the number */
|
||||
count = ((uintptr_t)spl - (uintptr_t)gsc->fsk_tx_buffer) / sizeof(*spl);
|
||||
|
||||
gsc->fsk_tx_phase = phase;
|
||||
gsc->fsk_tx_lastbit = lastbit;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Process received audio stream from radio unit. */
|
||||
void sender_receive(sender_t __attribute__((unused)) *sender, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) length, double __attribute__((unused)) rf_level_db)
|
||||
{
|
||||
}
|
||||
|
||||
/* Provide stream of audio toward radio unit */
|
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
gsc_t *gsc = (gsc_t *) sender;
|
||||
|
||||
again:
|
||||
/* get word */
|
||||
if (!gsc->fsk_tx_buffer_length) {
|
||||
int8_t bit = get_bit(gsc);
|
||||
|
||||
/* no message, power is off */
|
||||
if (bit < 0) {
|
||||
memset(samples, 0, sizeof(samples) * length);
|
||||
memset(power, 0, length);
|
||||
return;
|
||||
}
|
||||
|
||||
/* encode */
|
||||
gsc->fsk_tx_buffer_length = fsk_bit_encode(gsc, bit);
|
||||
gsc->fsk_tx_buffer_pos = 0;
|
||||
}
|
||||
|
||||
/* send encoded bit until end of source or destination buffer is reached */
|
||||
while (length) {
|
||||
*power++ = 1;
|
||||
*samples++ = gsc->fsk_tx_buffer[gsc->fsk_tx_buffer_pos++];
|
||||
length--;
|
||||
if (gsc->fsk_tx_buffer_pos == gsc->fsk_tx_buffer_length) {
|
||||
gsc->fsk_tx_buffer_length = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* do again, if destination buffer is not yet full */
|
||||
if (length)
|
||||
goto again;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
int dsp_init_sender(gsc_t *gsc, int samplerate, double deviation, double polarity);
|
||||
void dsp_cleanup_sender(gsc_t *gsc);
|
||||
|
|
@ -0,0 +1,823 @@
|
|||
/* Golay/GSC transcoding (encoding only - maybe)
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Inspired by GSC code written by Brandon Creighton <cstone@pobox.com>.
|
||||
*
|
||||
* Inspired by GOLAY code written by Robert Morelos-Zaragoza
|
||||
* <robert@spectra.eng.hawaii.edu>.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* Golay code was is use since 1973, the GSC extension was used after 1982.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libmobile/call.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
#include "../libmobile/cause.h"
|
||||
#include "golay.h"
|
||||
#include "dsp.h"
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int golay_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, double deviation, double polarity, const char *message, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback)
|
||||
{
|
||||
gsc_t *gsc;
|
||||
int rc;
|
||||
|
||||
gsc = calloc(1, sizeof(*gsc));
|
||||
if (!gsc) {
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Creating 'GOLAY' instance for frequency = %s (sample rate %d).\n", kanal, samplerate);
|
||||
|
||||
/* init general part of transceiver */
|
||||
rc = sender_create(&gsc->sender, kanal, frequency, frequency, device, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "Failed to init transceiver process!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* init audio processing */
|
||||
rc = dsp_init_sender(gsc, samplerate, deviation, polarity);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "Failed to init audio processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
gsc->tx = 1;
|
||||
gsc->default_message = message;
|
||||
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Created transmitter for frequency %s\n", kanal);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
golay_destroy(&gsc->sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void golay_msg_destroy(gsc_t *gsc, gsc_msg_t *msg);
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void golay_destroy(sender_t *sender)
|
||||
{
|
||||
gsc_t *gsc = (gsc_t *) sender;
|
||||
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Destroying 'GOLAY' instance for frequency = %s.\n", sender->kanal);
|
||||
|
||||
while (gsc->msg_list)
|
||||
golay_msg_destroy(gsc, gsc->msg_list);
|
||||
dsp_cleanup_sender(gsc);
|
||||
sender_destroy(&gsc->sender);
|
||||
free(gsc);
|
||||
}
|
||||
|
||||
/* Create message and add to queue */
|
||||
static gsc_msg_t *golay_msg_create(gsc_t *gsc, const char *address, const char *text, int force_type)
|
||||
{
|
||||
gsc_msg_t *msg, **msgp;
|
||||
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Creating msg instance to page address '%s'.\n", address);
|
||||
|
||||
/* create */
|
||||
msg = calloc(1, sizeof(*msg));
|
||||
if (!msg) {
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
if (strlen(address) != sizeof(msg->address) - 1) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Address has incorrect length, cannot page!\n");
|
||||
return NULL;
|
||||
}
|
||||
if (strlen(text) > sizeof(msg->data) - 1) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Given test is too long, cannot page!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* init */
|
||||
strcpy(msg->address, address);
|
||||
msg->force_type = force_type;
|
||||
strcpy(msg->data, text);
|
||||
|
||||
/* link */
|
||||
msgp = &gsc->msg_list;
|
||||
while ((*msgp))
|
||||
msgp = &(*msgp)->next;
|
||||
(*msgp) = msg;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* Remove and destroy msg from queue */
|
||||
static void golay_msg_destroy(gsc_t *gsc, gsc_msg_t *msg)
|
||||
{
|
||||
gsc_msg_t **msgp;
|
||||
|
||||
/* unlink */
|
||||
msgp = &gsc->msg_list;
|
||||
while ((*msgp) != msg)
|
||||
msgp = &(*msgp)->next;
|
||||
(*msgp) = msg->next;
|
||||
|
||||
/* destroy */
|
||||
free(msg);
|
||||
}
|
||||
|
||||
/* uncomment this for showing encoder tables ("<parity> <information>", LSB is the right most bit) */
|
||||
//#define DEBUG_TABLE
|
||||
|
||||
static uint32_t golay_table[4096];
|
||||
|
||||
#define X22 0x00400000
|
||||
#define X11 0x00000800
|
||||
#define MASK12 0xfffff800
|
||||
#define GEN_GOL 0x00000c75
|
||||
|
||||
/* generate golay encoding table. the redundancy is shifted 12 bits */
|
||||
void init_golay(void)
|
||||
{
|
||||
uint32_t syndrome, aux;
|
||||
int data;
|
||||
|
||||
for (data = 0; data < 4096; data++) {
|
||||
syndrome = data << 11;
|
||||
/* calculate syndrome */
|
||||
aux = X22;
|
||||
if (syndrome >= X11) {
|
||||
while (syndrome & MASK12) {
|
||||
while (!(aux & syndrome))
|
||||
aux = aux >> 1;
|
||||
syndrome ^= (aux / X11) * GEN_GOL;
|
||||
}
|
||||
}
|
||||
golay_table[data] = data | (syndrome << 12);
|
||||
#ifdef DEBUG_TABLE
|
||||
printf("Golay %4d: ", data);
|
||||
for (int i = 22; i >= 0; i--) {
|
||||
if (i == 11)
|
||||
printf(" ");
|
||||
printf("%d", (golay_table[data] >> i) & 1);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t bch_table[128];
|
||||
|
||||
#define X14 0x4000
|
||||
#define X8 0x0100
|
||||
#define MASK7 0xff00
|
||||
#define GEN_BCH 0x00000117
|
||||
|
||||
/* generate bch encoding table. the redundancy is shifted 7 bits */
|
||||
void init_bch(void)
|
||||
{
|
||||
uint16_t syndrome, aux;
|
||||
int data;
|
||||
|
||||
for (data = 0; data < 128; data++) {
|
||||
syndrome = data << 8;
|
||||
/* calculate syndrome */
|
||||
aux = X14;
|
||||
if (syndrome >= X8) {
|
||||
while (syndrome & MASK7) {
|
||||
while (!(aux & syndrome))
|
||||
aux = aux >> 1;
|
||||
syndrome ^= (aux / X8) * GEN_BCH;
|
||||
}
|
||||
}
|
||||
bch_table[data] = data | (syndrome << 7);
|
||||
#ifdef DEBUG_TABLE
|
||||
printf("BCH %3d: ", data);
|
||||
for (int i = 14; i >= 0; i--) {
|
||||
if (i == 6)
|
||||
printf(" ");
|
||||
printf("%d", (bch_table[data] >> i) & 1);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t calc_golay(uint16_t data)
|
||||
{
|
||||
return golay_table[data & 0xfff];
|
||||
}
|
||||
|
||||
static inline uint16_t calc_bch(uint16_t data)
|
||||
{
|
||||
return bch_table[data & 0x7f];
|
||||
}
|
||||
|
||||
static const uint16_t preamble_values[] = {
|
||||
2030, 1628, 3198, 647, 191, 3315, 1949, 2540, 1560, 2335,
|
||||
};
|
||||
|
||||
static const uint32_t start_code = 713;
|
||||
|
||||
/* Rep. 900-2 Table VI */
|
||||
static const uint16_t word1s[50] = {
|
||||
721, 2731, 2952, 1387, 1578, 1708, 2650, 1747, 2580, 1376,
|
||||
2692, 696, 1667, 3800, 3552, 3424, 1384, 3595, 876, 3124,
|
||||
2285, 2608, 899, 3684, 3129, 2124, 1287, 2616, 1647, 3216,
|
||||
375, 1232, 2824, 1840, 408, 3127, 3387, 882, 3468, 3267,
|
||||
1575, 3463, 3152, 2572, 1252, 2592, 1552, 835, 1440, 160,
|
||||
};
|
||||
|
||||
/* Rep. 900-2 Table VII (left column) */
|
||||
static char encode_alpha(char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z')
|
||||
c = c - 'a' + 'A';
|
||||
switch (c) {
|
||||
case 0x0a:
|
||||
case 0x0d:
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> CR/LF character.\n");
|
||||
c = 0x3c;
|
||||
break;
|
||||
case '{':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = 0x3b;
|
||||
break;
|
||||
case '}':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = 0x3d;
|
||||
break;
|
||||
case '\\':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = 0x20;
|
||||
break;
|
||||
default:
|
||||
if (c < 0x20 || c > 0x5d) {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> ' ' character.\n");
|
||||
c = 0x20;
|
||||
} else {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = c - 0x20;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Rep. 900-2 Table VII (right columns) */
|
||||
static char encode_numeric(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case 'u':
|
||||
case 'U':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'U' character.\n");
|
||||
c = 0xb;
|
||||
break;
|
||||
case ' ':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = 0xc;
|
||||
break;
|
||||
case '-':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = 0xd;
|
||||
break;
|
||||
case '=':
|
||||
case '*':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '*' character.\n");
|
||||
c = 0xe;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'A' character.\n");
|
||||
c = 0xf0;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'B' character.\n");
|
||||
c = 0xf1;
|
||||
break;
|
||||
case 'c':
|
||||
case 'C':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'C' character.\n");
|
||||
c = 0xf2;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'D' character.\n");
|
||||
c = 0xf3;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'E' character.\n");
|
||||
c = 0xf4;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'F' character.\n");
|
||||
c = 0xf6;
|
||||
break;
|
||||
case 'g':
|
||||
case 'G':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'G' character.\n");
|
||||
c = 0xf7;
|
||||
break;
|
||||
case 'h':
|
||||
case 'H':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'H' character.\n");
|
||||
c = 0xf8;
|
||||
break;
|
||||
case 'j':
|
||||
case 'J':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'J' character.\n");
|
||||
c = 0xf9;
|
||||
break;
|
||||
case 'l':
|
||||
case 'L':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'L' character.\n");
|
||||
c = 0xfb;
|
||||
break;
|
||||
case 'n':
|
||||
case 'N':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'N' character.\n");
|
||||
c = 0xfc;
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'P' character.\n");
|
||||
c = 0xfd;
|
||||
break;
|
||||
case 'r':
|
||||
case 'R':
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> 'r' character.\n");
|
||||
c = 0xfe;
|
||||
break;
|
||||
default:
|
||||
if (c >= '0' && c <= '9') {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> '%c' character.\n", c);
|
||||
c = c - '0';
|
||||
} else {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, " -> ' ' character.\n");
|
||||
c = 0xc;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static int encode_address(const char *code, int *preamble, uint16_t *word1, uint16_t *word2)
|
||||
{
|
||||
static const uint16_t illegal_low[16] = { 0, 25, 51, 103, 206, 340, 363, 412, 445, 530, 642, 726, 782, 810, 825, 877 };
|
||||
static const uint16_t illegal_high[7] = { 0, 292, 425, 584, 631, 841, 851 };
|
||||
int idx, g0, g1, a0, a1, a2, ap0, ap, ap1, ap2, ap3, b1b0, b3b2, g1g0, a2a1a0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (code[i] < '0' || code[i] > '9')
|
||||
break;
|
||||
}
|
||||
if (code[i]) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Invalid functional address character. Only 0..9 are allowed.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
idx = code[0] - '0';
|
||||
g1 = code[1] - '0';
|
||||
g0 = code[2] - '0';
|
||||
a2 = code[3] - '0';
|
||||
a1 = code[4] - '0';
|
||||
a0 = code[5] - '0';
|
||||
|
||||
*preamble = (idx + g0) % 10;
|
||||
|
||||
ap = a2 * 200 + a1 * 20 + a0 * 2;
|
||||
ap3 = ap / 1000;
|
||||
ap2 = (ap / 100) % 10;
|
||||
ap1 = (ap / 10) % 10;
|
||||
ap0 = ap % 10;
|
||||
|
||||
b1b0 = (ap1 * 10 + ap0) / 2;
|
||||
b3b2 = (ap3 * 10 + ap2);
|
||||
|
||||
g1g0 = (g1 * 10 + g0);
|
||||
if (g1g0 >= 50) {
|
||||
*word1 = word1s[g1g0 - 50];
|
||||
*word2 = b3b2 * 100 + b1b0 + 50;
|
||||
} else {
|
||||
*word1 = word1s[g1g0];
|
||||
*word2 = b3b2 * 100 + b1b0;
|
||||
}
|
||||
|
||||
a2a1a0 = a2 * 100 + a1 * 10 + a0;
|
||||
if (g1g0 < 50) {
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (a2a1a0 == illegal_low[i])
|
||||
break;
|
||||
}
|
||||
if (i < 16) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Functional address has invlid value '%03d' for last three characters.\n", a2a1a0);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (a2a1a0 == illegal_high[i])
|
||||
break;
|
||||
}
|
||||
if (i < 7) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Functional address has invlid value '%03d' for last three characters.\n", a2a1a0);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void queue_reset(gsc_t *gsc)
|
||||
{
|
||||
gsc->bit_index = 0;
|
||||
gsc->bit_num = 0;
|
||||
gsc->bit_overflow = 0;
|
||||
}
|
||||
|
||||
static inline void queue_bit(gsc_t *gsc, int bit)
|
||||
{
|
||||
if (gsc->bit_num == sizeof(gsc->bit))
|
||||
gsc->bit_overflow = 1;
|
||||
if (gsc->bit_overflow) {
|
||||
gsc->bit_num++;
|
||||
return;
|
||||
}
|
||||
gsc->bit[gsc->bit_num++] = bit;
|
||||
}
|
||||
|
||||
static inline void queue_dup(gsc_t *gsc, uint32_t data, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
queue_bit(gsc, (data >> i) & 1);
|
||||
queue_bit(gsc, (data >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void queue_comma(gsc_t *gsc, int bits, uint8_t polarity)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < bits; i++) {
|
||||
queue_bit(gsc, polarity);
|
||||
polarity = !polarity;
|
||||
}
|
||||
}
|
||||
|
||||
static int queue_batch(gsc_t *gsc, const char *address, int force_type, const char *message)
|
||||
{
|
||||
int type;
|
||||
int preamble;
|
||||
uint16_t word1, word2;
|
||||
uint8_t function;
|
||||
uint32_t golay;
|
||||
uint16_t bch[8];
|
||||
uint8_t msg[12], digit, shifted, contbit, checksum;
|
||||
int i, j, k;
|
||||
int rc;
|
||||
|
||||
queue_reset(gsc);
|
||||
|
||||
/* check address length */
|
||||
if (!address || strlen(address) != 7) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Invalid functional address '%s' size. Only 7 digits are allowed.\n", address);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* calculate address */
|
||||
rc = encode_address(address, &preamble, &word1, &word2);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* calculate function */
|
||||
switch (address[6]) {
|
||||
case '1': type = TYPE_VOICE; function = 0; break;
|
||||
case '2': type = TYPE_VOICE; function = 1; break;
|
||||
case '3': type = TYPE_VOICE; function = 2; break;
|
||||
case '4': type = TYPE_VOICE; function = 3; break;
|
||||
case '5': type = TYPE_ALPHA; function = 0; break;
|
||||
case '6': type = TYPE_ALPHA; function = 1; break;
|
||||
case '7': type = TYPE_ALPHA; function = 2; break;
|
||||
case '8': type = TYPE_ALPHA; function = 3; break;
|
||||
case '9': type = TYPE_TONE; function = 0; break;
|
||||
case '0': type = TYPE_TONE; function = 1; break;
|
||||
default:
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Illegal function suffix '%c' in last address digit.\n", address[6]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* override type */
|
||||
if (force_type >= 0) {
|
||||
type = force_type;
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Overriding message type as defined by sender.\n");
|
||||
}
|
||||
|
||||
if (type == TYPE_ALPHA || type == TYPE_NUMERIC)
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Coding text message for functional address '%s' and message '%s'.\n", address, message);
|
||||
else
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Coding tone only message for functional address %s.\n", address);
|
||||
|
||||
/* encode preamble and store */
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding preamble '%d'.\n", preamble);
|
||||
golay = calc_golay(preamble_values[preamble]);
|
||||
queue_comma(gsc, 28, golay & 1);
|
||||
for (i = 0; i < 18; i++) {
|
||||
queue_dup(gsc, golay, 23);
|
||||
}
|
||||
|
||||
/* encode start code and store */
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding start code.\n");
|
||||
golay = calc_golay(start_code);
|
||||
queue_comma(gsc, 28, golay & 1);
|
||||
queue_dup(gsc, golay, 23);
|
||||
golay ^= 0x7fffff;
|
||||
queue_bit(gsc, (golay & 1) ^ 1);
|
||||
queue_dup(gsc, golay, 23);
|
||||
|
||||
/* encode address and store */
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding address words '%d' and '%d'.\n", word1, word2);
|
||||
golay = calc_golay(word1);
|
||||
if (function & 0x2)
|
||||
golay ^= 0x7fffff;
|
||||
queue_comma(gsc, 28, golay & 1);
|
||||
queue_dup(gsc, golay, 23);
|
||||
golay = calc_golay(word2);
|
||||
if (function & 0x1)
|
||||
golay ^= 0x7fffff;
|
||||
queue_bit(gsc, (golay & 1) ^ 1);
|
||||
queue_dup(gsc, golay, 23);
|
||||
|
||||
/* encode message */
|
||||
if (type == TYPE_ALPHA) {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding %d alphanumeric digits.\n", (int)strlen(message));
|
||||
for (i = 0; *message; i++) {
|
||||
if (i == MAX_ADB) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Message overflows %d characters, cropping message.\n", MAX_ADB * 8);
|
||||
}
|
||||
for (j = 0; *message && j < 8; j++) {
|
||||
msg[j] = encode_alpha(*message++);
|
||||
}
|
||||
/* fill empty characters with NULL */
|
||||
while (j < 8)
|
||||
msg[j++] = 0x3e;
|
||||
/* 8 characters + continue-bit */
|
||||
bch[0] = calc_bch((msg[0] | (msg[1] << 6)) & 0x7f);
|
||||
bch[1] = calc_bch(((msg[1] >> 1) | (msg[2] << 5)) & 0x7f);
|
||||
bch[2] = calc_bch(((msg[2] >> 2) | (msg[3] << 4)) & 0x7f);
|
||||
bch[3] = calc_bch(((msg[3] >> 3) | (msg[4] << 3)) & 0x7f);
|
||||
bch[4] = calc_bch(((msg[4] >> 4) | (msg[5] << 2)) & 0x7f);
|
||||
bch[5] = calc_bch(((msg[5] >> 5) | (msg[6] << 1)) & 0x7f);
|
||||
if (*message && i < MAX_ADB)
|
||||
contbit = 1;
|
||||
else
|
||||
contbit = 0;
|
||||
bch[6] = calc_bch((contbit << 6) | msg[7]);
|
||||
/* checksum */
|
||||
checksum = bch[0] + bch[1] + bch[2] + bch[3] + bch[4] + bch[5] + bch[6];
|
||||
bch[7] = calc_bch(checksum & 0x7f);
|
||||
/* store comma bit */
|
||||
queue_bit(gsc, (bch[0] & 1) ^ 1); // inverted first bit
|
||||
/* store interleaved bits */
|
||||
for (j = 0; j < 15; j++) {
|
||||
for (k = 0; k < 8; k++)
|
||||
queue_bit(gsc, (bch[k] >> j) & 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == TYPE_NUMERIC) {
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding %d numeric digits.\n", (int)strlen(message));
|
||||
shifted = 0;
|
||||
for (i = 0; *message; i++) {
|
||||
if (i == MAX_NDB) {
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Message overflows %d characters, cropping message.\n", MAX_NDB * 12);
|
||||
}
|
||||
for (j = 0; *message && j < 12; j++) {
|
||||
/* get next digit or shifted digit */
|
||||
if (shifted) {
|
||||
digit = shifted & 0xf;
|
||||
shifted = 0;
|
||||
} else
|
||||
digit = encode_numeric(*message);
|
||||
/* if digit is extended, use the shifted code and later the digit itself */
|
||||
if (digit > 0xf) {
|
||||
shifted = digit;
|
||||
msg[j] = digit >> 4;
|
||||
} else {
|
||||
msg[j] = digit;
|
||||
message++;
|
||||
}
|
||||
}
|
||||
/* fill empty digits with NULL */
|
||||
while (j < 12)
|
||||
msg[j++] = 0xa;
|
||||
/* 8 digits + continue-bit */
|
||||
bch[0] = calc_bch((msg[0] | (msg[1] << 4)) & 0x7f);
|
||||
bch[1] = calc_bch(((msg[1] >> 3) | (msg[2] << 1) | (msg[3] << 5)) & 0x7f);
|
||||
bch[2] = calc_bch(((msg[3] >> 2) | (msg[4] << 2) | (msg[5] << 6)) & 0x7f);
|
||||
bch[3] = calc_bch(((msg[5] >> 1) | (msg[6] << 3)) & 0x7f);
|
||||
bch[4] = calc_bch((msg[7] | (msg[8] << 4)) & 0x7f);
|
||||
bch[5] = calc_bch(((msg[8] >> 3) | (msg[9] << 1) | (msg[10] << 5)) & 0x7f);
|
||||
if (*message && i < MAX_NDB)
|
||||
contbit = 1;
|
||||
else
|
||||
contbit = 0;
|
||||
bch[6] = calc_bch((contbit << 6) | (msg[10] >> 2) | (msg[11] << 2));
|
||||
/* checksum */
|
||||
checksum = bch[0] + bch[1] + bch[2] + bch[3] + bch[4] + bch[5] + bch[6];
|
||||
bch[7] = calc_bch(checksum & 0x7f);
|
||||
/* store comma bit */
|
||||
queue_bit(gsc, (bch[0] & 1) ^ 1); // inverted first bit
|
||||
/* store interleaved bits */
|
||||
for (j = 0; j < 15; j++) {
|
||||
for (k = 0; k < 8; k++)
|
||||
queue_bit(gsc, (bch[k] >> j) & 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* encode comma after message and store */
|
||||
PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding 'comma' sequence after message.\n");
|
||||
queue_comma(gsc, 121 * 8, 1);
|
||||
|
||||
/* check overflow */
|
||||
if (gsc->bit_overflow) {
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "Bit stream (%d bits) overflows bit buffer size (%d bits), please fix!\n", gsc->bit_num, (int)sizeof(gsc->bit));
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get next bit
|
||||
*
|
||||
* if there is no message, return -1, so that the transmitter is turned off.
|
||||
*
|
||||
* if there is a message, return next bit to be transmitted.
|
||||
*
|
||||
* if there is a message in the queue, encode message and return its first bit.
|
||||
*/
|
||||
int8_t get_bit(gsc_t *gsc)
|
||||
{
|
||||
gsc_msg_t *msg;
|
||||
uint8_t bit;
|
||||
int rc;
|
||||
|
||||
/* if currently transmiting message, send next bit */
|
||||
if (gsc->bit_num) {
|
||||
bit = gsc->bit[gsc->bit_index++];
|
||||
if (gsc->bit_index == gsc->bit_num) {
|
||||
queue_reset(gsc);
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Done transmitting message.\n");
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
|
||||
next_msg:
|
||||
msg = gsc->msg_list;
|
||||
|
||||
/* no message pending, turn transmitter off */
|
||||
if (!msg)
|
||||
return -1;
|
||||
|
||||
/* encode first message in queue */
|
||||
rc = queue_batch(gsc, msg->address, msg->force_type, msg->data);
|
||||
if (rc >= 0)
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Transmitting message to address '%s'.\n", msg->address);
|
||||
golay_msg_destroy(gsc, msg);
|
||||
if (rc < 0)
|
||||
goto next_msg;
|
||||
|
||||
/* return first bit */
|
||||
bit = gsc->bit[gsc->bit_index++];
|
||||
return bit;
|
||||
}
|
||||
|
||||
void golay_msg_send(const char *text)
|
||||
{
|
||||
char buffer[strlen(text) + 1], *p = buffer, *address_string, *message;
|
||||
gsc_t *gsc;
|
||||
int force_type = -1;
|
||||
|
||||
strcpy(buffer, text);
|
||||
address_string = strsep(&p, ",");
|
||||
message = p;
|
||||
if (message) {
|
||||
if (message[0] == 'a' && message[1] == ',') {
|
||||
force_type = TYPE_ALPHA;
|
||||
message += 2;
|
||||
} else
|
||||
if (message[0] == 'n' && message[1] == ',') {
|
||||
force_type = TYPE_NUMERIC;
|
||||
message += 2;
|
||||
}
|
||||
} else
|
||||
message = "";
|
||||
|
||||
gsc = (gsc_t *) sender_head;
|
||||
golay_msg_create(gsc, address_string, message, force_type);
|
||||
}
|
||||
|
||||
void call_down_clock(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* Call control starts call towards paging network. */
|
||||
int call_down_setup(int __attribute__((unused)) callref, const char *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
|
||||
{
|
||||
char channel = '\0';
|
||||
sender_t *sender;
|
||||
gsc_t *gsc;
|
||||
const char *address;
|
||||
const char *message;
|
||||
gsc_msg_t *msg;
|
||||
|
||||
/* find transmitter */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
/* skip channels that are different than requested */
|
||||
if (channel && sender->kanal[0] != channel)
|
||||
continue;
|
||||
gsc = (gsc_t *) sender;
|
||||
/* check if base station cannot transmit */
|
||||
if (!gsc->tx)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
if (channel)
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Cannot page, because given station not available, rejecting!\n");
|
||||
else
|
||||
PDEBUG(DGOLAY, DEBUG_NOTICE, "Cannot page, no trasmitting station available, rejecting!\n");
|
||||
return -CAUSE_NOCHANNEL;
|
||||
}
|
||||
|
||||
/* get address */
|
||||
address = dialing;
|
||||
|
||||
/* get message */
|
||||
if (caller_id[0])
|
||||
message = caller_id;
|
||||
else
|
||||
message = gsc->default_message;
|
||||
|
||||
/* create call process to page station */
|
||||
msg = golay_msg_create(gsc, address, message, -1);
|
||||
if (!msg)
|
||||
return -CAUSE_INVALNUMBER;
|
||||
return -CAUSE_NORMAL;
|
||||
}
|
||||
|
||||
void call_down_answer(int __attribute__((unused)) callref)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void _release(int __attribute__((unused)) callref, int __attribute__((unused)) cause)
|
||||
{
|
||||
PDEBUG(DGOLAY, DEBUG_INFO, "Call has been disconnected by network.\n");
|
||||
}
|
||||
|
||||
void call_down_disconnect(int callref, int cause)
|
||||
{
|
||||
_release(callref, cause);
|
||||
|
||||
call_up_release(callref, cause);
|
||||
}
|
||||
|
||||
/* Call control releases call toward mobile station. */
|
||||
void call_down_release(int callref, int cause)
|
||||
{
|
||||
_release(callref, cause);
|
||||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int __attribute__((unused)) callref, uint16_t __attribute__((unused)) sequence, uint32_t __attribute__((unused)) timestamp, uint32_t __attribute__((unused)) ssrc, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count)
|
||||
{
|
||||
}
|
||||
|
||||
void dump_info(void) {}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "../libmobile/sender.h"
|
||||
|
||||
#define TYPE_TONE 0 /* TONE only */
|
||||
#define TYPE_VOICE 1 /* TONE + VOICE */
|
||||
#define TYPE_ALPHA 2 /* TONE + DATA */
|
||||
#define TYPE_NUMERIC 3 /* TONE + DATA */
|
||||
#define MAX_ADB 10 /* 80 characters */
|
||||
#define MAX_NDB 2 /* 24 digits */
|
||||
|
||||
/* instance of outgoing message */
|
||||
typedef struct gsc_msg {
|
||||
struct gsc_msg *next;
|
||||
char address[8]; /* 7 digits + EOL */
|
||||
int force_type; /* override type from address digit 7 */
|
||||
char data[256]; /* message to be transmitted */
|
||||
} gsc_msg_t;
|
||||
|
||||
typedef struct gsc {
|
||||
sender_t sender;
|
||||
int tx;
|
||||
|
||||
gsc_msg_t *msg_list; /* queue of messages */
|
||||
const char *default_message;
|
||||
|
||||
/* current trasmitting message */
|
||||
uint8_t bit[4096];
|
||||
int bit_num;
|
||||
int bit_index; /* when playing out */
|
||||
int bit_overflow;
|
||||
|
||||
/* dsp states */
|
||||
double fsk_deviation; /* deviation of FSK signal on sound card */
|
||||
double fsk_polarity; /* polarity of FSK signal (-1.0 = bit '1' is down) */
|
||||
sample_t fsk_ramp_up[256]; /* samples of upward ramp shape */
|
||||
sample_t fsk_ramp_down[256]; /* samples of downward ramp shape */
|
||||
double fsk_bitduration; /* duration of a bit in samples */
|
||||
double fsk_bitstep; /* fraction of a bit each sample */
|
||||
sample_t *fsk_tx_buffer; /* tx buffer for one data block */
|
||||
int fsk_tx_buffer_size; /* size of tx buffer (in samples) */
|
||||
int fsk_tx_buffer_length; /* usage of buffer (in samples) */
|
||||
int fsk_tx_buffer_pos; /* current position sending buffer */
|
||||
double fsk_tx_phase; /* current bit position */
|
||||
uint8_t fsk_tx_lastbit; /* last bit of last message, to correctly ramp */
|
||||
} gsc_t;
|
||||
|
||||
int golay_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, double deviation, double polarity, const char *message, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback);
|
||||
void golay_destroy(sender_t *sender);
|
||||
|
||||
void init_golay(void);
|
||||
void init_bch(void);
|
||||
|
||||
int8_t get_bit(gsc_t *gsc);
|
||||
void golay_msg_send(const char *buffer);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
|
||||
const char *aaimage[] = {
|
||||
NULL
|
||||
};
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
/* Golay/GSC pager main
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libmobile/call.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "../libfm/fm.h"
|
||||
#include "../amps/tones.h"
|
||||
#include "../amps/noanswer.h"
|
||||
#include "../amps/outoforder.h"
|
||||
#include "../amps/invalidnumber.h"
|
||||
#include "../amps/congestion.h"
|
||||
#include "golay.h"
|
||||
|
||||
#define MSG_SEND "/tmp/golay_msg_send"
|
||||
#define MSG_RECEIVED "/tmp/golay_msg_received"
|
||||
static int msg_send_fd = -1;
|
||||
|
||||
static int tx = 0; /* we transmit */
|
||||
static int rx = 0; /* we receive */
|
||||
static double deviation = 4500; /* WB confirmed by an email: POCSAG and GSC have same deviation of +-4.5 kHz. */
|
||||
static int deviation_given = 0;
|
||||
static double polarity = 1;
|
||||
static int polarity_given = 0;
|
||||
static const char *message = "1234";
|
||||
|
||||
void print_help(const char *arg0)
|
||||
{
|
||||
main_mobile_print_help(arg0, "| -k 462.900 | -k <MHz> ");
|
||||
/* - - */
|
||||
printf(" -T --tx\n");
|
||||
printf(" Transmit GSC signal on given channel, to page a receiver. (default)\n");
|
||||
printf(" -R --rx\n");
|
||||
printf(" Receive GSC signal on given channel, so we are the receiver.\n");
|
||||
printf(" If none of the options -T nor -R is given, only transmitter is enabled.\n");
|
||||
printf(" -D --deviation wide | 4.5 | narrow | 1.0 | <other KHz>\n"); /* NB confirmed by IQ data from signal-id-wiki */
|
||||
printf(" Choose deviation of FFSK signal (default %.0f KHz).\n", deviation / 1000.0);
|
||||
printf(" -P --polarity -1 | nagative | 1 | positive\n");
|
||||
printf(" Choose polarity of FFSK signal. 'positive' means that a binary 1 uses\n");
|
||||
printf(" positive and a binary 0 negative deviation. (default %s KHz).\n", (polarity < 0) ? "negative" : "positive");
|
||||
printf(" -M --message \"...\"\n");
|
||||
printf(" Send this message, if no caller ID was given or if built-in console\n");
|
||||
printf(" is used. (default \"%s\").\n", message);
|
||||
printf("\n");
|
||||
printf("File: %s\n", MSG_SEND);
|
||||
printf(" Write \"<address>[,message]\" to it, to send a default message.\n");
|
||||
printf(" Write \"<address>,n,message\" to it, to send a numeric message.\n");
|
||||
printf(" Write \"<address>,a,message\" to it, to send an alphanumeric message.\n");
|
||||
printf("\n");
|
||||
printf("By default, an alphanumic message is sent, if last digit of the functional\n");
|
||||
printf("address is 5..8. Otherwise a tone only message is sent.\n");
|
||||
printf("\n");
|
||||
printf("A numeric message can have up to 24 digits, they are: 0123456789U-* and space\n");
|
||||
printf("Also 'shifted' digits can be sent using two digits, they are: ABCDEFGHJLNPR\n");
|
||||
printf("\n");
|
||||
printf("An aplhanumeric message can have up to 80 digits, sent upper case only.\n");
|
||||
main_mobile_print_station_id();
|
||||
main_mobile_print_hotkeys();
|
||||
}
|
||||
|
||||
static void add_options(void)
|
||||
{
|
||||
main_mobile_add_options();
|
||||
option_add('T', "tx", 0);
|
||||
option_add('R', "rx", 0);
|
||||
option_add('D', "deviation", 1);
|
||||
option_add('P', "polarity", 1);
|
||||
option_add('M', "message", 1);
|
||||
}
|
||||
|
||||
static int handle_options(int short_option, int argi, char **argv)
|
||||
{
|
||||
switch (short_option) {
|
||||
case 'T':
|
||||
tx = 1;
|
||||
break;
|
||||
case 'R':
|
||||
rx = 1;
|
||||
break;
|
||||
case 'D':
|
||||
if (argv[argi][0] == 'n' || argv[argi][0] == 'N')
|
||||
deviation = 1000.0;
|
||||
else if (argv[argi][0] == 'w' || argv[argi][0] == 'W')
|
||||
deviation = 4500.0;
|
||||
else
|
||||
deviation = atof(argv[argi]) * 1000.0;
|
||||
if (deviation < 1000.0) {
|
||||
fprintf(stderr, "Given deviation is too low, use higher deviation.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (deviation > 10000.0) {
|
||||
fprintf(stderr, "Given deviation is too high, use lower deviation.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
deviation_given = 1;
|
||||
break;
|
||||
case 'P':
|
||||
if (argv[argi][0] == 'n' || argv[argi][0] == 'N')
|
||||
polarity = -1.0;
|
||||
else if (argv[argi][0] == 'p' || argv[argi][0] == 'P')
|
||||
polarity = 1.0;
|
||||
else if (atoi(argv[argi]) == -1)
|
||||
polarity = -1.0;
|
||||
else if (atoi(argv[argi]) == 1)
|
||||
polarity = 1.0;
|
||||
else {
|
||||
fprintf(stderr, "Given polarity is not positive nor negative, use '-h' for help.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
polarity_given = 1;
|
||||
break;
|
||||
case 'M':
|
||||
message = options_strdup(argv[argi++]);
|
||||
break;
|
||||
default:
|
||||
return main_mobile_handle_options(short_option, argi, argv);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void myhandler(void)
|
||||
{
|
||||
static char buffer[256];
|
||||
static int pos = 0, rc, i;
|
||||
int space = sizeof(buffer) - pos;
|
||||
|
||||
rc = read(msg_send_fd, buffer + pos, space);
|
||||
if (rc > 0) {
|
||||
pos += rc;
|
||||
if (pos == space) {
|
||||
fprintf(stderr, "Message buffer overflow!\n");
|
||||
pos = 0;
|
||||
}
|
||||
/* check for end of line */
|
||||
for (i = 0; i < pos; i++) {
|
||||
if (buffer[i] == '\r' || buffer[i] == '\n')
|
||||
break;
|
||||
}
|
||||
/* send msg */
|
||||
if (i < pos) {
|
||||
buffer[i] = '\0';
|
||||
pos = 0;
|
||||
if (tx)
|
||||
golay_msg_send(buffer);
|
||||
else
|
||||
PDEBUG(DGOLAY, DEBUG_ERROR, "Failed to send message, transmitter is not enabled!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct number_lengths number_lengths[] = {
|
||||
{ 7, "functional address" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc, argi;
|
||||
const char *station_id = "";
|
||||
int i;
|
||||
double frequency;
|
||||
|
||||
/* GSC does not use emphasis, so disable it */
|
||||
uses_emphasis = 0;
|
||||
|
||||
/* init common tones */
|
||||
init_tones();
|
||||
init_outoforder();
|
||||
init_noanswer();
|
||||
init_invalidnumber();
|
||||
init_congestion();
|
||||
|
||||
/* init coding tables */
|
||||
init_golay();
|
||||
init_bch();
|
||||
|
||||
/* init mobile interface */
|
||||
main_mobile_init("0123456789", number_lengths, NULL, NULL);
|
||||
|
||||
/* handle options / config file */
|
||||
add_options();
|
||||
rc = options_config_file(argc, argv, "~/.osmocom/analog/golay.conf", handle_options);
|
||||
if (rc < 0)
|
||||
return 0;
|
||||
argi = options_command_line(argc, argv, handle_options);
|
||||
if (argi <= 0)
|
||||
return argi;
|
||||
|
||||
if (argi < argc) {
|
||||
station_id = argv[argi];
|
||||
rc = main_mobile_number_ask(station_id, "functional address");
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!num_kanal) {
|
||||
printf("No channel is specified, Use '-k <MHz>' to define frequency.\n\n");
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (use_sdr) {
|
||||
/* set device */
|
||||
for (i = 0; i < num_kanal; i++)
|
||||
dsp_device[i] = "sdr";
|
||||
num_device = num_kanal;
|
||||
}
|
||||
if (num_kanal == 1 && num_device == 0)
|
||||
num_device = 1; /* use default */
|
||||
if (num_kanal != num_device) {
|
||||
fprintf(stderr, "You need to specify as many sound devices as you have channels.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* TX is default */
|
||||
if (!tx && !rx)
|
||||
tx = 1;
|
||||
|
||||
if (rx) {
|
||||
fprintf(stderr, "Sorry, but RX is not yet supported and maybe never will.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* TX & RX if loopback */
|
||||
if (loopback)
|
||||
tx = rx = 1;
|
||||
|
||||
/* create pipe for message sendy */
|
||||
unlink(MSG_SEND);
|
||||
rc = mkfifo(MSG_SEND, 0666);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create mwaaage send FIFO '%s'!\n", MSG_SEND);
|
||||
goto fail;
|
||||
} else {
|
||||
msg_send_fd = open(MSG_SEND, O_RDONLY | O_NONBLOCK);
|
||||
if (msg_send_fd < 0) {
|
||||
fprintf(stderr, "Failed to open mwaaage send FIFO! '%s'\n", MSG_SEND);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* inits */
|
||||
fm_init(fast_math);
|
||||
|
||||
/* create transceiver instance */
|
||||
for (i = 0; i < num_kanal; i++) {
|
||||
frequency = atof(kanal[i]) * 1e6;
|
||||
rc = golay_create(kanal[i], frequency, dsp_device[i], use_sdr, dsp_samplerate, rx_gain, tx_gain, deviation, polarity, message, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
printf("Base station ready, please tune transmitter (or receiver) to %.4f MHz\n", frequency / 1e6);
|
||||
}
|
||||
|
||||
main_mobile_loop("golay", &quit, myhandler, station_id);
|
||||
|
||||
fail:
|
||||
/* pipe */
|
||||
if (msg_send_fd > 0)
|
||||
close(msg_send_fd);
|
||||
unlink(MSG_SEND);
|
||||
|
||||
/* destroy transceiver instance */
|
||||
while(sender_head)
|
||||
golay_destroy(sender_head);
|
||||
|
||||
/* exits */
|
||||
fm_exit();
|
||||
|
||||
options_free();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ static int generate_tone(imts_t *imts, sample_t *samples, int length)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
imts_t *imts = (imts_t *) sender;
|
||||
int count;
|
||||
int count, input_num;
|
||||
|
||||
memset(power, 1, length);
|
||||
|
||||
|
@ -296,7 +296,9 @@ again:
|
|||
break;
|
||||
case DSP_MODE_AUDIO:
|
||||
memset(power, 1, length);
|
||||
jitter_load(&imts->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
if (imts->pre_emphasis)
|
||||
pre_emphasis(&imts->estate, samples, length);
|
||||
break;
|
||||
|
|
|
@ -1284,7 +1284,7 @@ void call_down_release(int callref, __attribute__((unused)) int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
imts_t *imts;
|
||||
|
@ -1298,9 +1298,7 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
return;
|
||||
|
||||
if (imts->dsp_mode == DSP_MODE_AUDIO) {
|
||||
sample_t up[(int)((double)count * imts->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&imts->sender.srstate, samples, count, up);
|
||||
jitter_save(&imts->sender.dejitter, up, count);
|
||||
jitter_save(&imts->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ int dsp_init_sender(jolly_t *jolly, int nbfm, double squelch_db, int repeater)
|
|||
/* repeater */
|
||||
jolly->repeater = repeater;
|
||||
jolly->repeater_max = (int)((double)jolly->sender.samplerate * REPEATER_TIME);
|
||||
rc = jitter_create(&jolly->repeater_dejitter, jolly->sender.samplerate / 5);
|
||||
rc = jitter_create(&jolly->repeater_dejitter, "repeater", jolly->sender.samplerate, sizeof(sample_t), 0.050, 0.500, JITTER_FLAG_NONE);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to create and init repeater buffer!\n");
|
||||
goto error;
|
||||
|
@ -319,7 +319,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_l
|
|||
|
||||
/* if repeater mode, store sample in jitter buffer */
|
||||
if (jolly->repeater)
|
||||
jitter_save(&jolly->repeater_dejitter, samples, length);
|
||||
jitter_save(&jolly->repeater_dejitter, samples, length, 0, 0, 0, 0);
|
||||
|
||||
/* downsample, decode DTMF */
|
||||
count = samplerate_downsample(&jolly->sender.srstate, samples, length);
|
||||
|
@ -346,7 +346,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_l
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
jolly_t *jolly = (jolly_t *) sender;
|
||||
int count;
|
||||
int count, input_num;
|
||||
|
||||
switch (jolly->state) {
|
||||
case STATE_IDLE:
|
||||
|
@ -368,7 +368,9 @@ void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length
|
|||
case STATE_CALL:
|
||||
case STATE_CALL_DIALING:
|
||||
memset(power, 1, length);
|
||||
jitter_load(&jolly->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
break;
|
||||
case STATE_OUT_VERIFY:
|
||||
case STATE_IN_PAGING:
|
||||
|
|
|
@ -594,7 +594,7 @@ void call_down_release(int callref, __attribute__((unused)) int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
jolly_t *jolly;
|
||||
|
@ -607,11 +607,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
if (!sender)
|
||||
return;
|
||||
|
||||
if (jolly->state == STATE_CALL || jolly->state == STATE_CALL_DIALING) {
|
||||
sample_t up[(int)((double)count * jolly->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&jolly->sender.srstate, samples, count, up);
|
||||
jitter_save(&jolly->sender.dejitter, up, count);
|
||||
}
|
||||
if (jolly->state == STATE_CALL || jolly->state == STATE_CALL_DIALING)
|
||||
jitter_save(&jolly->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
|
||||
void call_down_clock(void) {}
|
||||
|
|
|
@ -11072,7 +11072,7 @@ int init_voice(int samplerate)
|
|||
{
|
||||
samplerate_t srstate;
|
||||
sample_t spl_in[CHUNK], *spl_out;
|
||||
int i, s, j, chunk, count;
|
||||
int i, s, j, chunk, count, output_num;
|
||||
int rc;
|
||||
|
||||
jolly_voice.spl[0] = (sample_t *)digit_0;
|
||||
|
@ -11109,7 +11109,8 @@ int init_voice(int samplerate)
|
|||
}
|
||||
|
||||
for (i = 0; i < 13; i++) {
|
||||
spl_out = calloc(((double)jolly_voice.size[i] * srstate.factor + 0.5) + 10, sizeof(*spl_out));
|
||||
output_num = samplerate_upsample_output_num(&srstate, jolly_voice.size[i]);
|
||||
spl_out = calloc(output_num, sizeof(*spl_out));
|
||||
count = 0;
|
||||
for (s = 0; s < jolly_voice.size[i]; s += CHUNK) {
|
||||
chunk = jolly_voice.size[i] - s;
|
||||
|
@ -11117,7 +11118,8 @@ int init_voice(int samplerate)
|
|||
chunk = CHUNK;
|
||||
for (j = 0; j < chunk; j++)
|
||||
spl_in[j] = (double)(((int16_t *)(jolly_voice.spl[i]))[s + j]) / 32767.0 * GAIN;
|
||||
count += samplerate_upsample(&srstate, spl_in, chunk, spl_out + count);
|
||||
samplerate_upsample(&srstate, spl_in, chunk, spl_out + count, output_num);
|
||||
count += output_num;
|
||||
}
|
||||
jolly_voice.spl[i] = spl_out;
|
||||
jolly_voice.size[i] = count;
|
||||
|
|
|
@ -57,6 +57,7 @@ struct debug_cat {
|
|||
{ "jollycom", "\033[1;34m" },
|
||||
{ "eurosignal", "\033[1;34m" },
|
||||
{ "pocsag", "\033[1;34m" },
|
||||
{ "golay", "\033[1;34m" },
|
||||
{ "5-ton-folge", "\033[1;34m" },
|
||||
{ "frame", "\033[0;36m" },
|
||||
{ "call", "\033[0;37m" },
|
||||
|
@ -93,12 +94,13 @@ struct debug_cat {
|
|||
{ "uk0", "\033[1;34m" },
|
||||
{ "ph", "\033[0;33m" },
|
||||
{ "dcf77", "\033[1;34m" },
|
||||
{ "jitter", "\033[0;36m" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int debuglevel = DEBUG_INFO;
|
||||
int debug_date = 0;
|
||||
uint64_t debug_mask = ~0;
|
||||
uint64_t debug_mask[2] = { ~0, ~0 };
|
||||
extern int num_kanal;
|
||||
|
||||
void (*clear_console_text)(void) = NULL;
|
||||
|
@ -155,7 +157,7 @@ void _printdebug(const char *file, const char __attribute__((unused)) *function,
|
|||
if (debuglevel > level)
|
||||
return;
|
||||
|
||||
if (!(debug_mask & ((uint64_t)1 << cat)))
|
||||
if (!(debug_mask[cat >> 6] & ((uint64_t)1 << (cat & 63))))
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
@ -289,7 +291,7 @@ int parse_debug_opt(const char *optarg)
|
|||
return -EINVAL;
|
||||
}
|
||||
if (dstring)
|
||||
debug_mask = 0;
|
||||
memset(debug_mask, 0, sizeof(debug_mask));
|
||||
while((p = strsep(&dstring, ","))) {
|
||||
for (i = 0; debug_cat[i].name; i++) {
|
||||
if (!strcasecmp(p, debug_cat[i].name))
|
||||
|
@ -300,7 +302,7 @@ int parse_debug_opt(const char *optarg)
|
|||
free(dup);
|
||||
return -EINVAL;
|
||||
}
|
||||
debug_mask |= ((uint64_t)1 << i);
|
||||
debug_mask[i >> 6] |= ((uint64_t)1 << (i & 63));
|
||||
}
|
||||
|
||||
free(dup);
|
||||
|
|
|
@ -19,42 +19,45 @@
|
|||
#define DJOLLY 12
|
||||
#define DEURO 13
|
||||
#define DPOCSAG 14
|
||||
#define DFUENF 15
|
||||
#define DFRAME 16
|
||||
#define DCALL 17
|
||||
#define DCC 18
|
||||
#define DDB 19
|
||||
#define DTRANS 20
|
||||
#define DDMS 21
|
||||
#define DSMS 22
|
||||
#define DSDR 23
|
||||
#define DUHD 24
|
||||
#define DSOAPY 25
|
||||
#define DWAVE 26
|
||||
#define DRADIO 27
|
||||
#define DAM791X 28
|
||||
#define DUART 29
|
||||
#define DDEVICE 30
|
||||
#define DDATENKLO 31
|
||||
#define DZEIT 32
|
||||
#define DSIM1 33
|
||||
#define DSIM2 34
|
||||
#define DSIMI 35
|
||||
#define DSIM7 36
|
||||
#define DMTP2 37
|
||||
#define DMTP3 38
|
||||
#define DMUP 39
|
||||
#define DROUTER 40
|
||||
#define DSTDERR 41
|
||||
#define DSS5 42
|
||||
#define DISDN 43
|
||||
#define DMISDN 44
|
||||
#define DDSS1 45
|
||||
#define DSIP 46
|
||||
#define DTEL 47
|
||||
#define DUK0 48
|
||||
#define DPH 49
|
||||
#define DDCF77 50
|
||||
#define DGOLAY 15
|
||||
#define DFUENF 16
|
||||
#define DFRAME 17
|
||||
#define DCALL 18
|
||||
#define DCC 19
|
||||
#define DDB 20
|
||||
#define DTRANS 21
|
||||
#define DDMS 22
|
||||
#define DSMS 23
|
||||
#define DSDR 24
|
||||
#define DUHD 25
|
||||
#define DSOAPY 26
|
||||
#define DWAVE 27
|
||||
#define DRADIO 28
|
||||
#define DAM791X 29
|
||||
#define DUART 30
|
||||
#define DDEVICE 31
|
||||
#define DDATENKLO 32
|
||||
#define DZEIT 33
|
||||
#define DSIM1 34
|
||||
#define DSIM2 35
|
||||
#define DSIMI 36
|
||||
#define DSIM7 37
|
||||
#define DMTP2 38
|
||||
#define DMTP3 39
|
||||
#define DMUP 40
|
||||
#define DROUTER 41
|
||||
#define DSTDERR 42
|
||||
#define DSS5 43
|
||||
#define DISDN 44
|
||||
#define DMISDN 45
|
||||
#define DDSS1 46
|
||||
#define DSIP 47
|
||||
#define DTEL 48
|
||||
#define DUK0 49
|
||||
#define DPH 50
|
||||
#define DDCF77 51
|
||||
#define DJITTER 52
|
||||
//NOTE: increment mask array, if 127 is exceeded
|
||||
|
||||
void lock_debug(void);
|
||||
void unlock_debug(void);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "dtmf_decode.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
@ -40,22 +41,6 @@
|
|||
|
||||
static const char dtmf_digit[] = " 123A456B789C*0#D";
|
||||
|
||||
#ifdef DEBUG
|
||||
const char *_debug_amplitude(double level)
|
||||
{
|
||||
static char text[42];
|
||||
|
||||
strcpy(text, " : ");
|
||||
if (level > 1.0)
|
||||
level = 1.0;
|
||||
if (level < -1.0)
|
||||
level = -1.0;
|
||||
text[20 + (int)(level * 20)] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
#endif
|
||||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude)
|
||||
{
|
||||
int rc;
|
||||
|
@ -98,6 +83,12 @@ void dtmf_decode_exit(dtmf_dec_t *dtmf)
|
|||
fm_demod_exit(&dtmf->demod_high);
|
||||
}
|
||||
|
||||
void dtmf_decode_reset(dtmf_dec_t *dtmf)
|
||||
{
|
||||
dtmf->detected = 0;
|
||||
dtmf->count = 0;
|
||||
}
|
||||
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high)
|
||||
{
|
||||
sample_t I_low[length], Q_low[length];
|
||||
|
@ -143,7 +134,7 @@ void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length)
|
|||
|
||||
for (i = 0; i < length; i++) {
|
||||
#ifdef DEBUG
|
||||
// printf("%s %.5f\n", _debug_amplitude(samples[i]/2.0), samples[i]/2.0);
|
||||
// printf("%s %.5f\n", debug_amplitude(samples[i]/2.0), samples[i]/2.0);
|
||||
#endif
|
||||
/* get frequency of low frequencies, correct amplitude drop at cutoff point */
|
||||
f1 = frequency_low[i] + (DTMF_LOW_1 + DTMF_LOW_4) / 2.0;
|
||||
|
|
|
@ -30,6 +30,7 @@ typedef struct dtmf_dec {
|
|||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude);
|
||||
void dtmf_decode_exit(dtmf_dec_t *dtmf);
|
||||
void dtmf_decode_reset(dtmf_dec_t *dtmf);
|
||||
void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length);
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high);
|
||||
|
||||
|
|
|
@ -27,10 +27,6 @@
|
|||
|
||||
#define PEAK_DTMF_LOW 0.2818 /* -11 dBm, relative to 0 dBm level */
|
||||
#define PEAK_DTMF_HIGH 0.3548 /* -9 dBm, relative to 0 dBm level */
|
||||
#define DTMF_DURATION 0.100 /* duration in seconds */
|
||||
|
||||
static sample_t dsp_sine_dtmf_low[65536];
|
||||
static sample_t dsp_sine_dtmf_high[65536];
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level)
|
||||
{
|
||||
|
@ -38,17 +34,15 @@ void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level)
|
|||
|
||||
memset(dtmf, 0, sizeof(*dtmf));
|
||||
dtmf->samplerate = samplerate;
|
||||
dtmf->max = (int)((double)samplerate * DTMF_DURATION + 0.5);
|
||||
|
||||
// FIXME: do this globally and not per instance */
|
||||
for (i = 0; i < 65536; i++) {
|
||||
dsp_sine_dtmf_low[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_LOW * dBm_level;
|
||||
dsp_sine_dtmf_high[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_HIGH * dBm_level;
|
||||
dtmf->sine_low[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_LOW * dBm_level;
|
||||
dtmf->sine_high[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_HIGH * dBm_level;
|
||||
}
|
||||
}
|
||||
|
||||
/* set dtmf tone */
|
||||
void dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone)
|
||||
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration)
|
||||
{
|
||||
double f1, f2;
|
||||
|
||||
|
@ -71,53 +65,66 @@ void dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone)
|
|||
case'd':case 'D': f1 = 941.0; f2 = 1633.0; break;
|
||||
default:
|
||||
dtmf->tone = 0;
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
dtmf->tone = tone;
|
||||
dtmf->pos = 0;
|
||||
dtmf->on = (int)((double)dtmf->samplerate * on_duration);
|
||||
dtmf->off = dtmf->on + (int)((double)dtmf->samplerate * off_duration);
|
||||
dtmf->phaseshift65536[0] = 65536.0 / ((double)dtmf->samplerate / f1);
|
||||
dtmf->phaseshift65536[1] = 65536.0 / ((double)dtmf->samplerate / f2);
|
||||
dtmf->phase65536[0] = 0.0;
|
||||
dtmf->phase65536[1] = 0.0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate audio stream from DTMF tone. Keep phase for next call of function. */
|
||||
void dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length)
|
||||
/* Generate audio stream from DTMF tone.
|
||||
* Keep phase for next call of function.
|
||||
* Stop, if tone has finished and return only the samples that were used.
|
||||
*/
|
||||
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length)
|
||||
{
|
||||
double *phaseshift, *phase;
|
||||
int i, pos, max;
|
||||
sample_t *sine_low, *sine_high;
|
||||
int count = 0;
|
||||
int i;
|
||||
|
||||
/* use silence, if no tone */
|
||||
if (!dtmf->tone) {
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
return;
|
||||
}
|
||||
/* if no tone */
|
||||
if (!dtmf->tone)
|
||||
return 0;
|
||||
|
||||
sine_low = dtmf->sine_low;
|
||||
sine_high = dtmf->sine_high;
|
||||
phaseshift = dtmf->phaseshift65536;
|
||||
phase = dtmf->phase65536;
|
||||
pos = dtmf->pos;
|
||||
max = dtmf->max;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine_dtmf_low[(uint16_t)phase[0]]
|
||||
+ dsp_sine_dtmf_high[(uint16_t)phase[1]];
|
||||
*samples++ = sine_low[(uint16_t)phase[0]]
|
||||
+ sine_high[(uint16_t)phase[1]];
|
||||
phase[0] += phaseshift[0];
|
||||
if (phase[0] >= 65536)
|
||||
phase[0] -= 65536;
|
||||
if (phase[0] >= 65536.0)
|
||||
phase[0] -= 65536.0;
|
||||
phase[1] += phaseshift[1];
|
||||
if (phase[1] >= 65536)
|
||||
phase[1] -= 65536;
|
||||
if (phase[1] >= 65536.0)
|
||||
phase[1] -= 65536.0;
|
||||
|
||||
dtmf->pos++;
|
||||
/* tone ends */
|
||||
if (++pos == max) {
|
||||
if (dtmf->pos == dtmf->on) {
|
||||
phaseshift[0] = 0.0;
|
||||
phaseshift[1] = 0.0;
|
||||
phase[0] = 0.0;
|
||||
phase[1] = 0.0;
|
||||
}
|
||||
/* pause ends */
|
||||
if (dtmf->pos == dtmf->off) {
|
||||
dtmf->tone = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
length -= i;
|
||||
count += i;
|
||||
|
||||
dtmf->pos = pos;
|
||||
|
||||
/* if tone ends, fill rest with silence */
|
||||
if (length)
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
typedef struct dtmf_enc {
|
||||
int samplerate; /* samplerate */
|
||||
char tone; /* current tone to be played */
|
||||
int on, off; /* samples to turn on and afterwards off */
|
||||
int pos; /* sample counter for tone */
|
||||
int max; /* max number of samples for tone duration */
|
||||
double phaseshift65536[2]; /* how much the phase of sine wave changes per sample */
|
||||
double phase65536[2]; /* current phase */
|
||||
sample_t sine_low[65536]; /* sine tables at individual levels */
|
||||
sample_t sine_high[65536];
|
||||
} dtmf_enc_t;
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level);
|
||||
void dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone);
|
||||
void dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length);
|
||||
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration);
|
||||
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length);
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#ifndef _LIB_FM_H
|
||||
#define _LIB_FM_H
|
||||
|
||||
#include "../libfilter/iir_filter.h"
|
||||
|
||||
int fm_init(int fast_math);
|
||||
|
@ -38,3 +41,4 @@ void fm_demod_exit(fm_demod_t *demod);
|
|||
void fm_demodulate_complex(fm_demod_t *demod, sample_t *frequency, int length, float *baseband, sample_t *I, sample_t *Q);
|
||||
void fm_demodulate_real(fm_demod_t *demod, sample_t *frequency, int length, sample_t *baseband, sample_t *I, sample_t *Q);
|
||||
|
||||
#endif /* _LIB_FM_H */
|
||||
|
|
167
src/libfsk/fsk.c
167
src/libfsk/fsk.c
|
@ -29,6 +29,12 @@
|
|||
|
||||
#define PI M_PI
|
||||
|
||||
/* uncomment to see the modulated curve */
|
||||
//#define DEBUG_MODULATOR
|
||||
|
||||
/* uncomment to see the shape of the filter */
|
||||
//#define DEBUG_MODULATOR_SHAPE
|
||||
|
||||
/*
|
||||
* fsk = instance of fsk modem
|
||||
* inst = instance of user
|
||||
|
@ -41,6 +47,7 @@
|
|||
*/
|
||||
int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int samplerate, double bitrate, double f0, double f1, double level, int ffsk, int filter)
|
||||
{
|
||||
double temp;
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
|
@ -49,7 +56,7 @@ int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int sa
|
|||
memset(fsk, 0, sizeof(*fsk));
|
||||
|
||||
/* gen sine table with deviation */
|
||||
fsk->sin_tab = calloc(65536+16384, sizeof(*fsk->sin_tab));
|
||||
fsk->sin_tab = calloc(65536, sizeof(*fsk->sin_tab));
|
||||
if (!fsk->sin_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
rc = -ENOMEM;
|
||||
|
@ -59,7 +66,6 @@ int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int sa
|
|||
fsk->sin_tab[i] = sin((double)i / 65536.0 * 2.0 * PI) * level;
|
||||
|
||||
fsk->inst = inst;
|
||||
fsk->tx_bit = -1;
|
||||
fsk->level = level;
|
||||
fsk->send_bit = send_bit;
|
||||
fsk->f0_deviation = (f0 - f1) / 2.0;
|
||||
|
@ -72,12 +78,12 @@ int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int sa
|
|||
fsk->high_bit = 0;
|
||||
}
|
||||
|
||||
fsk->bits_per_sample = (double)bitrate / (double)samplerate;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Bitduration of %.4f bits per sample @ %d.\n", fsk->bits_per_sample, samplerate);
|
||||
fsk->bits65536_per_sample = (double)bitrate / (double)samplerate * 65536.0;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Bitduration of %.4f bits per sample @ %d.\n", fsk->bits65536_per_sample / 65536.0, samplerate);
|
||||
|
||||
fsk->phaseshift65536[0] = f0 / (double)samplerate * 65536.0;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (phaseshift65536[0] = %.4f)\n", f0, fsk->phaseshift65536[0]);
|
||||
fsk->phaseshift65536[1] = f1 / (double)samplerate * 65536.0;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (phaseshift65536[0] = %.4f)\n", f0, fsk->phaseshift65536[0]);
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F1 = %.0f Hz (phaseshift65536[1] = %.4f)\n", f1, fsk->phaseshift65536[1]);
|
||||
|
||||
/* use ffsk modulation, i.e. each bit has an integer number of
|
||||
|
@ -86,6 +92,12 @@ int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int sa
|
|||
if (ffsk) {
|
||||
double waves;
|
||||
|
||||
if (filter) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "Cannot use FFSK with filter.\n");
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "enable FFSK modulation mode\n");
|
||||
fsk->ffsk = 1;
|
||||
waves = (f0 / bitrate);
|
||||
|
@ -100,22 +112,39 @@ int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int sa
|
|||
abort();
|
||||
}
|
||||
fsk->cycles_per_bit65536[1] = waves * 65536.0;
|
||||
} else {
|
||||
fsk->cycles_per_bit65536[0] = f0 / bitrate * 65536.0;
|
||||
fsk->cycles_per_bit65536[1] = f1 / bitrate * 65536.0;
|
||||
}
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (cycles_per_bit65536[0] = %.4f)\n", f0, fsk->cycles_per_bit65536[0]);
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F1 = %.0f Hz (cycles_per_bit65536[1] = %.4f)\n", f1, fsk->cycles_per_bit65536[1]);
|
||||
|
||||
/* if filter is enabled, add a band pass filter to smooth the spectrum of the tones
|
||||
* the bandwidth is twice the difference between f0 and f1
|
||||
*/
|
||||
/* if filter is enabled, use a cosine shaped curve to change the phase each sample */
|
||||
if (filter) {
|
||||
double low = (f0 + f1) / 2.0 - fabs(f0 - f1);
|
||||
double high = (f0 + f1) / 2.0 + fabs(f0 - f1);
|
||||
fsk->phase_tab_0_1 = calloc(65536 + 65536, sizeof(*fsk->sin_tab));
|
||||
if (!fsk->phase_tab_0_1) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
rc = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
fsk->phase_tab_1_0 = fsk->phase_tab_0_1 + 65536;
|
||||
for (i = 0; i < 65536; i++) {
|
||||
temp = cos((double)i / 65536.0 * PI) / 2 + 0.5; /* half cosine going from 1 to 0 */
|
||||
fsk->phase_tab_0_1[i] = temp * fsk->phaseshift65536[0] + (1.0 - temp) * fsk->phaseshift65536[1];
|
||||
fsk->phase_tab_1_0[i] = temp * fsk->phaseshift65536[1] + (1.0 - temp) * fsk->phaseshift65536[0];
|
||||
#ifdef DEBUG_MODULATOR_SHAPE
|
||||
fsk->phase_tab_0_1[i] = 1.0 - temp;
|
||||
fsk->phase_tab_1_0[i] = temp;
|
||||
#endif
|
||||
}
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "enable filter to smooth FSK transmission. (frequency rage %.0f .. %.0f)\n", low, high);
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Enable filter to smooth FSK transmission.\n");
|
||||
fsk->filter = 1;
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_highpass_init(&fsk->lp[0], low, samplerate, 2);
|
||||
iir_lowpass_init(&fsk->lp[1], high, samplerate, 2);
|
||||
}
|
||||
|
||||
/* must reset, because bit states must be initialized */
|
||||
fsk_mod_reset(fsk);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
|
@ -132,6 +161,11 @@ void fsk_mod_cleanup(fsk_mod_t *fsk)
|
|||
free(fsk->sin_tab);
|
||||
fsk->sin_tab = NULL;
|
||||
}
|
||||
if (fsk->phase_tab_0_1) {
|
||||
free(fsk->phase_tab_0_1);
|
||||
fsk->phase_tab_0_1 = NULL;
|
||||
fsk->phase_tab_1_0 = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* modulate bits
|
||||
|
@ -154,12 +188,16 @@ int fsk_mod_send(fsk_mod_t *fsk, sample_t *sample, int length, int add)
|
|||
/* get next bit */
|
||||
if (fsk->tx_bit < 0) {
|
||||
next_bit:
|
||||
fsk->tx_last_bit = fsk->tx_bit;
|
||||
fsk->tx_bit = fsk->send_bit(fsk->inst);
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("bit change to %d\n", fsk->tx_bit);
|
||||
printf("bit change from %d to %d\n", fsk->tx_last_bit, fsk->tx_bit);
|
||||
#endif
|
||||
if (fsk->tx_bit < 0)
|
||||
if (fsk->tx_bit < 0) {
|
||||
fsk_mod_reset(fsk);
|
||||
goto done;
|
||||
}
|
||||
fsk->tx_bit &= 1;
|
||||
/* correct phase when changing bit */
|
||||
if (fsk->ffsk) {
|
||||
/* round phase to nearest zero crossing */
|
||||
|
@ -167,40 +205,86 @@ next_bit:
|
|||
phase = 32768.0;
|
||||
else
|
||||
phase = 0;
|
||||
/* set phase according to current position in bit */
|
||||
phase += fsk->tx_bitpos * fsk->cycles_per_bit65536[fsk->tx_bit & 1];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("phase %.3f bitpos=%.6f\n", phase, fsk->tx_bitpos);
|
||||
printf("phase %.3f bitpos=%.6f\n", phase / 65536.0, fsk->tx_bitpos65536 / 65536.0);
|
||||
#endif
|
||||
}
|
||||
if (!fsk->filter) {
|
||||
/* change phase forward to the current bit position */
|
||||
phase += fsk->tx_bitpos65536 / 65536.0 * fsk->cycles_per_bit65536[fsk->tx_bit];
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
}
|
||||
}
|
||||
|
||||
/* modulate bit */
|
||||
phaseshift = fsk->phaseshift65536[fsk->tx_bit & 1];
|
||||
while (count < length && fsk->tx_bitpos < 1.0) {
|
||||
if (add)
|
||||
sample[count++] += fsk->sin_tab[(uint16_t)phase];
|
||||
else
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
if (!fsk->filter || fsk->tx_last_bit < 0 || fsk->tx_last_bit == fsk->tx_bit) {
|
||||
/* without filtering or when there is no bit change */
|
||||
phaseshift = fsk->phaseshift65536[fsk->tx_bit];
|
||||
while (count < length && fsk->tx_bitpos65536 < 65536.0) {
|
||||
if (add)
|
||||
sample[count++] += fsk->sin_tab[(uint16_t)phase];
|
||||
else
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("|%s|\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
|
||||
printf("|%s| %d\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level), fsk->tx_bit);
|
||||
#endif
|
||||
phase += phaseshift;
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
fsk->tx_bitpos += fsk->bits_per_sample;
|
||||
#ifdef DEBUG_MODULATOR_SHAPE
|
||||
printf("|%s| %d\n", debug_amplitude(fsk->tx_bit), fsk->tx_bit);
|
||||
#endif
|
||||
phase += phaseshift;
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
|
||||
}
|
||||
} else if (fsk->tx_bit > fsk->tx_last_bit) {
|
||||
/* with cosine shape filter, going from phase of 0 to phase of 1 */
|
||||
while (count < length && fsk->tx_bitpos65536 < 65536.0) {
|
||||
if (add)
|
||||
sample[count++] += fsk->sin_tab[(uint16_t)phase];
|
||||
else
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("|%s| 0->1\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
|
||||
#endif
|
||||
#ifdef DEBUG_MODULATOR_SHAPE
|
||||
printf("|%s|0->1\n", debug_amplitude(fsk->phase_tab_0_1[(uint16_t)fsk->tx_bitpos65536]));
|
||||
#endif
|
||||
phase += fsk->phase_tab_0_1[(uint16_t)fsk->tx_bitpos65536];
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
|
||||
}
|
||||
} else {
|
||||
/* with cosine shape filter, going from phase of 1 to phase of 0 */
|
||||
while (count < length && fsk->tx_bitpos65536 < 65536.0) {
|
||||
if (add)
|
||||
sample[count++] += fsk->sin_tab[(uint16_t)phase];
|
||||
else
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("|%s|1->0\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
|
||||
#endif
|
||||
#ifdef DEBUG_MODULATOR_SHAPE
|
||||
printf("|%s|1->0\n", debug_amplitude(fsk->phase_tab_1_0[(uint16_t)fsk->tx_bitpos65536]));
|
||||
#endif
|
||||
phase += fsk->phase_tab_1_0[(uint16_t)fsk->tx_bitpos65536];
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
|
||||
}
|
||||
}
|
||||
if (fsk->tx_bitpos >= 1.0) {
|
||||
fsk->tx_bitpos -= 1.0;
|
||||
if (fsk->tx_bitpos65536 >= 65536.0) {
|
||||
fsk->tx_bitpos65536 -= 65536.0;
|
||||
if (!fsk->filter) {
|
||||
/* change phase back to the point when bit has changed */
|
||||
phase -= fsk->tx_bitpos65536 / 65536.0 * fsk->cycles_per_bit65536[fsk->tx_bit];
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
}
|
||||
goto next_bit;
|
||||
}
|
||||
|
||||
/* post filter */
|
||||
if (fsk->filter) {
|
||||
iir_process(&fsk->lp[0], sample, length);
|
||||
iir_process(&fsk->lp[1], sample, length);
|
||||
}
|
||||
|
||||
done:
|
||||
fsk->tx_phase65536 = phase;
|
||||
|
||||
|
@ -208,11 +292,12 @@ done:
|
|||
}
|
||||
|
||||
/* reset transmitter state, so we get a clean start */
|
||||
void fsk_mod_tx_reset(fsk_mod_t *fsk)
|
||||
void fsk_mod_reset(fsk_mod_t *fsk)
|
||||
{
|
||||
fsk->tx_phase65536 = 0;
|
||||
fsk->tx_bitpos = 0;
|
||||
fsk->tx_phase65536 = 0.0;
|
||||
fsk->tx_bitpos65536 = 0.0;
|
||||
fsk->tx_bit = -1;
|
||||
fsk->tx_last_bit = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
#ifndef _LIB_FSK_H
|
||||
#define _LIB_FSK_H
|
||||
|
||||
#include "../libfm/fm.h"
|
||||
|
||||
typedef struct fsk_mod {
|
||||
void *inst;
|
||||
int (*send_bit)(void *inst);
|
||||
double bits_per_sample; /* fraction of a bit per sample */
|
||||
double bits65536_per_sample; /* fraction of a bit per sample */
|
||||
double *sin_tab; /* sine table with correct peak level */
|
||||
double *phase_tab_0_1; /* cosine shaped phase table (bit 0 to 1) */
|
||||
double *phase_tab_1_0; /* cosine shaped phase table (bit 1 to 0) */
|
||||
double phaseshift65536[2]; /* how much the phase of fsk synbol changes per sample */
|
||||
double cycles_per_bit65536[2]; /* cycles of one bit */
|
||||
double tx_phase65536; /* current transmit phase */
|
||||
|
@ -14,9 +19,9 @@ typedef struct fsk_mod {
|
|||
double f1_deviation;
|
||||
int low_bit, high_bit; /* a low or high deviation means which bit? */
|
||||
int tx_bit; /* current transmitting bit (-1 if not set) */
|
||||
double tx_bitpos; /* current transmit position in bit */
|
||||
int tx_last_bit; /* last transmitting bit (-1 if not set) */
|
||||
double tx_bitpos65536; /* current transmit position in bit */
|
||||
int filter; /* set, if filters are used */
|
||||
iir_filter_t lp[2]; /* filter to smooth transmission spectrum */
|
||||
} fsk_mod_t;
|
||||
|
||||
typedef struct fsk_demod {
|
||||
|
@ -36,8 +41,9 @@ typedef struct fsk_demod {
|
|||
int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int samplerate, double bitrate, double f0, double f1, double level, int coherent, int filter);
|
||||
void fsk_mod_cleanup(fsk_mod_t *fsk);
|
||||
int fsk_mod_send(fsk_mod_t *fsk, sample_t *sample, int length, int add);
|
||||
void fsk_mod_tx_reset(fsk_mod_t *fsk);
|
||||
void fsk_mod_reset(fsk_mod_t *fsk);
|
||||
int fsk_demod_init(fsk_demod_t *fsk, void *inst, void (*receive_bit)(void *inst, int bit, double quality, double level), int samplerate, double bitrate, double f0, double f1, double bitadjust);
|
||||
void fsk_demod_cleanup(fsk_demod_t *fsk);
|
||||
void fsk_demod_receive(fsk_demod_t *fsk, sample_t *sample, int length);
|
||||
|
||||
#endif /* _LIB_FSK_H */
|
||||
|
|
|
@ -242,7 +242,7 @@ void g711_init(void)
|
|||
g711_initialized = 1;
|
||||
}
|
||||
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
|
@ -262,7 +262,7 @@ void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
|
@ -282,7 +282,7 @@ void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
|
@ -302,7 +302,7 @@ void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data
|
|||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
|
@ -322,7 +322,7 @@ void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data
|
|||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
|
@ -342,7 +342,7 @@ void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *d
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
|
@ -362,7 +362,7 @@ void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *d
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
|
@ -382,7 +382,7 @@ void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *d
|
|||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
|
@ -402,7 +402,7 @@ void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *d
|
|||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -421,7 +421,7 @@ void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_d
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -440,7 +440,7 @@ void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -459,7 +459,7 @@ void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -478,7 +478,7 @@ void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_d
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -497,7 +497,7 @@ void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
@ -516,7 +516,7 @@ void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t
|
|||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
void g711_init(void);
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* Jitter buffering functions
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -17,6 +17,60 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* How does it work:
|
||||
*
|
||||
* Storing:
|
||||
*
|
||||
* Each saved frame is sorted into the list of packages by their sequence
|
||||
* number.
|
||||
*
|
||||
* The first packet will be stored with a delay of minimum jitter window size.
|
||||
*
|
||||
* Packets with the same sequence are dropped.
|
||||
*
|
||||
* Early packts that exceed maximum jitter window size cause jitter
|
||||
* window to shift into the future.
|
||||
*
|
||||
* Late packets cause jitter window to shift into the past (allowing more
|
||||
* delay). Minimum jitter window size is added also, to prevent subsequent
|
||||
* packets from beeing late too.
|
||||
*
|
||||
* If no sequence is provided (autosequence), the sequence number is generated
|
||||
* by a counter. Also the timestamp is generated by counting the length of each
|
||||
* frame.
|
||||
*
|
||||
* If ssrc changes, the buffer is reset.
|
||||
*
|
||||
*
|
||||
* Playout:
|
||||
*
|
||||
* The caller of the playout function can request any length of samples from
|
||||
* the packet list. The packt's time stamp and the jitter window time stamp
|
||||
* indicate what portion of a packet is already provided to the caller.
|
||||
* Complete packet, sent to the caller, are removed.
|
||||
*
|
||||
* Missing packets are interpolated by repeating last 20ms of audio (optional)
|
||||
* or by inserting zeroes (sample size > 1 byte) or by inserting 0xff (sample
|
||||
* size = 1). In case of repeating audio, the number of turns are limited until
|
||||
* buffer is reset to silence, if no frames are received for a certain time.
|
||||
*
|
||||
* Optionally the constant delay will be measured continuously and lowered if
|
||||
* greater than minimum window size. (adaptive jitter buffer size)
|
||||
*
|
||||
* Note that the delay is measured with time stamp of frame, no matter what
|
||||
* the length is. Length is an extra delay, but not considered here.
|
||||
*
|
||||
*
|
||||
* Unlocking:
|
||||
*
|
||||
* If the buffer is created or reset, the buffer is locked, so no packets are
|
||||
* stored. When the playout routine is called, the buffer is unlocked. This
|
||||
* prevents from filling the buffer before playout is performed, which would
|
||||
* cause high delay.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -26,35 +80,103 @@
|
|||
#include "../libdebug/debug.h"
|
||||
#include "jitter.h"
|
||||
|
||||
#define INITIAL_DELAY_INTERVAL 0.5
|
||||
#define REPEAT_DELAY_INTERVAL 3.0
|
||||
#define EXTRA_BUFFER 0.020 // 20 ms
|
||||
#define EXTRA_TIMEOUT 0.500 // maximum time to repeat extrapolation buffer
|
||||
|
||||
/* uncomment to enable heavy debugging */
|
||||
//#define HEAVY_DEBUG
|
||||
|
||||
static int unnamed_count = 1;
|
||||
|
||||
/* create jitter buffer */
|
||||
int jitter_create(jitter_t *jitter, int length)
|
||||
int jitter_create(jitter_t *jb, const char *name, double samplerate, int sample_size, double target_window_duration, double max_window_duration, uint32_t window_flags)
|
||||
{
|
||||
memset(jitter, 0, sizeof(*jitter));
|
||||
jitter->spl = malloc(length * sizeof(sample_t));
|
||||
if (!jitter->spl) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "No memory for jitter buffer.\n");
|
||||
return -ENOMEM;
|
||||
int rc = 0;
|
||||
memset(jb, 0, sizeof(*jb));
|
||||
jb->sample_duration = 1.0 / samplerate;
|
||||
jb->sample_size = sample_size;
|
||||
jb->target_window_size = (int)(samplerate * target_window_duration);
|
||||
jb->max_window_size = (int)(samplerate * max_window_duration);
|
||||
jb->window_flags = window_flags;
|
||||
|
||||
jb->extra_size = (int)(EXTRA_BUFFER * samplerate);
|
||||
jb->extra_samples = calloc(sample_size, jb->extra_size);
|
||||
if (!jb->extra_samples) {
|
||||
PDEBUG(DJITTER, DEBUG_ERROR, "No memory for frame.\n");
|
||||
rc = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
jitter->len = length;
|
||||
jb->extra_timeout_max = (int)ceil(EXTRA_TIMEOUT / EXTRA_BUFFER);
|
||||
|
||||
jitter_reset(jitter);
|
||||
/* optionally give a string to be show with the debug */
|
||||
if (name && *name)
|
||||
snprintf(jb->name, sizeof(jb->name) - 1, "(%s) ", name);
|
||||
else
|
||||
snprintf(jb->name, sizeof(jb->name) - 1, "(unnamed %d) ", unnamed_count++);
|
||||
|
||||
return 0;
|
||||
jitter_reset(jb);
|
||||
|
||||
PDEBUG(DJITTER, DEBUG_INFO, "%sCreated jitter buffer. (samplerate=%.0f, target_window=%.0fms, max_window=%.0fms, flag:latency=%s flag:repeat=%s)\n", jb->name, samplerate, target_window_duration * 1000.0, max_window_duration * 1000.0, (window_flags & JITTER_FLAG_LATENCY) ? "true" : "false", (window_flags & JITTER_FLAG_REPEAT) ? "true" : "false");
|
||||
|
||||
error:
|
||||
if (rc)
|
||||
jitter_destroy(jb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void jitter_reset(jitter_t *jitter)
|
||||
static void clear_extra_buffer(jitter_t *jb)
|
||||
{
|
||||
memset(jitter->spl, 0, jitter->len * sizeof(sample_t));
|
||||
|
||||
/* put write pointer ahead by half of the buffer length */
|
||||
jitter->inptr = jitter->len / 2;
|
||||
if (jb->sample_size == 1)
|
||||
memset(jb->extra_samples, 0xff, jb->sample_size * jb->extra_size);
|
||||
else
|
||||
memset(jb->extra_samples, 0, jb->sample_size * jb->extra_size);
|
||||
}
|
||||
|
||||
void jitter_destroy(jitter_t *jitter)
|
||||
/* reset jitter buffer */
|
||||
void jitter_reset(jitter_t *jb)
|
||||
{
|
||||
if (jitter->spl) {
|
||||
free(jitter->spl);
|
||||
jitter->spl = NULL;
|
||||
jitter_frame_t *jf, *temp;
|
||||
|
||||
PDEBUG(DJITTER, DEBUG_INFO, "%sReset jitter buffer.\n", jb->name);
|
||||
|
||||
/* jitter buffer locked */
|
||||
jb->unlocked = 0;
|
||||
|
||||
/* window becomes invalid */
|
||||
jb->window_valid = 0;
|
||||
|
||||
/* remove all pending frames */
|
||||
jf = jb->frame_list;
|
||||
while(jf) {
|
||||
temp = jf;
|
||||
jf = jf->next;
|
||||
free(temp);
|
||||
}
|
||||
jb->frame_list = NULL;
|
||||
|
||||
/* clear extrapolation buffer */
|
||||
if (jb->extra_samples)
|
||||
clear_extra_buffer(jb);
|
||||
jb->extra_index = 0;
|
||||
jb->extra_timeout_count = jb->extra_timeout_max; /* no data in buffer yet, so we set timeout condition */
|
||||
|
||||
/* delay measurement and reduction */
|
||||
jb->delay_counter = 0.0;
|
||||
jb->delay_interval = INITIAL_DELAY_INTERVAL;
|
||||
jb->min_delay_value = -1;
|
||||
}
|
||||
|
||||
void jitter_destroy(jitter_t *jb)
|
||||
{
|
||||
jitter_reset(jb);
|
||||
|
||||
PDEBUG(DJITTER, DEBUG_INFO, "%sDestroying jitter buffer.\n", jb->name);
|
||||
|
||||
if (jb->extra_samples) {
|
||||
free(jb->extra_samples);
|
||||
jb->extra_samples = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,64 +184,241 @@ void jitter_destroy(jitter_t *jitter)
|
|||
*
|
||||
* stop if buffer is completely filled
|
||||
*/
|
||||
void jitter_save(jitter_t *jb, sample_t *samples, int length)
|
||||
void jitter_save(jitter_t *jb, void *samples, int length, int has_sequence, uint16_t sequence, uint32_t timestamp, uint32_t ssrc)
|
||||
{
|
||||
sample_t *spl;
|
||||
int inptr, outptr, len, space;
|
||||
int i;
|
||||
jitter_frame_t *jf, **jfp;
|
||||
int16_t offset_sequence;
|
||||
int32_t offset_timestamp;
|
||||
|
||||
spl = jb->spl;
|
||||
inptr = jb->inptr;
|
||||
outptr = jb->outptr;
|
||||
len = jb->len;
|
||||
space = (outptr - inptr + len - 1) % len;
|
||||
/* ignore frames until the buffer is unlocked by jitter_load() */
|
||||
if (!jb->unlocked)
|
||||
return;
|
||||
|
||||
if (space < length)
|
||||
length = space;
|
||||
for (i = 0; i < length; i++) {
|
||||
spl[inptr++] = *samples++;
|
||||
if (inptr == len)
|
||||
inptr = 0;
|
||||
/* omit frames with no data */
|
||||
if (length < 1)
|
||||
return;
|
||||
|
||||
/* generate sequence and timestamp automatically, if enabled */
|
||||
if (!has_sequence) {
|
||||
#ifdef DEBUG_JITTER
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%sSave frame of %d samples (no seqence).\n", jb->name, length);
|
||||
#endif
|
||||
sequence = jb->next_sequence;
|
||||
jb->next_sequence++;
|
||||
timestamp = jb->next_timestamp;
|
||||
jb->next_timestamp += length;
|
||||
ssrc = jb->window_ssrc;
|
||||
} else {
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%sSave frame of %d samples (seqence=%u timestamp=%u ssrc=0x%02x).\n", jb->name, length, sequence, timestamp, ssrc);
|
||||
#endif
|
||||
jb->next_sequence = sequence + 1;
|
||||
jb->next_timestamp = timestamp + length;
|
||||
}
|
||||
|
||||
jb->inptr = inptr;
|
||||
/* first packet (with this ssrc) sets window size to target_window_size */
|
||||
if (!jb->window_valid || jb->window_ssrc != ssrc) {
|
||||
if (!jb->window_valid)
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Initial frame after init or reset.\n", jb->name);
|
||||
else
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s SSRC changed.\n", jb->name);
|
||||
// NOTE: Reset must be called before finding the frame location below, because there will be no frame in list anymore!
|
||||
jitter_reset(jb);
|
||||
jb->unlocked = 1;
|
||||
/* when using dynamic jitter buffer, we use half of the target delay */
|
||||
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
|
||||
jb->window_timestamp = timestamp - (uint32_t)jb->target_window_size / 2;
|
||||
} else {
|
||||
jb->window_timestamp = timestamp - (uint32_t)jb->target_window_size;
|
||||
}
|
||||
jb->window_valid = 1;
|
||||
jb->window_ssrc = ssrc;
|
||||
}
|
||||
|
||||
/* find location where to put frame into the list, depending on sequence number */
|
||||
jfp = &jb->frame_list;
|
||||
while(*jfp) {
|
||||
offset_sequence = (int16_t)(sequence - (*jfp)->sequence);
|
||||
/* found double entry */
|
||||
if (offset_sequence == 0) {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Dropping double packet (sequence = %d)\n", jb->name, sequence);
|
||||
return;
|
||||
}
|
||||
/* offset is negative, so we found the position to insert frame */
|
||||
if (offset_sequence < 0)
|
||||
break;
|
||||
jfp = &((*jfp)->next);
|
||||
}
|
||||
|
||||
offset_timestamp = timestamp - jb->window_timestamp;
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%sFrame has offset of %.0fms in jitter buffer.\n", jb->name, (double)offset_timestamp * jb->sample_duration * 1000.0);
|
||||
#endif
|
||||
|
||||
/* measure delay */
|
||||
if (jb->min_delay_value < 0 || offset_timestamp < jb->min_delay_value)
|
||||
jb->min_delay_value = offset_timestamp;
|
||||
|
||||
/* if frame is too early (delay ceases), shift window to the future */
|
||||
if (offset_timestamp > jb->max_window_size) {
|
||||
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too early: Shift jitter buffer to the future, to make the frame fit to the end. (offset_timestamp(%d) > max_window_size(%d))\n", jb->name, offset_timestamp, jb->max_window_size);
|
||||
/* shift window so it fits to the end of window */
|
||||
jb->window_timestamp = timestamp - jb->max_window_size;
|
||||
} else {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too early: Shift jitter buffer to the future, to make the frame fit to the target delay. (offset_timestamp(%d) > max_window_size(%d))\n", jb->name, offset_timestamp, jb->max_window_size);
|
||||
/* shift window so frame fits to the start of window + target delay */
|
||||
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* is frame is too late, shift window to the past. */
|
||||
if (offset_timestamp < 0) {
|
||||
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too late: Shift jitter buffer to the past, and add target window size. (offset_timestamp(%d) < 0)\n", jb->name, offset_timestamp);
|
||||
/* shift window so frame fits to the start of window + half of target delay */
|
||||
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size) / 2;
|
||||
} else {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too late: Shift jitter buffer to the past, and add half target window size. (offset_timestamp(%d) < 0)\n", jb->name, offset_timestamp);
|
||||
/* shift window so frame fits to the start of window + target delay */
|
||||
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* insert or append frame */
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Store frame\n", jb->name);
|
||||
#endif
|
||||
jf = malloc(sizeof(*jf) + length * jb->sample_size);
|
||||
if (!jf) {
|
||||
PDEBUG(DJITTER, DEBUG_ERROR, "No memory for frame.\n");
|
||||
return;
|
||||
}
|
||||
memset(jf, 0, sizeof(*jf)); // note: clear header only
|
||||
jf->sequence = sequence;
|
||||
jf->timestamp = timestamp;
|
||||
memcpy(jf->samples, samples, length * jb->sample_size);
|
||||
jf->length = length;
|
||||
jf->next = *jfp;
|
||||
*jfp = jf;
|
||||
}
|
||||
|
||||
/* get audio from jitterbuffer
|
||||
*/
|
||||
void jitter_load(jitter_t *jb, sample_t *samples, int length)
|
||||
void jitter_load(jitter_t *jb, void *samples, int length)
|
||||
{
|
||||
sample_t *spl;
|
||||
int inptr, outptr, len, fill;
|
||||
int i, ii;
|
||||
jitter_frame_t *jf;
|
||||
int32_t count, count2, index;
|
||||
|
||||
spl = jb->spl;
|
||||
inptr = jb->inptr;
|
||||
outptr = jb->outptr;
|
||||
len = jb->len;
|
||||
fill = (inptr - outptr + len) % len;
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%sLoad chunk of %d samples.\n", jb->name, length);
|
||||
#endif
|
||||
|
||||
if (fill < length)
|
||||
ii = fill;
|
||||
else
|
||||
ii = length;
|
||||
/* now unlock jitter buffer */
|
||||
jb->unlocked = 1;
|
||||
|
||||
/* fill what we got */
|
||||
for (i = 0; i < ii; i++) {
|
||||
*samples++ = spl[outptr++];
|
||||
if (outptr == len)
|
||||
outptr = 0;
|
||||
}
|
||||
/* on underrun, fill with silence */
|
||||
for (; i < length; i++) {
|
||||
*samples++ = 0;
|
||||
/* reduce delay */
|
||||
jb->delay_counter += jb->sample_duration * (double)length;
|
||||
if (jb->delay_counter >= jb->delay_interval) {
|
||||
if (jb->min_delay_value >= 0)
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Statistics: target_window_delay=%.0fms max_window_delay=%.0fms current min_delay=%.0fms\n", jb->name, (double)jb->target_window_size * jb->sample_duration * 1000.0, (double)jb->max_window_size * jb->sample_duration * 1000.0, (double)jb->min_delay_value * jb->sample_duration * 1000.0);
|
||||
/* delay reduction, if maximum delay is greater than target jitter window size */
|
||||
if ((jb->window_flags & JITTER_FLAG_LATENCY) && jb->min_delay_value > jb->target_window_size) {
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Reducing current minimum delay of %.0fms, because maximum delay is greater than target window size of %.0fms.\n", jb->name, (double)jb->min_delay_value * jb->sample_duration * 1000.0, (double)jb->target_window_size * jb->sample_duration * 1000.0);
|
||||
/* only reduce delay to half of the target window size */
|
||||
jb->window_timestamp += jb->min_delay_value - jb->target_window_size / 2;
|
||||
|
||||
}
|
||||
jb->delay_counter -= jb->delay_interval;
|
||||
jb->delay_interval = REPEAT_DELAY_INTERVAL;
|
||||
jb->min_delay_value = -1;
|
||||
}
|
||||
|
||||
jb->outptr = outptr;
|
||||
}
|
||||
|
||||
void jitter_clear(jitter_t *jb)
|
||||
{
|
||||
jb->inptr = jb->outptr = 0;
|
||||
/* process all frames until output buffer is loaded */
|
||||
while (length) {
|
||||
/* always get frame with the lowest sequence number (1st frame) */
|
||||
jf = jb->frame_list;
|
||||
|
||||
if (jf) {
|
||||
count = jf->timestamp - jb->window_timestamp;
|
||||
if (count > length)
|
||||
count = length;
|
||||
} else
|
||||
count = length;
|
||||
/* if there is no frame or we have not reached frame's time stamp, extrapolate */
|
||||
if (count > 0) {
|
||||
#ifdef HEAVY_DEBUG
|
||||
if (jf)
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s There is a frame ahead in buffer after %d samples. Interpolating gap.\n", jb->name, jf->timestamp - jb->window_timestamp);
|
||||
else
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s There is no frame ahead in buffer. Interpolating gap.\n", jb->name);
|
||||
#endif
|
||||
/* extrapolate by playing the extrapolation buffer */
|
||||
while (count) {
|
||||
count2 = count;
|
||||
if (count2 > jb->extra_size - jb->extra_index)
|
||||
count2 = jb->extra_size - jb->extra_index;
|
||||
memcpy(samples, (uint8_t *)jb->extra_samples + jb->extra_index * jb->sample_size, count2 * jb->sample_size);
|
||||
jb->extra_index += count2;
|
||||
if (jb->extra_index == jb->extra_size) {
|
||||
jb->extra_index = 0;
|
||||
if ((jb->window_flags & JITTER_FLAG_REPEAT) && jb->extra_timeout_count < jb->extra_timeout_max) {
|
||||
jb->extra_timeout_count++;
|
||||
if (jb->extra_timeout_count == jb->extra_timeout_max) {
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Repeated jitter buffer enough, clearing to silence.\n", jb->name);
|
||||
#endif
|
||||
clear_extra_buffer(jb);
|
||||
}
|
||||
}
|
||||
}
|
||||
samples = (uint8_t *)samples + count2 * jb->sample_size;
|
||||
length -= count2;
|
||||
jb->window_timestamp += count2;
|
||||
count -= count2;
|
||||
}
|
||||
if (length == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy samples from frame (what is not in the past) */
|
||||
index = jb->window_timestamp - jf->timestamp;
|
||||
while (index < jf->length) {
|
||||
/* use the lowest value of 'playout length' or 'remaining packet length' */
|
||||
count = length;
|
||||
if (jf->length - index < count)
|
||||
count = jf->length - index;
|
||||
/* if extrapolation is to be written, limit count to what we can store into buffer */
|
||||
if ((jb->window_flags & JITTER_FLAG_REPEAT) && jb->extra_size - jb->extra_index < count)
|
||||
count = jb->extra_size - jb->extra_index;
|
||||
/* copy samples from packet to play out, increment sample pointer and decrement length */
|
||||
#ifdef HEAVY_DEBUG
|
||||
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Copy data (offset=%u count=%u) from frame (sequence=%u timestamp=%u length=%u).\n", jb->name, index, count, jf->sequence, jf->timestamp, jf->length);
|
||||
#endif
|
||||
memcpy(samples, (uint8_t *)jf->samples + index * jb->sample_size, count * jb->sample_size);
|
||||
samples = (uint8_t *)samples + count * jb->sample_size;
|
||||
length -= count;
|
||||
/* copy frame data to extrapolation buffer also, increment index */
|
||||
if ((jb->window_flags & JITTER_FLAG_REPEAT)) {
|
||||
memcpy((uint8_t *)jb->extra_samples + jb->extra_index * jb->sample_size, (uint8_t *)jf->samples + index * jb->sample_size, count * jb->sample_size);
|
||||
jb->extra_index += count;
|
||||
if (jb->extra_index == jb->extra_size)
|
||||
jb->extra_index = 0;
|
||||
jb->extra_timeout_count = 0; /* now we have new data, we reset timeout condition */
|
||||
}
|
||||
/* increment time stamp */
|
||||
jb->window_timestamp += count;
|
||||
index += count;
|
||||
/* if there was enough to play out, we are done */
|
||||
if (length == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/* free frame, because all samples are now in the past */
|
||||
jb->frame_list = jf->next;
|
||||
free(jf);
|
||||
|
||||
/* now go for next loop, in case there is still date to play out */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,60 @@
|
|||
|
||||
#define JITTER_FLAG_NONE 0 // no flags at all
|
||||
#define JITTER_FLAG_LATENCY (1 << 0) // keep latency close to target_window_duration
|
||||
#define JITTER_FLAG_REPEAT (1 << 1) // repeat audio to extrapolate gaps
|
||||
|
||||
/* window settings for low latency audio and extrapolation of gaps */
|
||||
#define JITTER_AUDIO 0.050, 1.000, JITTER_FLAG_LATENCY | JITTER_FLAG_REPEAT
|
||||
/* window settings for analog data (fax/modem) or digial data (HDLC) */
|
||||
#define JITTER_DATA 0.100, 0.200, JITTER_FLAG_NONE
|
||||
|
||||
typedef struct jitter_frame {
|
||||
struct jitter_frame *next;
|
||||
uint16_t sequence;
|
||||
uint32_t timestamp;
|
||||
int length;
|
||||
uint8_t samples[0];
|
||||
} jitter_frame_t;
|
||||
|
||||
typedef struct jitter {
|
||||
sample_t *spl; /* pointer to sample buffer */
|
||||
int len; /* buffer size: number of samples */
|
||||
int inptr, outptr; /* write pointer and read pointer */
|
||||
char name[64];
|
||||
|
||||
/* sample properties */
|
||||
int sample_size;
|
||||
double sample_duration;
|
||||
|
||||
/* automatic sequence generation */
|
||||
uint16_t next_sequence;
|
||||
uint32_t next_timestamp;
|
||||
|
||||
/* window properties */
|
||||
int unlocked;
|
||||
uint32_t window_flags;
|
||||
int target_window_size;
|
||||
int max_window_size;
|
||||
int window_valid;
|
||||
uint32_t window_ssrc;
|
||||
uint32_t window_timestamp;
|
||||
|
||||
/* reduction of delay */
|
||||
double delay_interval;
|
||||
double delay_counter;
|
||||
int32_t min_delay_value;
|
||||
|
||||
/* extrapolation */
|
||||
int extra_size;
|
||||
int extra_index;
|
||||
void *extra_samples;
|
||||
int extra_timeout_max;
|
||||
int extra_timeout_count;
|
||||
|
||||
/* list of frames */
|
||||
jitter_frame_t *frame_list;
|
||||
} jitter_t;
|
||||
|
||||
int jitter_create(jitter_t *jitter, int length);
|
||||
void jitter_reset(jitter_t *jitter);
|
||||
void jitter_destroy(jitter_t *jitter);
|
||||
void jitter_save(jitter_t *jb, sample_t *samples, int length);
|
||||
void jitter_load(jitter_t *jb, sample_t *samples, int length);
|
||||
void jitter_clear(jitter_t *jb);
|
||||
int jitter_create(jitter_t *jb, const char *name, double samplerate, int sample_size, double target_window_duration, double max_window_duration, uint32_t window_flags);
|
||||
void jitter_reset(jitter_t *jb);
|
||||
void jitter_destroy(jitter_t *jb);
|
||||
void jitter_save(jitter_t *jb, void *samples, int length, int has_sequence, uint16_t sequence, uint32_t timestamp, uint32_t ssrc);
|
||||
void jitter_load(jitter_t *jb, void *samples, int length);
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ static int release_on_disconnect; /* release towards mobile phone, if OSMO-CC ca
|
|||
|
||||
osmo_cc_endpoint_t endpoint, *ep;
|
||||
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *arg)
|
||||
{
|
||||
uint16_t *src = (uint16_t *)src_data, *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
@ -75,7 +75,7 @@ void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len
|
|||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *arg)
|
||||
{
|
||||
uint16_t *src = (uint16_t *)src_data, *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
@ -387,7 +387,7 @@ static void process_timeout(struct timer *timer)
|
|||
}
|
||||
}
|
||||
|
||||
void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
|
||||
void down_audio(struct osmo_cc_session_codec *codec, uint8_t __attribute__((unused)) marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len)
|
||||
{
|
||||
process_t *process = codec->media->session->priv;
|
||||
sample_t samples[len / 2];
|
||||
|
@ -395,12 +395,12 @@ void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unu
|
|||
/* if we are disconnected, ignore audio */
|
||||
if (!process || process->pattern != PATTERN_NONE)
|
||||
return;
|
||||
int16_to_samples(samples, (int16_t *)data, len / 2);
|
||||
int16_to_samples_speech(samples, (int16_t *)data, len / 2);
|
||||
#ifdef DEBUG_LEVEL
|
||||
double lev = level_of(samples, len / 2);
|
||||
printf("festnetz-level: %s %.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
#endif
|
||||
call_down_audio(process->callref, samples, len / 2);
|
||||
call_down_audio(process->callref, sequence_number, timestamp, ssrc, samples, len / 2);
|
||||
}
|
||||
|
||||
static void indicate_setup(process_t *process, const char *callerid, const char *dialing, uint8_t network_type, const char *network_id)
|
||||
|
@ -621,8 +621,8 @@ void call_up_audio(int callref, sample_t *samples, int count)
|
|||
double lev = level_of(samples, count);
|
||||
printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
#endif
|
||||
samples_to_int16(data, samples, count);
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, count * 2, 1, count);
|
||||
samples_to_int16_speech(data, samples, count);
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, count * 2, 0, 1, count, process);
|
||||
/* don't destroy process here in case of an error */
|
||||
}
|
||||
|
||||
|
@ -645,7 +645,7 @@ void call_clock(void)
|
|||
printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
samples_to_int16(data, samples, 160);
|
||||
#endif
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, 160 * 2, 1, 160);
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, 160 * 2, 0, 1, 160, process);
|
||||
/* don't destroy process here in case of an error */
|
||||
}
|
||||
process = process->next;
|
||||
|
@ -935,7 +935,7 @@ void call_media_handle(void)
|
|||
|
||||
while(process) {
|
||||
if (process->session)
|
||||
osmo_cc_session_handle(process->session);
|
||||
osmo_cc_session_handle(process->session, process);
|
||||
process = process->next;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ void call_down_release(int callref, int cause);
|
|||
|
||||
/* send and receive audio */
|
||||
void call_up_audio(int callref, sample_t *samples, int count);
|
||||
void call_down_audio(int callref, sample_t *samples, int count);
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count);
|
||||
|
||||
/* clock to transmit to */
|
||||
void call_clock(void); /* from main loop */
|
||||
|
|
|
@ -89,8 +89,8 @@ static console_t console;
|
|||
|
||||
extern osmo_cc_endpoint_t *ep;
|
||||
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *arg);
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *arg);
|
||||
|
||||
static struct osmo_cc_helper_audio_codecs codecs[] = {
|
||||
{ "L16", 8000, 1, encode_l16, decode_l16 },
|
||||
|
@ -141,28 +141,26 @@ static void free_console(void)
|
|||
console.callref = 0;
|
||||
}
|
||||
|
||||
void up_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
|
||||
void up_audio(struct osmo_cc_session_codec *codec, uint8_t __attribute__((unused)) marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len)
|
||||
{
|
||||
int count = len / 2;
|
||||
sample_t samples[count];
|
||||
|
||||
/* save audio from transceiver to jitter buffer */
|
||||
if (console.sound) {
|
||||
sample_t up[(int)((double)count * console.srstate.factor + 0.5) + 10];
|
||||
int16_to_samples(samples, (int16_t *)data, count);
|
||||
count = samplerate_upsample(&console.srstate, samples, count, up);
|
||||
jitter_save(&console.dejitter, up, count);
|
||||
int16_to_samples_speech(samples, (int16_t *)data, count);
|
||||
jitter_save(&console.dejitter, samples, count, 1, sequence_number, timestamp, ssrc);
|
||||
return;
|
||||
}
|
||||
/* if echo test is used, send echo back to mobile */
|
||||
if (console.echo_test) {
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 1, count);
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 0, 1, count, &console);
|
||||
return;
|
||||
}
|
||||
/* if no sound is used, send test tone to mobile */
|
||||
if (console.state == CONSOLE_CONNECT) {
|
||||
get_test_patterns((int16_t *)data, count);
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 1, count);
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 0, 1, count, &console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +396,7 @@ int console_init(const char *audiodev, int samplerate, int buffer, int loopback,
|
|||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&console.dejitter, samplerate / 5);
|
||||
rc = jitter_create(&console.dejitter, "console", 8000, sizeof(sample_t), 0.050, 0.200, JITTER_FLAG_NONE);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init dejitter buffer!\n");
|
||||
goto error;
|
||||
|
@ -571,7 +569,7 @@ void process_console(int c)
|
|||
process_ui(c);
|
||||
|
||||
if (console.session)
|
||||
osmo_cc_session_handle(console.session);
|
||||
osmo_cc_session_handle(console.session, &console);
|
||||
|
||||
if (!console.sound)
|
||||
return;
|
||||
|
@ -580,7 +578,7 @@ void process_console(int c)
|
|||
/* handle audio, if sound device is used */
|
||||
sample_t samples[console.buffer_size + 10], *samples_list[1];
|
||||
uint8_t *power_list[1];
|
||||
int count;
|
||||
int count, input_num;
|
||||
int rc;
|
||||
|
||||
count = sound_get_tosend(console.sound, console.buffer_size);
|
||||
|
@ -591,7 +589,11 @@ void process_console(int c)
|
|||
return;
|
||||
}
|
||||
if (count > 0) {
|
||||
jitter_load(&console.dejitter, samples, count);
|
||||
/* load and upsample */
|
||||
input_num = samplerate_upsample_input_num(&console.srstate, count);
|
||||
jitter_load(&console.dejitter, samples, input_num);
|
||||
samplerate_upsample(&console.srstate, samples, input_num, samples, count);
|
||||
/* write to sound device */
|
||||
samples_list[0] = samples;
|
||||
power_list[0] = NULL;
|
||||
rc = sound_write(console.sound, samples_list, power_list, count, NULL, NULL, 1);
|
||||
|
@ -613,9 +615,9 @@ void process_console(int c)
|
|||
if (count) {
|
||||
int i;
|
||||
|
||||
if (console.loopback == 3)
|
||||
jitter_save(&console.dejitter, samples, count);
|
||||
count = samplerate_downsample(&console.srstate, samples, count);
|
||||
if (console.loopback == 3)
|
||||
jitter_save(&console.dejitter, samples, count, 0, 0, 0, 0);
|
||||
/* put samples into ring buffer */
|
||||
for (i = 0; i < count; i++) {
|
||||
console.tx_buffer[console.tx_buffer_pos] = samples[i];
|
||||
|
@ -625,8 +627,8 @@ void process_console(int c)
|
|||
/* only if we have a call */
|
||||
if (console.callref && console.codec) {
|
||||
int16_t data[160];
|
||||
samples_to_int16(data, console.tx_buffer, 160);
|
||||
osmo_cc_rtp_send(console.codec, (uint8_t *)data, 160 * 2, 1, 160);
|
||||
samples_to_int16_speech(data, console.tx_buffer, 160);
|
||||
osmo_cc_rtp_send(console.codec, (uint8_t *)data, 160 * 2, 0, 1, 160, &console);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,7 +149,13 @@ int sender_create(sender_t *sender, const char *kanal, double sendefrequenz, dou
|
|||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&sender->dejitter, samplerate / 5);
|
||||
rc = jitter_create(&sender->dejitter, sender->kanal, 8000, sizeof(sample_t), JITTER_AUDIO);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&sender->loop_dejitter, sender->kanal, samplerate, sizeof(sample_t), JITTER_AUDIO);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
|
||||
goto error;
|
||||
|
@ -288,6 +294,7 @@ void sender_destroy(sender_t *sender)
|
|||
wave_destroy_playback(&sender->wave_tx_play);
|
||||
|
||||
jitter_destroy(&sender->dejitter);
|
||||
jitter_destroy(&sender->loop_dejitter);
|
||||
}
|
||||
|
||||
/* set frequency modulation and parameters */
|
||||
|
@ -373,7 +380,7 @@ cant_recover:
|
|||
for (i = 0, inst = sender; inst; i++, inst = inst->slave) {
|
||||
/* load TX data from audio loop or from sender instance */
|
||||
if (inst->loopback == 3)
|
||||
jitter_load(&inst->dejitter, samples[i], count);
|
||||
jitter_load(&inst->loop_dejitter, samples[i], count);
|
||||
else
|
||||
sender_send(inst, samples[i], power[i], count);
|
||||
/* internal loopback: loop back TX audio to RX */
|
||||
|
@ -458,7 +465,7 @@ cant_recover:
|
|||
sender_receive(inst, samples[i], count, rf_level_db[i]);
|
||||
}
|
||||
if (inst->loopback == 3)
|
||||
jitter_save(&inst->dejitter, samples[i], count);
|
||||
jitter_save(&inst->loop_dejitter, samples[i], count, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
|
|
|
@ -73,6 +73,7 @@ typedef struct sender {
|
|||
|
||||
/* audio buffer for audio to send to transmitter (also used as loopback buffer) */
|
||||
jitter_t dejitter;
|
||||
jitter_t loop_dejitter;
|
||||
|
||||
/* audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz) */
|
||||
sample_t rxbuf[160];
|
||||
|
|
|
@ -568,6 +568,30 @@ static void notify_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
|||
forward_to_ul(call, msg);
|
||||
}
|
||||
|
||||
static void modify_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
/* to lower layer */
|
||||
forward_to_ll(call, msg);
|
||||
}
|
||||
|
||||
static void modify_cnf(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
/* to upper layer */
|
||||
forward_to_ul(call, msg);
|
||||
}
|
||||
|
||||
static void modify_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
/* to upper layer */
|
||||
forward_to_ul(call, msg);
|
||||
}
|
||||
|
||||
static void modify_rsp(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
/* to lower layer */
|
||||
forward_to_ll(call, msg);
|
||||
}
|
||||
|
||||
static void disc_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
/* change state */
|
||||
|
@ -853,6 +877,14 @@ static struct statemachine {
|
|||
OSMO_CC_MSG_INFO_IND, info_ind},
|
||||
{SBIT(OSMO_CC_STATE_ACTIVE),
|
||||
OSMO_CC_MSG_INFO_REQ, info_req},
|
||||
{SBIT(OSMO_CC_STATE_ACTIVE),
|
||||
OSMO_CC_MSG_MODIFY_REQ, modify_req},
|
||||
{SBIT(OSMO_CC_STATE_ACTIVE),
|
||||
OSMO_CC_MSG_MODIFY_CNF, modify_cnf},
|
||||
{SBIT(OSMO_CC_STATE_ACTIVE),
|
||||
OSMO_CC_MSG_MODIFY_IND, modify_ind},
|
||||
{SBIT(OSMO_CC_STATE_ACTIVE),
|
||||
OSMO_CC_MSG_MODIFY_RSP, modify_rsp},
|
||||
|
||||
/* call release */
|
||||
{SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_INIT_IN) |
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "endpoint.h"
|
||||
#include "helper.h"
|
||||
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug)
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug)
|
||||
{
|
||||
osmo_cc_session_t *session;
|
||||
osmo_cc_session_media_t *media;
|
||||
|
@ -52,7 +52,7 @@ osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, vo
|
|||
return session;
|
||||
}
|
||||
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec)
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec)
|
||||
{
|
||||
char offer_sdp[65536];
|
||||
const char *accept_sdp;
|
||||
|
@ -125,6 +125,7 @@ const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *pr
|
|||
if (!selected_codec) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No codec found in setup message that we support.\n");
|
||||
osmo_cc_free_session(*session_p);
|
||||
*session_p = NULL;
|
||||
return NULL;
|
||||
}
|
||||
osmo_cc_session_accept_codec(selected_codec, codecs[selected_codec_i].encoder, codecs[selected_codec_i].decoder);
|
||||
|
@ -138,6 +139,7 @@ const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *pr
|
|||
accept_sdp = osmo_cc_session_send_answer(*session_p);
|
||||
if (!accept_sdp) {
|
||||
osmo_cc_free_session(*session_p);
|
||||
*session_p = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ struct osmo_cc_helper_audio_codecs {
|
|||
const char *payload_name;
|
||||
uint32_t payload_rate;
|
||||
int payload_channels;
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
};
|
||||
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug);
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec);
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug);
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec);
|
||||
int osmo_cc_helper_audio_negotiate(osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p);
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#define _OSMO_CC_NAME2VALUE(array) { \
|
||||
int value; \
|
||||
for (value = 0; (size_t)value < (sizeof(array) / sizeof(array[0])); value++) { \
|
||||
if (!array[value]) \
|
||||
continue; \
|
||||
if (!strcasecmp(array[value], name)) \
|
||||
return value; \
|
||||
} \
|
||||
|
|
|
@ -30,6 +30,10 @@ enum osmo_cc_msg_type {
|
|||
OSMO_CC_MSG_NOTIFY_IND = 0x85,
|
||||
OSMO_CC_MSG_INFO_REQ = 0x88,
|
||||
OSMO_CC_MSG_INFO_IND = 0x89,
|
||||
OSMO_CC_MSG_MODIFY_REQ = 0x90,
|
||||
OSMO_CC_MSG_MODIFY_IND = 0x91,
|
||||
OSMO_CC_MSG_MODIFY_RSP = 0x92,
|
||||
OSMO_CC_MSG_MODIFY_CNF = 0x93,
|
||||
OSMO_CC_MSG_ATTACH_REQ = 0xf8,
|
||||
OSMO_CC_MSG_ATTACH_IND = 0xf9,
|
||||
OSMO_CC_MSG_ATTACH_RSP = 0xfa,
|
||||
|
|
|
@ -52,7 +52,7 @@ struct rtp_x_hdr {
|
|||
uint16_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_t *marker_p, uint8_t *pt_p, uint16_t *sequence_p, uint32_t *timestamp_p)
|
||||
static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_t *marker_p, uint8_t *pt_p, uint16_t *sequence_p, uint32_t *timestamp_p, uint32_t *ssrc_p)
|
||||
{
|
||||
static uint8_t data[2048];
|
||||
int len;
|
||||
|
@ -83,6 +83,7 @@ static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_
|
|||
payload_type = rtph->byte1 & 0x7f;
|
||||
*sequence_p = ntohs(rtph->sequence);
|
||||
*timestamp_p = ntohl(rtph->timestamp);
|
||||
*ssrc_p = ntohl(rtph->ssrc);
|
||||
|
||||
if (version != RTP_VERSION) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP version %d not supported.\n", version);
|
||||
|
@ -131,7 +132,7 @@ static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t pt, uint16_t sequence, uint32_t timestamp, uint32_t ssrc)
|
||||
static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t marker, uint8_t pt, uint16_t sequence, uint32_t timestamp, uint32_t ssrc)
|
||||
{
|
||||
struct rtp_hdr *rtph;
|
||||
char data[sizeof(*rtph) + payload_len];
|
||||
|
@ -140,7 +141,7 @@ static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t pt, ui
|
|||
rtph = (struct rtp_hdr *)data;
|
||||
len = sizeof(*rtph);
|
||||
rtph->byte0 = RTP_VERSION << 6;
|
||||
rtph->byte1 = pt;
|
||||
rtph->byte1 = pt | (marker << 7);
|
||||
rtph->sequence = htons(sequence);
|
||||
rtph->timestamp = htonl(timestamp);
|
||||
rtph->ssrc = htonl(ssrc);
|
||||
|
@ -172,7 +173,7 @@ int osmo_cc_rtp_open(osmo_cc_session_media_t *media)
|
|||
int flags;
|
||||
int rc;
|
||||
|
||||
media->rtp_ssrc = rand();
|
||||
media->tx_ssrc = rand();
|
||||
|
||||
osmo_cc_rtp_close(media);
|
||||
|
||||
|
@ -320,7 +321,7 @@ connect_error:
|
|||
}
|
||||
|
||||
/* send rtp data with given codec */
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp)
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, uint8_t marker, int inc_sequence, int inc_timestamp, void *priv)
|
||||
{
|
||||
uint8_t *payload = NULL;
|
||||
int payload_len = 0;
|
||||
|
@ -329,13 +330,13 @@ void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, in
|
|||
return;
|
||||
|
||||
if (codec->encoder)
|
||||
codec->encoder(data, len, &payload, &payload_len);
|
||||
codec->encoder(data, len, &payload, &payload_len, priv);
|
||||
else {
|
||||
payload = data;
|
||||
payload_len = len;
|
||||
}
|
||||
|
||||
rtp_send(codec->media->rtp_socket, payload, payload_len, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->rtp_ssrc);
|
||||
rtp_send(codec->media->rtp_socket, payload, payload_len, marker, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->tx_ssrc);
|
||||
codec->media->tx_sequence += inc_sequence;
|
||||
codec->media->tx_timestamp += inc_timestamp;
|
||||
|
||||
|
@ -344,7 +345,7 @@ void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, in
|
|||
}
|
||||
|
||||
/* receive rtp data for given media, return < 0, if there is nothing this time */
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media)
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media, void *priv)
|
||||
{
|
||||
int rc;
|
||||
uint8_t *payload = NULL;
|
||||
|
@ -358,7 +359,7 @@ int osmo_cc_rtp_receive(osmo_cc_session_media_t *media)
|
|||
if (!media || media->rtp_socket <= 0)
|
||||
return -EIO;
|
||||
|
||||
rc = rtp_receive(media->rtp_socket, &payload, &payload_len, &marker, &payload_type, &media->rx_sequence, &media->rx_timestamp);
|
||||
rc = rtp_receive(media->rtp_socket, &payload, &payload_len, &marker, &payload_type, &media->rx_sequence, &media->rx_timestamp, &media->rx_ssrc);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
|
@ -374,14 +375,14 @@ int osmo_cc_rtp_receive(osmo_cc_session_media_t *media)
|
|||
}
|
||||
|
||||
if (codec->decoder)
|
||||
codec->decoder(payload, payload_len, &data, &len);
|
||||
codec->decoder(payload, payload_len, &data, &len, priv);
|
||||
else {
|
||||
data = payload;
|
||||
len = payload_len;
|
||||
}
|
||||
|
||||
if (codec->media->receive)
|
||||
codec->media->receiver(codec, media->rx_sequence, media->rx_timestamp, data, len);
|
||||
codec->media->receiver(codec, marker, media->rx_sequence, media->rx_timestamp, media->rx_ssrc, data, len);
|
||||
|
||||
if (codec->decoder)
|
||||
free(data);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
void osmo_cc_set_rtp_ports(osmo_cc_session_config_t *conf, uint16_t from, uint16_t to);
|
||||
int osmo_cc_rtp_open(osmo_cc_session_media_t *media);
|
||||
int osmo_cc_rtp_connect(osmo_cc_session_media_t *media);
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp);
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media);
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, uint8_t marker, int inc_sequence, int inc_timestamp, void *priv);
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media, void *priv);
|
||||
void osmo_cc_rtp_close(osmo_cc_session_media_t *media);
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ void osmo_cc_free_session(osmo_cc_session_t *session)
|
|||
free(session);
|
||||
}
|
||||
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug)
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), int debug)
|
||||
{
|
||||
osmo_cc_session_config_t *conf = session->config;
|
||||
osmo_cc_session_media_t *media, **mediap;
|
||||
|
@ -197,7 +197,7 @@ void osmo_cc_free_media(osmo_cc_session_media_t *media)
|
|||
free(media);
|
||||
}
|
||||
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *payload_name, uint32_t payload_rate, int payload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug)
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *payload_name, uint32_t payload_rate, int payload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), int debug)
|
||||
{
|
||||
osmo_cc_session_codec_t *codec, **codecp;
|
||||
int rc;
|
||||
|
@ -374,7 +374,7 @@ osmo_cc_session_t *osmo_cc_session_receive_offer(osmo_cc_session_config_t *conf,
|
|||
return session;
|
||||
}
|
||||
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len))
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len))
|
||||
{
|
||||
osmo_cc_session_config_t *conf = media->session->config;
|
||||
|
||||
|
@ -403,7 +403,7 @@ void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_s
|
|||
}
|
||||
|
||||
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len))
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv))
|
||||
{
|
||||
codec->accepted = 1;
|
||||
codec->encoder = encoder;
|
||||
|
@ -622,14 +622,14 @@ int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, u
|
|||
&& codec->payload_channels == channels);
|
||||
}
|
||||
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session)
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session, void *codec_priv)
|
||||
{
|
||||
osmo_cc_session_media_t *media;
|
||||
int w = 0, rc;
|
||||
|
||||
osmo_cc_session_for_each_media(session->media_list, media) {
|
||||
do {
|
||||
rc = osmo_cc_rtp_receive(media);
|
||||
rc = osmo_cc_rtp_receive(media, codec_priv);
|
||||
if (rc >= 0)
|
||||
w = 1;
|
||||
} while (rc >= 0);
|
||||
|
|
|
@ -79,10 +79,10 @@ typedef struct osmo_cc_session_media {
|
|||
osmo_cc_session_connection_data_t connection_data_local, connection_data_remote;
|
||||
struct osmo_cc_session_codec *codec_list;
|
||||
int send, receive;
|
||||
void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len);
|
||||
void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len);
|
||||
int rtp_socket;
|
||||
int rtcp_socket;
|
||||
uint32_t rtp_ssrc;
|
||||
uint32_t tx_ssrc, rx_ssrc;
|
||||
uint16_t tx_sequence, rx_sequence;
|
||||
uint32_t tx_timestamp, rx_timestamp;
|
||||
int accepted;
|
||||
|
@ -96,8 +96,8 @@ typedef struct osmo_cc_session_codec {
|
|||
const char *payload_name;
|
||||
uint32_t payload_rate;
|
||||
int payload_channels;
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
|
||||
int accepted;
|
||||
} osmo_cc_session_codec_t;
|
||||
|
||||
|
@ -110,15 +110,15 @@ typedef struct osmo_cc_session_codec {
|
|||
void osmo_cc_set_local_peer(osmo_cc_session_config_t *conf, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address);
|
||||
osmo_cc_session_t *osmo_cc_new_session(osmo_cc_session_config_t *conf, void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug);
|
||||
void osmo_cc_free_session(osmo_cc_session_t *session);
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug);
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len), int debug);
|
||||
void osmo_cc_free_media(osmo_cc_session_media_t *media);
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *playload_name, uint32_t playload_rate, int playload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug);
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *playload_name, uint32_t playload_rate, int playload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), int debug);
|
||||
void osmo_cc_free_codec(osmo_cc_session_codec_t *codec);
|
||||
int osmo_cc_session_check(struct osmo_cc_session *session, int remote);
|
||||
const char *osmo_cc_session_send_offer(osmo_cc_session_t *session);
|
||||
osmo_cc_session_t *osmo_cc_session_receive_offer(osmo_cc_session_config_t *conf, void *priv, const char *sdp);
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len));
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len));
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len));
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv));
|
||||
const char *osmo_cc_session_send_answer(osmo_cc_session_t *session);
|
||||
int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp);
|
||||
const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype);
|
||||
|
@ -126,5 +126,5 @@ const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrty
|
|||
const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type);
|
||||
const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto);
|
||||
int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels);
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session);
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session, void *codec_priv);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
* of 16 bits signed value:
|
||||
*/
|
||||
static double int_16_speech_level = SPEECH_LEVEL * 0.7079; /* 16 dBm below dBm0, which is about 3dBm below full 16 bit range */
|
||||
static double int_16_1mw_level = 0.7079; /* dBm0, 3dBm below full 16 bit range */
|
||||
|
||||
/* A sample_t is a value that has virtually infinite precision but will also
|
||||
* support high numbers. 'double' or 'float' types are sufficient.
|
||||
|
@ -40,7 +41,8 @@ static double int_16_speech_level = SPEECH_LEVEL * 0.7079; /* 16 dBm below dBm0,
|
|||
* envelope is network dependent.
|
||||
*/
|
||||
|
||||
void samples_to_int16(int16_t *spl, sample_t *samples, int length)
|
||||
/* sample conversion relative to SPEECH level */
|
||||
void samples_to_int16_speech(int16_t *spl, sample_t *samples, int length)
|
||||
{
|
||||
int32_t value;
|
||||
|
||||
|
@ -55,10 +57,33 @@ void samples_to_int16(int16_t *spl, sample_t *samples, int length)
|
|||
}
|
||||
}
|
||||
|
||||
void int16_to_samples(sample_t *samples, int16_t *spl, int length)
|
||||
void int16_to_samples_speech(sample_t *samples, int16_t *spl, int length)
|
||||
{
|
||||
while (length--) {
|
||||
*samples++ = (double)(*spl++) / 32767.0 / int_16_speech_level;
|
||||
}
|
||||
}
|
||||
|
||||
/* sample conversion relative to 1mW level */
|
||||
void samples_to_int16_1mw(int16_t *spl, sample_t *samples, int length)
|
||||
{
|
||||
int32_t value;
|
||||
|
||||
while (length--) {
|
||||
value = *samples++ * int_16_1mw_level * 32768.0;
|
||||
if (value > 32767.0)
|
||||
*spl++ = 32767;
|
||||
else if (value < -32767.0)
|
||||
*spl++ = -32767;
|
||||
else
|
||||
*spl++ = (uint16_t)value;
|
||||
}
|
||||
}
|
||||
|
||||
void int16_to_samples_1mw(sample_t *samples, int16_t *spl, int length)
|
||||
{
|
||||
while (length--) {
|
||||
*samples++ = (double)(*spl++) / 32767.0 / int_16_1mw_level;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ typedef double sample_t;
|
|||
|
||||
#define SPEECH_LEVEL 0.1585
|
||||
|
||||
void samples_to_int16(int16_t *spl, sample_t *samples, int length);
|
||||
void int16_to_samples(sample_t *samples, int16_t *spl, int length);
|
||||
void samples_to_int16_speech(int16_t *spl, sample_t *samples, int length);
|
||||
void int16_to_samples_speech(sample_t *samples, int16_t *spl, int length);
|
||||
void samples_to_int16_1mw(int16_t *spl, sample_t *samples, int length);
|
||||
void int16_to_samples_1mw(sample_t *samples, int16_t *spl, int length);
|
||||
|
||||
|
|
|
@ -34,8 +34,11 @@ int init_samplerate(samplerate_t *state, double low_samplerate, double high_samp
|
|||
abort();
|
||||
}
|
||||
|
||||
iir_lowpass_init(&state->up.lp, filter_cutoff, high_samplerate, 2);
|
||||
iir_lowpass_init(&state->down.lp, filter_cutoff, high_samplerate, 2);
|
||||
state->filter_cutoff = filter_cutoff;
|
||||
if (state->filter_cutoff) {
|
||||
iir_lowpass_init(&state->up.lp, filter_cutoff, high_samplerate, 2);
|
||||
iir_lowpass_init(&state->down.lp, filter_cutoff, high_samplerate, 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -49,7 +52,8 @@ int samplerate_downsample(samplerate_t *state, sample_t *samples, int input_num)
|
|||
sample_t last_sample;
|
||||
|
||||
/* filter down */
|
||||
iir_process(&state->down.lp, samples, input_num);
|
||||
if (state->filter_cutoff)
|
||||
iir_process(&state->down.lp, samples, input_num);
|
||||
|
||||
/* get last sample for interpolation */
|
||||
last_sample = state->down.last_sample;
|
||||
|
@ -94,15 +98,60 @@ int samplerate_downsample(samplerate_t *state, sample_t *samples, int input_num)
|
|||
return output_num;
|
||||
}
|
||||
|
||||
/* convert low sample rate to high sample rate */
|
||||
int samplerate_upsample(samplerate_t *state, sample_t *input, int input_num, sample_t *output)
|
||||
int samplerate_upsample_input_num(samplerate_t *state, int output_num)
|
||||
{
|
||||
int output_num = 0, i, idx;
|
||||
double factor = 1.0 / state->factor, in_index, diff;
|
||||
sample_t buff[(int)((double)input_num / factor + 0.5) + 10]; /* add some safety */
|
||||
sample_t *samples, last_sample;
|
||||
double factor = 1.0 / state->factor, in_index;
|
||||
int idx = 0;
|
||||
|
||||
/* count output */
|
||||
in_index = state->up.in_index;
|
||||
|
||||
while (output_num--) {
|
||||
/* increment input index */
|
||||
in_index += factor;
|
||||
/* count index on overflow */
|
||||
if (in_index >= 1.0) {
|
||||
idx++;
|
||||
in_index -= 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
int samplerate_upsample_output_num(samplerate_t *state, int input_num)
|
||||
{
|
||||
double factor = 1.0 / state->factor, in_index;
|
||||
int output_num = 0, idx = 0;
|
||||
|
||||
/* count output */
|
||||
in_index = state->up.in_index;
|
||||
|
||||
while (idx < input_num) {
|
||||
/* count output number */
|
||||
output_num++;
|
||||
/* increment input index */
|
||||
in_index += factor;
|
||||
/* count index on overflow */
|
||||
if (in_index >= 1.0) {
|
||||
idx++;
|
||||
in_index -= 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return output_num;
|
||||
}
|
||||
|
||||
/* convert low sample rate to high sample rate */
|
||||
void samplerate_upsample(samplerate_t *state, sample_t *input, int input_num, sample_t *output, int output_num)
|
||||
{
|
||||
int i, idx;
|
||||
double factor = 1.0 / state->factor, in_index;
|
||||
sample_t buff[output_num];
|
||||
sample_t *samples, current_sample, last_sample;
|
||||
|
||||
/* get last sample for interpolation */
|
||||
current_sample = state->up.current_sample;
|
||||
last_sample = state->up.last_sample;
|
||||
|
||||
if (input == output)
|
||||
|
@ -112,46 +161,41 @@ int samplerate_upsample(samplerate_t *state, sample_t *input, int input_num, sam
|
|||
|
||||
/* resample input */
|
||||
in_index = state->up.in_index;
|
||||
idx = 0;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
/* convert index to int */
|
||||
idx = (int)in_index;
|
||||
/* if index is outside input sample range, we are done */
|
||||
if (idx >= input_num)
|
||||
break;
|
||||
for (i = 0; i < output_num; i++) {
|
||||
/* linear interpolation */
|
||||
diff = in_index - (double)idx;
|
||||
if (idx)
|
||||
samples[i] = input[idx - 1] * (1.0 - diff) + input[idx] * diff;
|
||||
else
|
||||
samples[i] = last_sample * (1.0 - diff) + input[idx] * diff;
|
||||
/* count output number */
|
||||
output_num++;
|
||||
samples[i] = last_sample * (1.0 - in_index) + current_sample * in_index;
|
||||
/* increment input index */
|
||||
in_index += factor;
|
||||
/* get next sample on overflow */
|
||||
if (in_index >= 1.0) {
|
||||
if (idx == input_num) {
|
||||
fprintf(stderr, "Given input_num is too small, please fix!\n");
|
||||
}
|
||||
last_sample = current_sample;
|
||||
current_sample = input[idx++];
|
||||
in_index -= 1.0;
|
||||
}
|
||||
}
|
||||
if (idx < input_num) {
|
||||
fprintf(stderr, "Given input_num is too large, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* store last sample for interpolation */
|
||||
if (input_num)
|
||||
state->up.last_sample = input[input_num - 1];
|
||||
|
||||
/* remove number of input samples from index */
|
||||
in_index -= (double)input_num;
|
||||
/* in_index cannot be negative, except due to rounding error, so... */
|
||||
if ((int)in_index < 0)
|
||||
in_index = 0.0;
|
||||
|
||||
state->up.last_sample = last_sample;
|
||||
state->up.current_sample = current_sample;
|
||||
state->up.in_index = in_index;
|
||||
|
||||
/* filter up */
|
||||
iir_process(&state->up.lp, samples, output_num);
|
||||
if (state->filter_cutoff)
|
||||
iir_process(&state->up.lp, samples, output_num);
|
||||
|
||||
if (input == output) {
|
||||
/* copy samples */
|
||||
for (i = 0; i < output_num; i++)
|
||||
*output++ = samples[i];
|
||||
}
|
||||
|
||||
return output_num;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
typedef struct samplerate {
|
||||
double factor;
|
||||
double filter_cutoff;
|
||||
struct {
|
||||
iir_filter_t lp;
|
||||
sample_t last_sample;
|
||||
|
@ -9,6 +10,7 @@ typedef struct samplerate {
|
|||
} down;
|
||||
struct {
|
||||
iir_filter_t lp;
|
||||
sample_t current_sample;
|
||||
sample_t last_sample;
|
||||
double in_index;
|
||||
} up;
|
||||
|
@ -16,4 +18,6 @@ typedef struct samplerate {
|
|||
|
||||
int init_samplerate(samplerate_t *state, double low_samplerate, double high_samplerate, double filter_cutoff);
|
||||
int samplerate_downsample(samplerate_t *state, sample_t *samples, int input_num);
|
||||
int samplerate_upsample(samplerate_t *state, sample_t *input, int input_num, sample_t *output);
|
||||
int samplerate_upsample_input_num(samplerate_t *state, int output_num);
|
||||
int samplerate_upsample_output_num(samplerate_t *state, int input_num);
|
||||
void samplerate_upsample(samplerate_t *state, sample_t *input, int input_num, sample_t *output, int output_num);
|
||||
|
|
|
@ -85,7 +85,7 @@ int dsp_init_sender(mpt1327_t *mpt1327, double squelch_db)
|
|||
mpt1327->dmp_frame_quality = display_measurements_add(&mpt1327->sender.dispmeas, "Frame Quality", "%.1f %% (last)", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, 100.0);
|
||||
|
||||
/* repeater */
|
||||
rc = jitter_create(&mpt1327->repeater_dejitter, mpt1327->sender.samplerate / 5);
|
||||
rc = jitter_create(&mpt1327->repeater_dejitter, "repeater", mpt1327->sender.samplerate, sizeof(sample_t), 0.050, 0.500, JITTER_FLAG_NONE);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to create and init repeater buffer!\n");
|
||||
goto error;
|
||||
|
@ -235,7 +235,7 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double __at
|
|||
if (mpt1327->dsp_mode == DSP_MODE_TRAFFIC) {
|
||||
/* if repeater mode, store sample in jitter buffer */
|
||||
if (mpt1327->repeater)
|
||||
jitter_save(&mpt1327->repeater_dejitter, samples, length);
|
||||
jitter_save(&mpt1327->repeater_dejitter, samples, length, 0, 0, 0, 0);
|
||||
|
||||
if (mpt1327->unit && mpt1327->unit->callref) {
|
||||
int count;
|
||||
|
@ -279,6 +279,7 @@ static int fsk_send_bit(void *inst)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
mpt1327_t *mpt1327 = (mpt1327_t *) sender;
|
||||
int input_num;
|
||||
|
||||
if (mpt1327->dsp_mode == DSP_MODE_OFF) {
|
||||
memset(power, 0, length);
|
||||
|
@ -289,7 +290,9 @@ void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length
|
|||
memset(power, 1, length);
|
||||
|
||||
if (mpt1327->dsp_mode == DSP_MODE_TRAFFIC) {
|
||||
jitter_load(&mpt1327->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
/* if repeater mode, sum samples from jitter buffer to samples */
|
||||
if (mpt1327->repeater) {
|
||||
sample_t uplink[length];
|
||||
|
|
|
@ -1642,7 +1642,7 @@ void call_down_release(int callref, __attribute__((unused)) int cause)
|
|||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
||||
{
|
||||
mpt1327_unit_t *unit;
|
||||
|
||||
|
@ -1652,11 +1652,8 @@ void call_down_audio(int callref, sample_t *samples, int count)
|
|||
if (!unit->tc)
|
||||
return;
|
||||
|
||||
if (unit->tc->state == STATE_BUSY && unit->tc->dsp_mode == DSP_MODE_TRAFFIC) {
|
||||
sample_t up[(int)((double)count * unit->tc->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&unit->tc->sender.srstate, samples, count, up);
|
||||
jitter_save(&unit->tc->sender.dejitter, up, count);
|
||||
}
|
||||
if (unit->tc->state == STATE_BUSY && unit->tc->dsp_mode == DSP_MODE_TRAFFIC)
|
||||
jitter_save(&unit->tc->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
||||
}
|
||||
|
||||
void dump_info(void)
|
||||
|
|
|
@ -139,7 +139,7 @@ static struct nmt_country {
|
|||
{ 450, 1, 7, 1,9, "PL", "Poland", "CENTERTEL", frq_450_scandinavia },
|
||||
{ 450, 1, 6, 1,9, "BG", "Bulgaria", "MOBIFON", frq_450_scandinavia },
|
||||
{ 450, 1, 5, 1,9, "RO", "Romania", "Telefonica Romania", frq_450_scandinavia },
|
||||
{ 450, 1, 6, 1,9, "UA", "Ukraine", "Ukraine Mobile Comm.", frq_450_scandinavia },
|
||||
{ 450, 1, 8, 1,9, "UA", "Ukraine", "Ukrainian Mobile Communications", frq_450_scandinavia },
|
||||
{ 450, 1, 1, 1,9, "RU1", "", "", frq_450_scandinavia },
|
||||
{ 450, 1, 2, 1,9, "RU2", "", "", frq_450_scandinavia },
|
||||
{ 450, 1, 3, 1,9, "RU3", "", "", frq_450_scandinavia },
|
||||
|
|
|
@ -356,16 +356,20 @@ void sender_receive(sender_t *sender, sample_t *samples, int length, double __at
|
|||
|
||||
if ((nmt->dsp_mode == DSP_MODE_AUDIO || nmt->dsp_mode == DSP_MODE_DTMF)
|
||||
&& nmt->trans && nmt->trans->callref) {
|
||||
int count;
|
||||
int len, count;
|
||||
|
||||
count = samplerate_downsample(&nmt->sender.srstate, samples, length);
|
||||
len = samplerate_downsample(&nmt->sender.srstate, samples, length);
|
||||
if (nmt->compandor)
|
||||
expand_audio(&nmt->cstate, samples, count);
|
||||
if (nmt->dsp_mode == DSP_MODE_DTMF)
|
||||
dtmf_encode(&nmt->dtmf, samples, count);
|
||||
expand_audio(&nmt->cstate, samples, len);
|
||||
if (nmt->dsp_mode == DSP_MODE_DTMF) {
|
||||
/* encode and fill with silence after finish */
|
||||
count = dtmf_encode(&nmt->dtmf, samples, len);
|
||||
if (count < len)
|
||||
memset(samples + count, 0, sizeof(*samples) * (len - count));
|
||||
}
|
||||
spl = nmt->sender.rxbuf;
|
||||
pos = nmt->sender.rxbuf_pos;
|
||||
for (i = 0; i < count; i++) {
|
||||
for (i = 0; i < len; i++) {
|
||||
spl[pos++] = samples[i];
|
||||
if (pos == 160) {
|
||||
call_up_audio(nmt->trans->callref, spl, 160);
|
||||
|
@ -446,7 +450,7 @@ static void dial_tone(nmt_t *nmt, sample_t *samples, int length)
|
|||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
nmt_t *nmt = (nmt_t *) sender;
|
||||
int count;
|
||||
int count, input_num;
|
||||
|
||||
memset(power, 1, length);
|
||||
|
||||
|
@ -454,7 +458,9 @@ again:
|
|||
switch (nmt->dsp_mode) {
|
||||
case DSP_MODE_AUDIO:
|
||||
case DSP_MODE_DTMF:
|
||||
jitter_load(&nmt->sender.dejitter, samples, length);
|
||||
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
||||
jitter_load(&sender->dejitter, samples, input_num);
|
||||
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
||||
/* send after dejitter, so audio is flushed */
|
||||
if (nmt->dms.tx_frame_valid) {
|
||||
fsk_mod_send(&nmt->fsk_mod, samples, length, 0);
|
||||
|
@ -509,7 +515,7 @@ void nmt_set_dsp_mode(nmt_t *nmt, enum dsp_mode mode)
|
|||
{
|
||||
/* reset frame */
|
||||
if (mode == DSP_MODE_FRAME && nmt->dsp_mode != mode) {
|
||||
fsk_mod_tx_reset(&nmt->fsk_mod);
|
||||
fsk_mod_reset(&nmt->fsk_mod);
|
||||
nmt->tx_frame_length = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -315,6 +315,7 @@ static struct nmt_frame {
|
|||
{ NMT_MESSAGE_4b, "NNNPYYJJJJJJJHHH", MTX_TO_MS, 7, "4b", "Access channel indication" },
|
||||
{ NMT_MESSAGE_5a, "NNNPYYZXXXXXXLLL", MTX_TO_MS, 6, "5a", "Line signal" },
|
||||
{ NMT_MESSAGE_5b, "NNNPYYZXXXXXXLQQ", MTX_TO_MS, 6, "5b", "Line signal: Answer to coin-box" },
|
||||
{ NMT_MESSAGE_5c, "NNNPYYZXXXXXXFFF", MTX_TO_MS, 2, "5c", "Line signal: Message(s) waiting" },
|
||||
{ NMT_MESSAGE_6, "JJJPJJJJJJJJJJJJ", MTX_TO_XX, 0, "6", "Idle frame" },
|
||||
{ NMT_MESSAGE_7, "NNNPYYCCCCCCCJJJ", MTX_TO_MS, 8, "7", "Authentication request" },
|
||||
{ NMT_MESSAGE_8, "NNNPYYMHHHHHHHWW", MTX_TO_MS, 1, "8", "A-subscriber number" },
|
||||
|
@ -323,7 +324,7 @@ static struct nmt_frame {
|
|||
{ NMT_MESSAGE_10c, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 6, "10c", "Seizure and identity from called MS on traffic channel" },
|
||||
{ NMT_MESSAGE_10d, "NNNPZXXXXXXTJJJJ", MS_TO_MTX, 6, "10d", "Call acknowledgement from MS on the alternative type of call on combined CC/TC (shortened frame)" },
|
||||
{ NMT_MESSAGE_11a, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 14, "11a", "Roaming updating seizure and identity on traffic channel" },
|
||||
{ NMT_MESSAGE_11b, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 15, "11b", "Seizure and call achnowledgment on calling channel from MS with priority (shortened frame)" },
|
||||
{ NMT_MESSAGE_11b, "NNNPZXXXXXXTYJJJ", MS_TO_MTX, 15, "11b", "Seizure and call achnowledgment on calling channel from MS with priority (shortened frame)" },
|
||||
{ NMT_MESSAGE_12, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 11, "12", "Seizure from coin-box on traffic channel" },
|
||||
{ NMT_MESSAGE_13a, "NNNPZXXXXXXLLLLL", MS_TO_MTX, 8, "13a", "Line signal" },
|
||||
{ NMT_MESSAGE_13b, "NNNPZXXXXXXLLLQQ", MS_TO_MTX, 8, "13b", "Line signal: Answer acknowledgment from coin box" },
|
||||
|
@ -348,7 +349,7 @@ static struct nmt_frame {
|
|||
{ NMT_MESSAGE_28, "NNNPZJJVVVVJJJJJ", BS_TO_MTX, 13, "28", "Other maintenance information from BS" },
|
||||
{ NMT_MESSAGE_30, "NNNPYYJJJJJJJHHH", MTX_TO_MS, 10, "30", "Test channel indication" },
|
||||
{ NMT_MESSAGE_UKN_MTX, "---P------------", MTX_TO_XX, 0, "", "illegal (Spare)" },
|
||||
{ NMT_MESSAGE_UKN_B, "---P------------", XX_TO_MTX, 0, "", "illegal (Spare)" },
|
||||
{ NMT_MESSAGE_UKN_BS_MS,"---P------------", XX_TO_MTX, 0, "", "illegal (Spare)" },
|
||||
{ 0, NULL, 0, 0, NULL, NULL }
|
||||
};
|
||||
|
||||
|
@ -619,6 +620,27 @@ static const char *param_password(uint64_t value, int ndigits, enum nmt_directio
|
|||
return result;
|
||||
}
|
||||
|
||||
static const char *param_waiting(uint64_t value, int __attribute__((unused)) ndigits, enum nmt_direction __attribute__((unused)) direction)
|
||||
{
|
||||
static char result[128];
|
||||
|
||||
if (value & 0x01)
|
||||
strcat(result, "SMS ");
|
||||
if (value & 0x02)
|
||||
strcat(result, "voice-mail ");
|
||||
if (value & 0x04)
|
||||
strcat(result, "fax ");
|
||||
if (value & 0x08)
|
||||
strcat(result, "e-mail ");
|
||||
if (value & 0x10)
|
||||
strcat(result, "data ");
|
||||
if (value & 0x20)
|
||||
strcat(result, "(spare) ");
|
||||
strcat(result, " is waiting");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct nmt_parameter {
|
||||
int system;
|
||||
char digit;
|
||||
|
@ -633,7 +655,7 @@ static struct nmt_parameter {
|
|||
{ 900, 'Y', "Traffic area", param_ta_900 },
|
||||
{ 0, 'Z', "Mobile subscriber country", param_country },
|
||||
{ 0, 'X', "Mobile subscriber No.", param_number },
|
||||
{ 0, 'Q', "Tariff class", param_integer },
|
||||
{ 0, 'Q', "Tariff class", param_hex },
|
||||
{ 0, 'L', "Line signal", param_line_signal },
|
||||
{ 0, 'S', "Digit signals", param_digit },
|
||||
{ 0, 'J', "Idle information", param_hex },
|
||||
|
@ -651,6 +673,7 @@ static struct nmt_parameter {
|
|||
{ 0, 'c', "c", param_hex },
|
||||
{ 0, 'M', "Sequence Number", param_integer },
|
||||
{ 0, 'W', "Checksum", param_hex },
|
||||
{ 0, 'F', "Addit. Info.", param_waiting },
|
||||
{ 0, 0, NULL, NULL }
|
||||
};
|
||||
|
||||
|
@ -714,7 +737,7 @@ enum nmt_mt decode_frame_mt(const uint8_t *digits, enum nmt_direction direction,
|
|||
case 15:
|
||||
return NMT_MESSAGE_11b;
|
||||
}
|
||||
return NMT_MESSAGE_UKN_B;
|
||||
return NMT_MESSAGE_UKN_BS_MS;
|
||||
} else {
|
||||
/* MTX to MS/BS */
|
||||
switch (digits[3]) {
|
||||
|
@ -723,7 +746,7 @@ enum nmt_mt decode_frame_mt(const uint8_t *digits, enum nmt_direction direction,
|
|||
case 1:
|
||||
return NMT_MESSAGE_8;
|
||||
case 2:
|
||||
break;
|
||||
return NMT_MESSAGE_5c;
|
||||
case 3:
|
||||
if (digits[6] == 15)
|
||||
return NMT_MESSAGE_21b;
|
||||
|
@ -956,6 +979,9 @@ static void disassemble_frame(int nmt_system, frame_t *frame, const uint8_t *dig
|
|||
case 'W':
|
||||
frame->checksum = value;
|
||||
break;
|
||||
case 'F':
|
||||
frame->waiting_info = value;
|
||||
break;
|
||||
default:
|
||||
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' does not exist, please fix!\n", digit);
|
||||
abort();
|
||||
|
@ -1083,6 +1109,9 @@ static void assemble_frame(int nmt_system, frame_t *frame, uint8_t *digits, int
|
|||
case 'W':
|
||||
value = frame->checksum;
|
||||
break;
|
||||
case 'F':
|
||||
value = frame->waiting_info;
|
||||
break;
|
||||
default:
|
||||
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' does not exist, please fix!\n", digit);
|
||||
abort();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue