initial git import

This commit is contained in:
Andreas Eversberg 2016-03-01 18:40:38 +01:00
commit 946c9ce10a
84 changed files with 12959 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
config.guess
config.log
config.status
config.sub
configure
depcomp
install-sh
libcolorize.pc
libtool
ltmain.sh
missing
*.o
*.lo
*.la
.deps
.libs
m4
src/common/libcommon.a
src/anetz/anetz
src/bnetz/bnetz

3
Makefile.am Normal file
View File

@ -0,0 +1,3 @@
AUTOMAKE_OPTIONS = foreign
SUBDIRS = src

11
README Normal file
View File

@ -0,0 +1,11 @@
This software implements base station protocol of classic mobile phones. With
radio transmitter and receiver connected to one sound card and a headset
connected to another sound card, it is possible to make and receive calls to
and from mobile phone. Currently supported networks:
* A-Netz base station
* B-Netz (ATF-1) base station
USE AT YOUR OWN RISK!

36
configure.ac Normal file
View File

@ -0,0 +1,36 @@
dnl Process this file with autoconf to produce a configure script
AC_INIT([abcnetz],
m4_esyscmd([./git-version-gen .tarball-version]),
[authors@their.domains])
AM_INIT_AUTOMAKE([dist-bzip2])
AC_CONFIG_MACRO_DIR([m4])
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
LT_INIT
dnl checks for header files
AC_HEADER_STDC
dnl Checks for typedefs, structures and compiler characteristics
AC_CANONICAL_HOST
PKG_CHECK_MODULES(ALSA, alsa >= 1.0)
AC_OUTPUT(
src/common/Makefile
src/anetz/Makefile
src/bnetz/Makefile
src/Makefile
Makefile)

BIN
docs/a-netz-display.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

80
docs/a-netz.html Normal file
View File

@ -0,0 +1,80 @@
<html>
<head>
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>A-Netz</center></h2>
<center><img src="a-netz.jpg"/></center>
<p align='justify'>
Now run your base station on channel 30.
Tune the transmitter to 162.050 MHz and the receiver to 157.550.
You should tune the receiver to 164.050 first, to check if you hear the idle signal from the base station.
Then tune to actually uplink frequency 157.550 MHz.
</p>
<font size='4'><pre>
# src/anetz/anetz -k 30
...
anetz.c:170 info : Entering IDLE state, sending 2280 Hz tone.
Base station ready, please tune transmitter to 162.050 MHz and receiver to 157.550 MHz.
on-hook: ..... (enter 0..9 or d=dial)
</pre></font>
<center><img src="a-netz-display.jpg"/></center>
<p align='justify'>
Turn on you phone and wait for it to warm up (requires about half a minute).
Tune the phone to channel 30 and switch on your transmitter for the base station.
The phone should indicate a (green) light, to show idle channel.
If there is no green light, increase the level of your FM signal on the transmitter.
Or if you can't increase the audio level on the transmitter, do it with 'alsamixer'.
</p>
<font size='4'><pre>
anetz.c:244 info : Received 1750 Hz calling signal from mobile station, sending 1750 Hz acknowledge signal.
anetz.c:256 info : 1750 Hz signal from mobile station is gone, stopping acknowledge signal.
call.c:574 info : Incomming call from '' to 'operator'
anetz.c:272 info : Received 1750 Hz release signal from mobile station, sending idle signal.
anetz.c:201 info : Entering IDLE state, sending 2280 Hz tone.
call.c:695 info : Call has been released with cause=16
call disconnected: hangup (enter h=hangup)
</pre></font>
<p align='justify'>
When you pick up the phone, the phone transmits a 1750 Hz calling tone.
On reception, the base station then transmits a 1750 Hz acknowledge ton.
After that the call is establised.
After establishment, you can use the headset, if present, for speech communication with the phone.
On hangup, the phone transmit a 1750 Hz hangup tone.
Then the base station returns to idle again.
Be sure that the phone turns off the transmitter and indicates the (green) light.
</p>
<p align='justify'>
To call to the phone, be sure that your transmitter transmits loud enough to send 4 sine waves at once.
Enter the last 5 digits of the phone's number and press 'd' to dial.
If you listen to the transmit signal, you should hear 4 low pitched tones at once.
The phone should now stop the idle light and indicate an incomming call.
There is no acknowledgement from the phone until you pick up the call.
If the phone does not indicate an incomming call, increase the volume of the transmit signal, but be sure not to overdrive it.
Also be sure that you are actually dialing the right number, so the base station generates the correct paging tones.
</p>
<p align='justify'>
Instead of transmitting all 4 tones at once, they can be transmitted after each other.
Each tone is playes for a short time.
After the last tone has been played, base station starts again with the first tone.
My phone also responds to a call, even if the tones cycle rather than sent simultaniously.
In this case the level of each tone is four times highter (+6 dB).
Add command line option "-P 50" to send each tone for 50 milliseconds.
Try something between 20-100 milliseconds, if the phone still doesn't ring.
</p>
[<a href="index.html">Back to main page</a>]
</font></td></tr></table></center>
</body>
</html>

BIN
docs/a-netz.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

BIN
docs/a-netz_small.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
docs/alsa-mic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/alsa-mute.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/alsa-select.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/alsa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/b-netz-display.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

109
docs/b-netz.html Normal file
View File

@ -0,0 +1,109 @@
<html>
<head>
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>B-Netz</center></h2>
<center><img src="b-netz.jpg"/></center>
<p align='justify'>
Before testing this software, power on your B-Netz.
Refer to the manual how to dial a number.
Start dialing and after some seconds you should hear a busy signal.
This means that the phone sweeps over all channels to find a base startion.
If no free base station is found, you will get a busy signal.
</p>
<center><img src="b-netz-display.jpg"/></center>
<p align='justify'>
Now run your base station on channel 1.
Tune the transmitter to 153.010 MHz and the receiver to 148.410.
You should tune the receiver to 153.010 first, to check if you hear the idle signal from the base station.
Then tune to actually uplink frequency 148.410 MHz.
</p>
<font size='4'><pre>
# src/bnetz/bnetz -k 1
...
bnetz.c:316 info : Entering IDLE state, sending 'Gruppenfreisignal' 2 on channel 1.
Base station ready, please tune transmitter to 153.010 MHz and receiver to 148.410 MHz.
To call phone, switch transmitter (using pilot signal) to 153.370 MHz.
on-hook: ..... (enter 0..9 or d=dial)
</pre></font>
<p align='justify'>
Now dial again on the phone and watch the base station receiving the call:
</p>
<font size='4'><pre>
bnetz.c:471 info : Received signal 'Kanalbelegung' from mobile station, sending signal 'Wahlabruf'.
bnetz.c:561 info : Received station id from mobile phone: 50993
bnetz.c:569 info : Received number from mobile phone: 800330100
bnetz.c:571 info : Sending station id back to phone: 50993.
bnetz.c:631 info : Dialing complete 50993-&gt;0800330100, call established.
bnetz.c:637 info : Setup call to network.
call.c:574 info : Incomming call from '50993' to '0800330100'
bnetz.c:669 notice : Received 'Schlusssignal' from mobile station
bnetz.c:352 info : Entering IDLE state, sending 'Gruppenfreisignal' 2.
call.c:695 info : Call has been released with cause=16
</pre></font>
<p align='justify'>
The first thing the phone does is to find the channel 1.
Then it transmits a signal tone, called 'Kanalbelegung'.
The base station responds and sends a signal tone, called 'Wahlabruf'.
Then the phone sends caller ID + number.
The base station replies the caller ID to prevent false transmissions.
After establishment, you can use the headset, if present, for speech communication with the phone.
If you hangup the phone, the call gets released by a message, called 'Schlusssignal'.
The base station returns to idle.
</p>
<p align='justify'>
In order to call the phone from the base station, you need to transmit channel 19.
Your transmitter must tune to 153.370 MHz in order to page the phone.
The phones listens to incomming signals from the base station.
In order to transmit on channel 19, you may use a second transmitter or re-tune your single transmitter.
There are many ways todo that, but it is actually up to your own how to couple it and how to control your transmitter.
I use an optocoupler to tell my transmitter to switch to channel 19.
</p>
<center><img src="trigger-1.jpg"/></center>
<br>
<center><img src="trigger-2.jpg"/><img src="trigger-3.jpg"/></center>
<p align='justify'>
I measure about 3 Volts peak on the ouput of the USB chip I use.
Since my optocoupler triggers at arround 1 Volts, I have two Volts on the Resistor, which results in 10 mA current.
In order to check and change the voltage, use '-P positive' or '-P negative' option to select trigger level on one audio channel.
Run the base station and enter a 5 digit number.
Measure the voltage on both audio output channels.
Once you press 'd' for dialing, the base station triggers the channel using positive or negative level.
See if and where the voltage changes.
The trigger is just about two seconds long, so check your meter quickly after pressing 'd'.
Once the base station timed out, press 'h' for hangup and try again.
</p>
<font size='4'><pre>
./bnetz/bnetz -k 1 -P positive
...
on-hook: 55555 (enter 0..9 or d=dial)
call.c:437 info : Outgoing call to 55555
bnetz.c:757 info : Call to mobile station, paging station id '55555'
bnetz.c:375 info : Entering paging state (try 1), sending 'Selektivruf' to '55555'.
bnetz.c:411 info : Paging mobile station 55555 complete, waiting for answer.
</pre></font>
<p align='justify'>
Now again it is up to your own to make the transmitter switch to channel 19 on trigger level.
If you use a second transmitter, use the tigger to press the PTT button.
</p>
[<a href="index.html">Back to main page</a>]
</font></td></tr></table></center>
</body>
</html>

BIN
docs/b-netz.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

BIN
docs/b-netz_small.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/coil.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

BIN
docs/dummyload.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
docs/headphone.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

92
docs/headset.html Normal file
View File

@ -0,0 +1,92 @@
<html>
<head>
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>Connecting headset</center></h2>
<p align='justify'>
You need a headset and a second audio device.
I use a cheap 'LogiLink' USB sound adapter and a Headset with microphone.
</p>
<center><img src="headset.jpg"/></center>
<p align='justify'>
You may use an USB sound adapter and a headset or a USB headset with built-in sound card.
Check 'alsamixer' if the sound adapter has been detected.
Press 'F6' to select the headset or the sound adapter connected to the headset.
</p>
<center><img src="alsa-select.png"/></center>
<p align='justify'>
Mute the microphone, so you wount hear yourself if you speak into the microphone.
Be sure to press 'F3' to change into Playback view.
Mute playback and not recording from microphone.
To mute, select he microphone with the cursor keys and press 'm'.
</p>
<center><img src="alsa-mute.png"/></center>
<p align='justify'>
To get the device, run 'arecord -l' and you get the following list, if you have two sound cards:
</p>
<font size='4'><pre>
# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: Intel [HDA Intel], device 0: ALC269VB Analog [ALC269VB Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 2: Set [C-Media USB Headphone Set], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
</pre></font>
<p align='justify'>
You see your card 0 and device 0: This should be the sound card you connect to your radio equipment.
There is also the second sound device: In my case it is card 2 and device 0.
Connect the second sound device to your headset and use '-c hw:2,0" (card 2, device 0) to tell the base station to select this sound device for your headset.
</p>
<p align='justify'>
To calibrate audio level, run some music player and adjust output volume, so you hear the music at regular speech volume.
Use 'alsamixer' to change the output volume of your second sound card.
This calibartion must be done before calibrating the microphone.
To calibarte input level (mircrophone), run the B-Netz base station with loopback test 3.
</p>
<font size='4'><pre>
# src/bnetz/bnetz -L 3 -c hw:2,0
bnetz.c:268 info : Entering IDLE state, sending 'Gruppenfreisignal' 2 on channel 1.
Base station ready, please tune transmitter to 153.010 MHz and receiver to 148.410 MHz.
To call phone, switch transmitter using pilot tone to 153.370 MHz
</pre></font>
<p align='justify'>
Now you should hear yourself when you speak into the microphone.
You should also notice a short delay, but this is normal.
Now it is time to calibrate the microphone:
Use 'alsamixer' to adjust input level, so that you hear your voice at regular speech volume.
Be sure to select Capture view by pressing 'F4'.
</p>
<center><img src="alsa-mic.png"/></center>
<p align='justify'>
I also had to mute the "Auto Gain Control", as found in the Playback View by pressing 'F4'.
I muted it using 'm' key.
</p>
<p align='justify'>
Finally store the settings using "alsactl store" command.
Do this whenever you want to keep your adjustments.
</p>
[<a href="index.html">Back to main page</a>]
</font></td></tr></table></center>
</body>
</html>

BIN
docs/headset.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

31
docs/index.html Normal file
View File

@ -0,0 +1,31 @@
<html>
<head>
<link href="style.css" rel="stylesheet" type="text/css" />
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>osmocom-analog</center></h2>
<center><img src="a-netz_small.jpg"/><img src="b-netz_small.jpg"/></center>
<br><br>
<center>
A base station emulator for classic mobile networks. All these networks use analog voice transmission. The signalling is done by tones (A-Netz) or FSK modulated digital messages.
</center>
<center>
<ul>
<li><a href="install.html">Software installation</a></li>
<li><a href="setup.html">Radio setup</a></li>
<li><a href="headset.html">Connecting headset</a></li>
<li><a href="a-netz.html">Using A-Netz phone</a></li>
<li><a href="b-netz.html">Using B-Netz phone</a></li>
</ul>
</center>
</font></td></tr></table></center>
</body>
</html>

59
docs/install.html Normal file
View File

@ -0,0 +1,59 @@
<html>
<head>
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>Software installation</center></h2>
<p align='justify'>
To run this software, you need a linux PC with development environment (gcc compiler).
At least one Alsa sound interfaces is required.
Two sound interfaces are required to talk and listen trough the base station using a headset with microphone.
</p>
<p align='justify'>
If you want to install from GIT repository, run 'autoreconf -if' inside GIT repository first:
</p>
<font size='4'><pre>
# autoreconf -if
</pre></font>
<p align='justify'>
Unpack the archive and change to its directory. Then compile:
</p>
<font size='4'><pre>
# ./configure
# make
</pre></font>
<p align='justify'>
At your option:
</p>
<font size='4'><pre>
# make install
</pre></font>
<p align='justify'>
Now you are ready for a quick test:
</p>
<font size='4'><pre>
# src/bnetz/bnetz -k 1 -L 2
bnetz.c:268 info : Entering IDLE state, sending 'Gruppenfreisignal' 2 on channel 1.
Base station ready, please tune transmitter to 153.010 MHz and receiver to 148.410 MHz.
To call phone, switch transmitter using pilot tone to 153.370 MHz
</pre></font>
<p align='justify'>
Do you hear the whisteling sound on your speaker/headset? Now you can continue with the radio setup.
</p>
[<a href="index.html">Back to main page</a>]
</font></td></tr></table></center>
</body>
</html>

BIN
docs/microphone.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

BIN
docs/pc+sound.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

BIN
docs/power.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
docs/receiver.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

144
docs/setup.html Normal file
View File

@ -0,0 +1,144 @@
<html>
<head>
<title>osmocom-analog</title>
</head>
<body>
<center><table border='0' cellspacing='0' cellpadding='0' width='816'><tr><td><font face="ARIAL" size='5'>
<h2><center>Radio setup</center></h2>
<p align='justify'>
To emulate a base station, you need a linux machine with sound interface. The interface must be supported by <a href="http://www.alsa-project.org">Alsa</a> kernel driver.
</p>
<center><img src="pc+sound.jpg"/></center>
<p align='justify'>
Use a radio transmitter for 2-meter band to transmit to the phone (downlink).
An amateur radio can be used, but be sure it is not limited to amateur radio frequencies.
For A-Netz the transmitter must be able to transmit arround 162 MHz.
For B-Netz the transmitter must be able to transmit arround 153 MHz.
</p>
<center><img src="transmitter.jpg"/></center>
<p align='justify'>
Use radio receiver for 2-methers band to receive from the phone. (uplink)
You cannot use the transmitter as well, since you need to transmit and receive simultaniously.
For A-Netz the receiver must be able to receive arround 162 MHz and 157 MHz.
For B-Netz the receiver must be able to receive arround 153 MHz and 148 MHz.
The reason why the receiver must also receive on the transmitter frequency (downlink) is because you need to make a test loop for calibration process.
This is explained below.
</p>
<center><img src="receiver.jpg"/></center>
<p align='justify'>
In order to keep RF emission low, use a dummy load for lab test.
Use two seperated antennas for outside use of the mobile phone.
Connect one dummy load to the transmitter and another one to the mobile phone's antenna connector.
</p>
<center><img src="dummyload.jpg"/></center>
<p align='justify'>
For the radios and the phone, I use a sufficient power supply.
Especially for old A-Netz phones, be sure to have something close to 13.8 Volts and 5 Ampere or even more, depending on your phone.
My phone did not work correctly with a standard 12 Volts regulated power supply.
Amateur radio power supplies are made for 13.8 Volts.
Also you can use a fully charged car battery with a sufficient fuse.
In my case I use a modified ATX-2 power supply with 13.8 Volts and a built-in overcurrent protection.
</p>
<center><img src="power.jpg"/></center>
<p align='justify'>
And finally you need a classic working phone.
Be sure to connect the phone to a dummy load and the transmitter to another one.
</p>
<center><img src="b-netz-display.jpg"/></center>
<p align='justify'>
There are two ways to couple the receiver and transmitter with your sound card. You can use a headset and a microphone. The pro is that you can monitor what you actually transmitting and receiving. The problem is that you might get audio from RX side mixed with TX side. You get echo and the base station may detect self-generated tones. This might cause malfuntion to the base station software. Better you put the microphone inside a headset and keep TX and RX side away from each other.
</p>
<center><img src="microphone.jpg"/></center>
<p align='justify'>
The better way is to use inductive coil and resistors.
How to couple your sound card with radios is beyond the scope of this document.
Because you have radios and all the electronics that is required, I assume you have the knowledge to do it right.
</p>
<center><img src="coil.jpg"/></center>
<p align='justify'>
To adjust input and output levels of your sound card, run 'alsamixer'.
</p>
<center><img src="alsa.png"/></center>
<p align='justify'>
To avoid echo of audio input (mic), mute the input (select item and press 'm').
We want to capture microphone, but not echo it back to the audio output.
Also we want audio on line/headset output and capture from microphone imput.
</p>
<p align='justify'>
Now we want to calibrate transmitter and receiver audio level.
Run the B-Netz base station in loopback test mode (-L 2).
Even if you plan to setup A-Netz base station, use B-Netz base station for calibration.
</p>
<font size='4'><pre>
# src/bnetz/bnetz -k 1 -L 2
bnetz.c:268 info : Entering IDLE state, sending 'Gruppenfreisignal' 2 on channel 1.
Base station ready, please tune transmitter to 153.010 MHz and receiver to 148.410 MHz.
To call phone, switch transmitter using pilot tone to 153.370 MHz
</pre></font>
<p align='justify'>
Tune your transmitter AND reciever to 153.010 MHz.
Press the PTT button on you transmitter and check if you hear your voice at a normal level from the receiver.
Then connect the audio output (line out) to your transmitter or couple it with a headphone.
You should hear now the whistle sound clearly on the receiver.
Adjust the audio output, so that the tone is not overdriven but loud enough to match any regular received voice.
</p>
<p align='justify'>
Now connect the audio input (microphone) to your receiver or couple it with a microphone to the speaker of your receiver.
Enable the transmitter. (PTT button)
You should now see the signal beeing decoded by the base station:
</p>
<font size='4'><pre>
bnetz.c:474 notice : Received telegramm 'Ziffer 2'. (quality=99% level=33%)
bnetz.c:478 notice : Round trip delay is 0.053 seconds
bnetz.c:474 notice : Received telegramm 'Ziffer 3'. (quality=98% level=33%)
bnetz.c:478 notice : Round trip delay is 0.054 seconds
bnetz.c:474 notice : Received telegramm 'Ziffer 4'. (quality=99% level=31%)
bnetz.c:478 notice : Round trip delay is 0.053 seconds
bnetz.c:474 notice : Received telegramm 'Ziffer 5'. (quality=97% level=30%)
bnetz.c:478 notice : Round trip delay is 0.054 seconds
</pre></font>
<p align='justify'>
Adjust the input level. (using 'alsamixer')
Try to maintain an input level arround 30%.
The quality should be 90% or better.
</p>
<p align='justify'>
Now you have connected the base station to your radio eqipment and roughly adjusted the levels.
</p>
<p align='justify'>
Finally store the settings using "alsactl store" command.
Do this whenever you want to keep your adjustments.
</p>
[<a href="index.html">Back to main page</a>]
</font></td></tr></table></center>
</body>
</html>

BIN
docs/transmitter.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
docs/trigger-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/trigger-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/trigger-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

151
git-version-gen Executable file
View File

@ -0,0 +1,151 @@
#!/bin/sh
# Print a version string.
scriptversion=2010-01-28.01
# Copyright (C) 2007-2010 Free Software Foundation, Inc.
#
# 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/>.
# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
# It may be run two ways:
# - from a git repository in which the "git describe" command below
# produces useful output (thus requiring at least one signed tag)
# - from a non-git-repo directory containing a .tarball-version file, which
# presumes this script is invoked like "./git-version-gen .tarball-version".
# In order to use intra-version strings in your project, you will need two
# separate generated version string files:
#
# .tarball-version - present only in a distribution tarball, and not in
# a checked-out repository. Created with contents that were learned at
# the last time autoconf was run, and used by git-version-gen. Must not
# be present in either $(srcdir) or $(builddir) for git-version-gen to
# give accurate answers during normal development with a checked out tree,
# but must be present in a tarball when there is no version control system.
# Therefore, it cannot be used in any dependencies. GNUmakefile has
# hooks to force a reconfigure at distribution time to get the value
# correct, without penalizing normal development with extra reconfigures.
#
# .version - present in a checked-out repository and in a distribution
# tarball. Usable in dependencies, particularly for files that don't
# want to depend on config.h but do want to track version changes.
# Delete this file prior to any autoconf run where you want to rebuild
# files to pick up a version string change; and leave it stale to
# minimize rebuild time after unrelated changes to configure sources.
#
# It is probably wise to add these two files to .gitignore, so that you
# don't accidentally commit either generated file.
#
# Use the following line in your configure.ac, so that $(VERSION) will
# automatically be up-to-date each time configure is run (and note that
# since configure.ac no longer includes a version string, Makefile rules
# should not depend on configure.ac for version updates).
#
# AC_INIT([GNU project],
# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
# [bug-project@example])
#
# Then use the following lines in your Makefile.am, so that .version
# will be present for dependencies, and so that .tarball-version will
# exist in distribution tarballs.
#
# BUILT_SOURCES = $(top_srcdir)/.version
# $(top_srcdir)/.version:
# echo $(VERSION) > $@-t && mv $@-t $@
# dist-hook:
# echo $(VERSION) > $(distdir)/.tarball-version
case $# in
1) ;;
*) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
esac
tarball_version_file=$1
nl='
'
# First see if there is a tarball-only version file.
# then try "git describe", then default.
if test -f $tarball_version_file
then
v=`cat $tarball_version_file` || exit 1
case $v in
*$nl*) v= ;; # reject multi-line output
[0-9]*) ;;
*) v= ;;
esac
test -z "$v" \
&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
fi
if test -n "$v"
then
: # use $v
elif
v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
|| git describe --abbrev=4 HEAD 2>/dev/null` \
&& case $v in
[0-9]*) ;;
v[0-9]*) ;;
*) (exit 1) ;;
esac
then
# Is this a new git that lists number of commits since the last
# tag or the previous older version that did not?
# Newer: v6.10-77-g0f8faeb
# Older: v6.10-g0f8faeb
case $v in
*-*-*) : git describe is okay three part flavor ;;
*-*)
: git describe is older two part flavor
# Recreate the number of commits and rewrite such that the
# result is the same as if we were using the newer version
# of git describe.
vtag=`echo "$v" | sed 's/-.*//'`
numcommits=`git rev-list "$vtag"..HEAD | wc -l`
v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
;;
esac
# Change the first '-' to a '.', so version-comparing tools work properly.
# Remove the "g" in git describe's output string, to save a byte.
v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
else
v=UNKNOWN
fi
v=`echo "$v" |sed 's/^v//'`
# Don't declare a version "dirty" merely because a time stamp has changed.
git status > /dev/null 2>&1
dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
case "$dirty" in
'') ;;
*) # Append the suffix only if there isn't one already.
case $v in
*-dirty) ;;
*) v="$v-dirty" ;;
esac ;;
esac
# Omit the trailing newline, so that m4_esyscmd can use the result directly.
echo "$v" | tr -d '\012'
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:

3
src/Makefile.am Normal file
View File

@ -0,0 +1,3 @@
AUTOMAKE_OPTIONS = foreign
SUBDIRS = common anetz bnetz

16
src/anetz/Makefile.am Normal file
View File

@ -0,0 +1,16 @@
AM_CPPFLAGS = -Wall -g $(all_includes)
bin_PROGRAMS = \
anetz
anetz_SOURCES = \
anetz.c \
dsp.c \
image.c \
main.c
anetz_LDADD = \
$(COMMON_LA) \
$(ALSA_LIBS) \
$(top_builddir)/src/common/libcommon.a \
-lm

458
src/anetz/anetz.c Normal file
View File

@ -0,0 +1,458 @@
/* A-Netz protocol handling
*
* (C) 2016 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 <string.h>
#include <errno.h>
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/cause.h"
#include "anetz.h"
#include "dsp.h"
/* Call reference for calls from mobile station to network
This offset of 0x400000000 is required for MNCC interface. */
static int new_callref = 0x40000000;
/* Timers */
#define PAGING_TO 30 /* Nach dieser Zeit ist der Operator genervt... */
/* Convert channel number to frequency number of base station.
Set 'unterband' to 1 to get frequency of mobile station. */
double anetz_kanal2freq(int kanal, int unterband)
{
double freq = 162.050;
freq += (kanal - 30) * 0.050;
if (kanal >= 45)
freq += 6.800;
if (unterband)
freq -= 4.500;
return freq;
}
/* Convert paging frequency number to to frequency. */
static double anetz_dauerruf_frq(int n)
{
if (n < 1 || n > 30)
abort();
return 337.5 + (double)n * 15.0;
}
/* Table with frequency sets to use for paging. */
static struct anetz_dekaden {
int dekade[4];
} anetz_gruppenkennziffer[10] = {
{ { 2, 2, 3, 3 } }, /* 0 */
{ { 1, 1, 2, 2 } }, /* 1 */
{ { 1, 1, 3, 3 } }, /* 2 */
{ { 1, 1, 2, 3 } }, /* 3 */
{ { 1, 2, 2, 3 } }, /* 4 */
{ { 1, 2, 3, 3 } }, /* 5 */
{ { 1, 1, 1, 2 } }, /* 6 */
{ { 1, 1, 1, 3 } }, /* 7 */
{ { 2, 2, 2, 3 } }, /* 8 */
{ { 1, 2, 2, 2 } }, /* 9 */
};
/* Takes the last 5 digits of a number and returns 4 paging tones.
If number is invalid, NULL is returned. */
static double *anetz_nummer2freq(const char *nummer)
{
int f[4];
static double freq[4];
int *dekade;
int i, j, digit;
/* get last 5 digits */
if (strlen(nummer) < 5) {
PDEBUG(DANETZ, DEBUG_ERROR, "Number must have at least 5 digits!\n");
return NULL;
}
nummer = nummer + strlen(nummer) - 5;
/* check for digits */
for (i = 0; i < 4; i++) {
if (nummer[i] < '0' || nummer[i] > '9') {
PDEBUG(DANETZ, DEBUG_ERROR, "Number must have digits 0..9!\n");
return NULL;
}
}
/* get decade */
dekade = anetz_gruppenkennziffer[*nummer - '0'].dekade;
PDEBUG(DANETZ, DEBUG_DEBUG, "Dekaden: %d %d %d %d\n", dekade[0], dekade[1], dekade[2], dekade[3]);
nummer++;
/* get 4 frequencies out of decades */
for (i = 0; i < 4; i++) {
digit = nummer[i] - '0';
if (digit == 0)
digit = 10;
f[i] = (dekade[i] - 1) * 10 + digit;
freq[i] = anetz_dauerruf_frq(f[i]);
for (j = 0; j < i; j++) {
if (dekade[i] == dekade[j] && nummer[i] == nummer[j]) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Number invalid, digit #%d and #%d of '%s' use same frequency F%d=%.1f of same decade %d!\n", i+1, j+1, nummer, f[i], freq[i], dekade[i]);
return NULL;
}
}
}
PDEBUG(DANETZ, DEBUG_DEBUG, "Frequencies: F%d=%.1f F%d=%.1f F%d=%.1f F%d=%.1f\n", f[0], freq[0], f[1], freq[1], f[2], freq[2], f[3], freq[3]);
return freq;
}
/* global init */
int anetz_init(void)
{
return 0;
}
static void anetz_timeout(struct timer *timer);
/* Create transceiver instance and link to a list. */
int anetz_create(const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume)
{
anetz_t *anetz;
int rc;
if (kanal < 30 || kanal > 63) {
PDEBUG(DANETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal);
return -EINVAL;
}
anetz = calloc(1, sizeof(anetz_t));
if (!anetz) {
PDEBUG(DANETZ, DEBUG_ERROR, "No memory!\n");
return -EIO;
}
PDEBUG(DANETZ, DEBUG_DEBUG, "Creating 'A-Netz' instance for 'Kanal' = %d (sample rate %d).\n", kanal, samplerate);
/* init general part of transceiver */
rc = sender_create(&anetz->sender, sounddev, samplerate, kanal, loopback, loss_volume, -1);
if (rc < 0) {
PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init 'Sender' processing!\n");
goto error;
}
/* init audio processing */
rc = dsp_init_sender(anetz);
if (rc < 0) {
PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init signal processing!\n");
goto error;
}
/* go into idle state */
PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n");
anetz->state = ANETZ_FREI;
anetz->dsp_mode = DSP_MODE_TONE;
timer_init(&anetz->timer, anetz_timeout, anetz);
return 0;
error:
anetz_destroy(&anetz->sender);
return rc;
}
/* Destroy transceiver instance and unlink from list. */
void anetz_destroy(sender_t *sender)
{
anetz_t *anetz = (anetz_t *) sender;
PDEBUG(DANETZ, DEBUG_DEBUG, "Destroying 'A-Netz' instance for 'Kanal' = %d.\n", sender->kanal);
timer_exit(&anetz->timer);
dsp_cleanup_sender(anetz);
sender_destroy(&anetz->sender);
free(sender);
}
/* Abort connection towards mobile station by sending idle tone. */
static void anetz_go_idle(anetz_t *anetz)
{
timer_stop(&anetz->timer);
PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n");
anetz->state = ANETZ_FREI;
anetz->dsp_mode = DSP_MODE_TONE;
anetz->station_id[0] = '\0';
}
/* Enter paging state and transmit 4 paging tones. */
static void anetz_page(anetz_t *anetz, const char *dial_string, double *freq)
{
PDEBUG(DANETZ, DEBUG_INFO, "Entering paging state, sending 'Selektivruf' to '%s'.\n", dial_string);
anetz->state = ANETZ_ANRUF;
anetz->dsp_mode = DSP_MODE_PAGING;
dsp_set_paging(anetz, freq);
strcpy(anetz->station_id, dial_string);
timer_start(&anetz->timer, PAGING_TO);
}
/* Loss of signal was detected, release active call. */
void anetz_loss_indication(anetz_t *anetz)
{
if (anetz->state == ANETZ_GESPRAECH) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n");
anetz_go_idle(anetz);
call_in_release(anetz->sender.callref, CAUSE_TEMPFAIL);
anetz->sender.callref = 0;
}
}
/* A continuous tone was detected or is gone. */
void anetz_receive_tone(anetz_t *anetz, int tone)
{
if (tone >= 0)
PDEBUG(DANETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (tone) ? 1750 : 2280);
else
PDEBUG(DANETZ, DEBUG_DEBUG, "Continuous tone is gone.\n");
if (anetz->sender.loopback) {
return;
}
switch (anetz->state) {
case ANETZ_FREI:
/* initiate call on calling tone */
if (tone == 1) {
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz calling signal from mobile station, removing idle signal.\n");
anetz->state = ANETZ_GESPRAECH;
anetz->dsp_mode = DSP_MODE_SILENCE;
break;
}
break;
case ANETZ_GESPRAECH:
/* throughconnect speech when calling/answer tone is gone */
if (tone != 1) {
if (!anetz->sender.callref) {
int callref = ++new_callref;
int rc;
PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, setup call.\n");
rc = call_in_setup(callref, anetz->station_id, "0");
if (rc < 0) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", -rc);
anetz_go_idle(anetz);
break;
}
anetz->sender.callref = callref;
} else {
PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, answer call.\n");
call_in_answer(anetz->sender.callref, anetz->station_id);
}
anetz->dsp_mode = DSP_MODE_AUDIO;
}
/* release call */
if (tone == 1) {
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz release signal from mobile station, sending idle signal.\n");
anetz_go_idle(anetz);
call_in_release(anetz->sender.callref, CAUSE_NORMAL);
anetz->sender.callref = 0;
break;
}
break;
case ANETZ_ANRUF:
/* answer call on answer tone */
if (tone == 1) {
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz answer signal from mobile station, removing paging tones.\n");
timer_stop(&anetz->timer);
anetz->state = ANETZ_GESPRAECH;
anetz->dsp_mode = DSP_MODE_SILENCE;
break;
}
default:
break;
}
}
/* Timeout handling */
static void anetz_timeout(struct timer *timer)
{
anetz_t *anetz = (anetz_t *)timer->priv;
switch (anetz->state) {
case ANETZ_ANRUF:
PDEBUG(DANETZ, DEBUG_NOTICE, "Timeout while waiting for answer, releasing.\n");
anetz_go_idle(anetz);
call_in_release(anetz->sender.callref, CAUSE_NOANSWER);
anetz->sender.callref = 0;
break;
default:
break;
}
}
/* Call control starts call towards mobile station. */
int call_out_setup(int callref, char *dialing)
{
sender_t *sender;
anetz_t *anetz;
double *freq;
/* 1. check if number is invalid, return INVALNUMBER */
if (strlen(dialing) > 7) {
inval:
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
return -CAUSE_INVALNUMBER;
}
freq = anetz_nummer2freq(dialing);
if (!freq)
goto inval;
/* 2. check if given number is already in a call, return BUSY */
for (sender = sender_head; sender; sender = sender->next) {
anetz = (anetz_t *) sender;
if (strlen(anetz->station_id) < 5)
continue;
if (!strcmp(anetz->station_id + strlen(anetz->station_id) - 5, dialing + strlen(dialing) - 5))
break;
}
if (sender) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
return -CAUSE_BUSY;
}
/* 3. check if all senders are busy, return NOCHANNEL */
for (sender = sender_head; sender; sender = sender->next) {
anetz = (anetz_t *) sender;
if (anetz->state == ANETZ_FREI)
break;
}
if (!sender) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
return -CAUSE_NOCHANNEL;
}
PDEBUG(DANETZ, DEBUG_INFO, "Call to mobile station, paging with tones: %.1f %.1f %.1f %.1f\n", freq[0], freq[1], freq[2], freq[3]);
/* 4. trying to page mobile station */
sender->callref = callref;
anetz_page(anetz, dialing, freq);
call_in_alerting(callref);
return 0;
}
/* Call control sends disconnect (with tones).
* An active call stays active, so tones and annoucements can be received
* by mobile station.
*/
void call_out_disconnect(int callref, int cause)
{
sender_t *sender;
anetz_t *anetz;
PDEBUG(DANETZ, DEBUG_INFO, "Call has been disconnected by network.\n");
for (sender = sender_head; sender; sender = sender->next) {
anetz = (anetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
call_in_release(callref, CAUSE_INVALCALLREF);
return;
}
/* Release when not active */
if (anetz->state == ANETZ_GESPRAECH)
return;
switch (anetz->state) {
case ANETZ_ANRUF:
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, going idle!\n");
anetz_go_idle(anetz);
break;
default:
break;
}
call_in_release(callref, cause);
sender->callref = 0;
}
/* Call control releases call toward mobile station. */
void call_out_release(int callref, int cause)
{
sender_t *sender;
anetz_t *anetz;
PDEBUG(DANETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n");
for (sender = sender_head; sender; sender = sender->next) {
anetz = (anetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender) {
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
/* don't send release, because caller already released */
return;
}
sender->callref = 0;
switch (anetz->state) {
case ANETZ_GESPRAECH:
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during call, going idle!\n");
anetz_go_idle(anetz);
break;
case ANETZ_ANRUF:
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during alerting, going idle!\n");
anetz_go_idle(anetz);
break;
default:
break;
}
}
/* Receive audio from call instance. */
void call_rx_audio(int callref, int16_t *samples, int count)
{
sender_t *sender;
anetz_t *anetz;
for (sender = sender_head; sender; sender = sender->next) {
anetz = (anetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender)
return;
if (anetz->dsp_mode == DSP_MODE_AUDIO) {
int16_t up[count * anetz->sender.srstate.factor];
count = samplerate_upsample(&anetz->sender.srstate, samples, count, up);
jitter_save(&anetz->sender.audio, up, count);
}
}

47
src/anetz/anetz.h Normal file
View File

@ -0,0 +1,47 @@
#include "../common/sender.h"
enum dsp_mode {
DSP_MODE_SILENCE, /* send silence to transmitter, block audio from receiver */
DSP_MODE_AUDIO, /* stream audio */
DSP_MODE_TONE, /* send 2280 Hz tone 0 */
DSP_MODE_PAGING, /* send four paging tones */
};
enum anetz_state {
ANETZ_FREI, /* sending 2280 Hz tone */
ANETZ_GESPRAECH, /* during conversation */
ANETZ_ANRUF, /* phone is paged */
};
typedef struct anetz {
sender_t sender;
/* sender's states */
enum anetz_state state; /* current sender's state */
char station_id[8]; /* current station ID */
struct timer timer;
/* dsp states */
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */
int fsk_tone_coeff; /* coefficient k = 2*cos(2*PI*f/samplerate), k << 15 */
int samples_per_chunk; /* how many samples lasts one chunk */
int16_t *fsk_filter_spl; /* array with samples_per_chunk */
int fsk_filter_pos; /* current sample position in filter_spl */
int tone_detected; /* what tone has been detected */
int tone_count; /* how long has that tone been detected */
double tone_phaseshift256; /* how much the phase of sine wave changes per sample */
double tone_phase256; /* current phase */
double paging_phaseshift256[4];/* how much the phase of sine wave changes per sample */
double paging_phase256[4]; /* current phase */
int paging_tone; /* current tone (0..3) in sequenced mode */
int paging_count; /* current sample count of tone in seq. mode */
} anetz_t;
double anetz_kanal2freq(int kanal, int unterband);
int anetz_init(void);
int anetz_create(const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume);
void anetz_destroy(sender_t *sender);
void anetz_loss_indication(anetz_t *anetz);
void anetz_receive_tone(anetz_t *anetz, int bit);

329
src/anetz/dsp.c Normal file
View File

@ -0,0 +1,329 @@
/* A-Netz signal processing
*
* (C) 2016 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 <string.h>
#include <errno.h>
#include <math.h>
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/goertzel.h"
#include "anetz.h"
#include "dsp.h"
#define PI 3.1415927
/* signalling */
#define TX_PEAK 8190.0 /* peak amplitude of sine wave (must be less than 32768/4) */
// FIXME: what is the allowed deviation of tone?
#define CHUNK_DURATION 0.010 /* 10 ms */
// FIXME: how long until we detect a tone?
#define TONE_DETECT_TH 8 /* chunk intervals to detect continous tone */
/* carrier loss detection */
#define LOSS_INTERVAL 100 /* filter steps (chunk durations) for one second interval */
#define LOSS_TIME 12 /* duration of signal loss before release */
extern int page_sequence;
/* two signalling tones */
static double fsk_tones[2] = {
2280.0,
1750.0,
};
/* table for fast sine generation */
int dsp_sine[256];
/* global init for audio processing */
void dsp_init(void)
{
int i;
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table.\n");
for (i = 0; i < 256; i++) {
dsp_sine[i] = (int)(sin((double)i / 256.0 * 2.0 * PI) * TX_PEAK);
}
if (TX_PEAK > 8191.0) {
fprintf(stderr, "TX_PEAK definition too high, please fix!\n");
abort();
}
}
/* Init transceiver instance. */
int dsp_init_sender(anetz_t *anetz)
{
int16_t *spl;
double coeff;
int detect_tone = (anetz->sender.loopback) ? 0 : 1;
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
audio_init_loss(&anetz->sender.loss, LOSS_INTERVAL, anetz->sender.loss_volume, LOSS_TIME);
anetz->samples_per_chunk = anetz->sender.samplerate * CHUNK_DURATION;
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per chunk duration.\n", anetz->samples_per_chunk);
spl = calloc(1, anetz->samples_per_chunk << 1);
if (!spl) {
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
anetz->fsk_filter_spl = spl;
anetz->tone_detected = -1;
coeff = 2.0 * cos(2.0 * PI * fsk_tones[detect_tone] / (double)anetz->sender.samplerate);
anetz->fsk_tone_coeff = coeff * 32768.0;
PDEBUG(DFSK, DEBUG_DEBUG, "RX %.0f Hz coeff = %d\n", fsk_tones[detect_tone], (int)anetz->fsk_tone_coeff);
anetz->tone_phaseshift256 = 256.0 / ((double)anetz->sender.samplerate / fsk_tones[0]);
PDEBUG(DFSK, DEBUG_DEBUG, "TX %.0f Hz phaseshift = %.4f\n", fsk_tones[0], anetz->tone_phaseshift256);
return 0;
}
/* Cleanup transceiver instance. */
void dsp_cleanup_sender(anetz_t *anetz)
{
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
if (anetz->fsk_filter_spl) {
free(anetz->fsk_filter_spl);
anetz->fsk_filter_spl = NULL;
}
}
/* Count duration of tone and indicate detection/loss to protocol handler. */
static void fsk_receive_tone(anetz_t *anetz, int tone, int goodtone, double level)
{
/* lost tone because it is not good anymore or has changed */
if (!goodtone || tone != anetz->tone_detected) {
if (anetz->tone_count >= TONE_DETECT_TH) {
PDEBUG(DFSK, DEBUG_DEBUG, "Lost %.0f Hz tone after %d ms.\n", fsk_tones[anetz->tone_detected], 1000.0 * CHUNK_DURATION * anetz->tone_count);
anetz_receive_tone(anetz, -1);
}
if (goodtone)
anetz->tone_detected = tone;
else
anetz->tone_detected = -1;
anetz->tone_count = 0;
return;
}
anetz->tone_count++;
if (anetz->tone_count >= TONE_DETECT_TH)
audio_reset_loss(&anetz->sender.loss);
if (anetz->tone_count == TONE_DETECT_TH) {
PDEBUG(DFSK, DEBUG_DEBUG, "Detecting continous %.0f Hz tone. (level = %d%%)\n", fsk_tones[anetz->tone_detected], (int)(level * 100));
anetz_receive_tone(anetz, anetz->tone_detected);
}
}
/* Filter one chunk of audio an detect tone, quality and loss of signal. */
static void fsk_decode_chunk(anetz_t *anetz, int16_t *spl, int max)
{
double level, result;
level = audio_level(spl, max);
if (audio_detect_loss(&anetz->sender.loss, level))
anetz_loss_indication(anetz);
audio_goertzel(spl, max, 0, &anetz->fsk_tone_coeff, &result, 1);
/* show quality of tone */
if (anetz->sender.loopback) {
/* adjust level, so we get peak of sine curve */
PDEBUG(DFSK, DEBUG_NOTICE, "Quality Tone:%3.0f%% Level:%3.0f%%\n", result / level * 100.0, level / 0.63662 * 100.0);
}
/* adjust level, so we get peak of sine curve */
/* indicate detected tone 1 (1750 Hz) or tone 0 (2280 Hz) at loopback */
if (level / 0.63 > 0.05 && result / level > 0.5)
fsk_receive_tone(anetz, (anetz->sender.loopback) ? 0 : 1, 1, level / 0.63662);
else
fsk_receive_tone(anetz, (anetz->sender.loopback) ? 0 : 1, 0, level / 0.63662);
}
/* Process received audio stream from radio unit. */
void sender_receive(sender_t *sender, int16_t *samples, int length)
{
anetz_t *anetz = (anetz_t *) sender;
int16_t *spl;
int max, pos;
int i;
/* write received samples to decode buffer */
max = anetz->samples_per_chunk;
pos = anetz->fsk_filter_pos;
spl = anetz->fsk_filter_spl;
for (i = 0; i < length; i++) {
spl[pos++] = samples[i];
if (pos == max) {
pos = 0;
fsk_decode_chunk(anetz, spl, max);
}
}
anetz->fsk_filter_pos = pos;
/* Forward audio to network (call process). */
if (anetz->dsp_mode == DSP_MODE_AUDIO && anetz->sender.callref) {
int16_t down[length]; /* more than enough */
int count;
count = samplerate_downsample(&anetz->sender.srstate, samples, length, down);
spl = anetz->sender.rxbuf;
pos = anetz->sender.rxbuf_pos;
for (i = 0; i < count; i++) {
spl[pos++] = down[i];
if (pos == 160) {
call_tx_audio(anetz->sender.callref, spl, 160);
pos = 0;
}
}
anetz->sender.rxbuf_pos = pos;
} else
anetz->sender.rxbuf_pos = 0;
}
/* Set 4 paging frequencies */
void dsp_set_paging(anetz_t *anetz, double *freq)
{
int i;
for (i = 0; i < 4; i++) {
anetz->paging_phaseshift256[i] = 256.0 / ((double)anetz->sender.samplerate / freq[i]);
anetz->paging_phase256[i] = 0;
}
}
/* Generate audio stream of 4 simultanious paging tones. Keep phase for next call of function.
* Use TX_PEAK for one tone, which gives peak of TX_PEAK * 4 for all tones. */
static void fsk_paging_tone(anetz_t *anetz, int16_t *samples, int length)
{
double phaseshift[5], phase[5];
int i;
for (i = 0; i < 4; i++) {
phaseshift[i] = anetz->paging_phaseshift256[i];
phase[i] = anetz->paging_phase256[i];
}
for (i = 0; i < length; i++) {
*samples++ = dsp_sine[((uint8_t)phase[0]) & 0xff]
+ dsp_sine[((uint8_t)phase[1]) & 0xff]
+ dsp_sine[((uint8_t)phase[2]) & 0xff]
+ dsp_sine[((uint8_t)phase[3]) & 0xff];
phase[0] += phaseshift[0];
phase[1] += phaseshift[1];
phase[2] += phaseshift[2];
phase[3] += phaseshift[3];
if (phase[0] >= 256) phase[0] -= 256;
if (phase[1] >= 256) phase[1] -= 256;
if (phase[2] >= 256) phase[2] -= 256;
if (phase[3] >= 256) phase[3] -= 256;
}
for (i = 0; i < 4; i++) {
anetz->paging_phase256[i] = phase[i];
}
}
/* Generate audio stream of 4 sequenced paging tones. Keep phase for next call of function.
* Use TX_PEAK * 2 for each tone. We will use a lower peak, because the radio might not TX it. */
static void fsk_paging_tone_sequence(anetz_t *anetz, int16_t *samples, int length, int numspl)
{
double phaseshift, phase;
int tone, count;
phase = anetz->tone_phase256;
tone = anetz->paging_tone;
count = anetz->paging_count;
next_tone:
phaseshift = anetz->paging_phaseshift256[tone];
while (length) {
*samples++ = dsp_sine[((uint8_t)phase) & 0xff] << 2;
phase += phaseshift;
if (phase >= 256)
phase -= 256;
if (++count == numspl) {
count = 0;
if (++tone == 4)
tone = 0;
goto next_tone;
}
length--;
}
anetz->tone_phase256 = phase;
anetz->paging_tone = tone;
anetz->paging_count = count;
}
/* Generate audio stream from tone. Keep phase for next call of function. */
static void fsk_tone(anetz_t *anetz, int16_t *samples, int length)
{
double phaseshift, phase;
int i;
phaseshift = anetz->tone_phaseshift256;
phase = anetz->tone_phase256;
for (i = 0; i < length; i++) {
*samples++ = dsp_sine[((uint8_t)phase) & 0xff];
phase += phaseshift;
if (phase >= 256)
phase -= 256;
}
anetz->tone_phase256 = phase;
}
/* Provide stream of audio toward radio unit */
void sender_send(sender_t *sender, int16_t *samples, int length)
{
anetz_t *anetz = (anetz_t *) sender;
switch (anetz->dsp_mode) {
case DSP_MODE_SILENCE:
memset(samples, 0, length * sizeof(*samples));
break;
case DSP_MODE_AUDIO:
jitter_load(&anetz->sender.audio, samples, length);
break;
case DSP_MODE_TONE:
fsk_tone(anetz, samples, length);
break;
case DSP_MODE_PAGING:
if (page_sequence)
fsk_paging_tone_sequence(anetz, samples, length, page_sequence * anetz->sender.samplerate / 1000);
else
fsk_paging_tone(anetz, samples, length);
break;
}
}

6
src/anetz/dsp.h Normal file
View File

@ -0,0 +1,6 @@
void dsp_init(void);
int dsp_init_sender(anetz_t *anetz);
void dsp_cleanup_sender(anetz_t *anetz);
void dsp_set_paging(anetz_t *anetz, double *freq);

59
src/anetz/image.c Normal file
View File

@ -0,0 +1,59 @@
#include <stdio.h>
#include <string.h>
#include "image.h"
const char *image[] = {
"@w",
"",
" A-NETZ",
"@g /",
" @w~@g /",
" @w~@g / @G/|\\@g",
" @w~@g ___________/_______ @G//|\\\\@g",
" @G/|\\@g /| | |\\\\ @w~@g @G//|\\\\@g",
"@B___@G/|\\@B___________________@g/ | | | \\\\@B_____________@G//|\\\\@B__",
" @G//|\\\\@g _/_____________/_(|_______|________|__\\\\________ @G///|\\\\\\@g",
" @G//|\\\\@g ( - - \\ @G///|\\\\\\@g",
" @G_|_@g | _____ _____ ) @G/ | \\@g",
" =____/@b/ \\@g\\_________________________/@b/ \\@g\\______= @G_|_",
"@w_____________@b( (@w*@b) )@w_________________________@b( (@w*@b) )@w________________",
" @b\\___/@w @b\\___/@w",
" ===== ====== ====== ====== ====== ======",
"",
"____________________________________________________________________",
NULL
};
void print_image(void)
{
int i, j;
for (i = 0; image[i]; i++) {
for (j = 0; j < strlen(image[i]); j++) {
if (image[i][j] == '@') {
j++;
switch(image[i][j]) {
case 'g': /* gray */
printf("\033[0;37m");
break;
case 'G': /* green */
printf("\033[0;32m");
break;
case 'w': /* white */
printf("\033[1;37m");
break;
case 'b': /* brown (yellow) */
printf("\033[0;33m");
break;
case 'B': /* blue */
printf("\033[0;34m");
break;
}
} else
printf("%c", image[i][j]);
}
printf("\n");
}
printf("\033[0;39m");
}

3
src/anetz/image.h Normal file
View File

@ -0,0 +1,3 @@
void print_image(void);

177
src/anetz/main.c Normal file
View File

@ -0,0 +1,177 @@
/* A-Netz main
*
* (C) 2016 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 <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sched.h>
#include "../common/main.h"
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/mncc_sock.h"
#include "anetz.h"
#include "dsp.h"
#include "image.h"
/* settings */
int page_sequence = 0;
void print_help(const char *arg0)
{
print_help_common(arg0);
/* - - */
printf(" -P --page-sequence 0 | <ms>\n");
printf(" Cycle paging tones, rather than sending simultaniously.\n");
printf(" (default = '%d')\n", page_sequence);
printf("\nstation-id: Give (last) 5 digits of station-id, you don't need to enter it\n");
printf(" for every start of this program.\n");
}
static int handle_options(int argc, char **argv)
{
int skip_args = 0;
static struct option long_options_special[] = {
{"page-sequence", 1, 0, 'P'},
{0, 0, 0, 0}
};
set_options_common("P:", long_options_special);
while (1) {
int option_index = 0, c;
c = getopt_long(argc, argv, optstring, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'P':
page_sequence = atoi(optarg);
skip_args += 2;
break;
default:
opt_switch_common(c, argv[0], &skip_args);
}
}
free(long_options);
return skip_args;
}
int main(int argc, char *argv[])
{
int rc;
int skip_args;
const char *station_id = "";
skip_args = handle_options(argc, argv);
argc -= skip_args;
argv += skip_args;
if (argc > 1) {
station_id = argv[1];
if (strlen(station_id) != 5 && strlen(station_id) != 7) {
printf("Given station ID '%s' does not have 7 or (the last) 5 digits\n", station_id);
return 0;
}
if (strlen(station_id) > 5)
station_id += strlen(station_id) - 5;
}
if (!kanal) {
printf("No channel (\"Kanal\") is specified, I suggest channel 30.\n\n");
print_help(argv[0]);
return 0;
}
if (!loopback)
print_image();
/* init functions */
if (use_mncc_sock) {
rc = mncc_init("/tmp/bsc_mncc");
if (rc < 0) {
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
return -1;
}
}
dsp_init();
anetz_init();
rc = call_init(station_id, call_sounddev, samplerate, latency, loopback);
if (rc < 0) {
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
goto fail;
}
/* create transceiver instance */
rc = anetz_create(sounddev, samplerate, kanal, loopback, lossdetect / 100.0);
if (rc < 0) {
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
goto fail;
}
printf("Base station ready, please tune transmitter to %.3f MHz and receiver "
"to %.3f MHz.\n", anetz_kanal2freq(kanal, 0),
anetz_kanal2freq(kanal, 1));
signal(SIGINT,sighandler);
signal(SIGHUP,sighandler);
signal(SIGTERM,sighandler);
signal(SIGPIPE,sighandler);
if (rt_prio > 0) {
struct sched_param schedp;
int rc;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = rt_prio;
rc = sched_setscheduler(0, SCHED_RR, &schedp);
if (rc)
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
}
main_loop(&quit, latency);
if (rt_prio > 0) {
struct sched_param schedp;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &schedp);
}
fail:
/* cleanup functions */
call_cleanup();
if (use_mncc_sock)
mncc_exit();
/* destroy transceiver instance */
while (sender_head)
anetz_destroy(sender_head);
return 0;
}

17
src/bnetz/Makefile.am Normal file
View File

@ -0,0 +1,17 @@
AM_CPPFLAGS = -Wall -g $(all_includes)
bin_PROGRAMS = \
bnetz
bnetz_SOURCES = \
bnetz.c \
dsp.c \
image.c \
ansage-27.c \
main.c
bnetz_LDADD = \
$(COMMON_LA) \
$(ALSA_LIBS) \
$(top_builddir)/src/common/libcommon.a \
-lm

5003
src/bnetz/ansage-27.c Normal file

File diff suppressed because it is too large Load Diff

3
src/bnetz/ansage-27.h Normal file
View File

@ -0,0 +1,3 @@
void init_ansage_27(void);

870
src/bnetz/bnetz.c Normal file
View File

@ -0,0 +1,870 @@
/* B-Netz protocol handling
*
* (C) 2016 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 <string.h>
#include <errno.h>
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/cause.h"
#include "bnetz.h"
#include "dsp.h"
/* Call reference for calls from mobile station to network
This offset of 0x400000000 is required for MNCC interface. */
static int new_callref = 0x40000000;
/* mobile originating call */
#define CARRIER_TO 0.08 /* 80 ms search for carrier */
#define GRUPPE_TO 0.4 /* 400 ms search for "Gruppensignal" */
#define DIALING_TO 1.00 /* FIXME: get real value */
/* radio loss condition */
#define LOSS_OF_SIGNAL 9.6 /* duration of carrier loss until release: 9.6 s */
/* mobile terminating call */
#define ALERTING_TO 60 /* timeout after 60 seconds alerting the MS */
#define PAGING_TO 4 /* timeout 4 seconds after "Selektivruf" */
#define PAGE_TRIES 2 /* two tries */
#define SWITCH19_TIME 1.0 /* time to switch channel (radio should be tansmitting after that) */
/* Convert channel number to frequency number of base station.
Set 'unterband' to 1 to get frequency of mobile station. */
double bnetz_kanal2freq(int kanal, int unterband)
{
double freq = 153.010;
if (kanal >= 50)
freq += 9.200 - 0.020 * 49;
freq += (kanal - 1) * 0.020;
if (unterband)
freq -= 4.600;
return freq;
}
/* List of message digits */
static struct impulstelegramme {
int digit;
const char *sequence;
uint16_t telegramm;
const char *description;
} impulstelegramme[] = {
/* Ziffern */
{ '0', "0111011000000011", 0x0000, "Ziffer 0" },
{ '1', "0111010100000101", 0x0000, "Ziffer 1" },
{ '2', "0111010010001001", 0x0000, "Ziffer 2" },
{ '3', "0111010001010001", 0x0000, "Ziffer 3" },
{ '4', "0111001100000110", 0x0000, "Ziffer 4" },
{ '5', "0111001010001010", 0x0000, "Ziffer 5" },
{ '6', "0111001001010010", 0x0000, "Ziffer 6" },
{ '7', "0111000110001100", 0x0000, "Ziffer 7" },
{ '8', "0111000101010100", 0x0000, "Ziffer 8" },
{ '9', "0111000011011000", 0x0000, "Ziffer 9" },
/* Signale */
{ 's', "0111001000100010", 0x0000, "Funkwahl ohne Gebuehrenuebermittlung" },
{ 'S', "0111000100100100", 0x0000, "Funkwahl mit Gebuehrenuebermittlung" },
{ 'e', "0111010000100001", 0x0000, "Funkwahlende" },
{ 't', "0111010101010101", 0x0000, "Trennsignal/Schlusssignal" },
/* Kanalbefehl B1 */
{ 1001, "0111011000000101", 0x0000, "Kanalbefehl 1" },
{ 1002, "0111011000001001", 0x0000, "Kanalbefehl 2" },
{ 1003, "0111011000010001", 0x0000, "Kanalbefehl 3" },
{ 1004, "0111011000000110", 0x0000, "Kanalbefehl 4" },
{ 1005, "0111011000001010", 0x0000, "Kanalbefehl 5" },
{ 1006, "0111011000010010", 0x0000, "Kanalbefehl 6" },
{ 1007, "0111011000001100", 0x0000, "Kanalbefehl 7" },
{ 1008, "0111011000010100", 0x0000, "Kanalbefehl 8" },
{ 1009, "0111011000011000", 0x0000, "Kanalbefehl 9" },
{ 1010, "0111010100000011", 0x0000, "Kanalbefehl 10" },
{ 1011, "0111001100000101", 0x0000, "Kanalbefehl 11" }, /* 41 */
{ 1012, "0111010100001001", 0x0000, "Kanalbefehl 12" },
{ 1013, "0111010100010001", 0x0000, "Kanalbefehl 13" },
{ 1014, "0111010100000110", 0x0000, "Kanalbefehl 14" },
{ 1015, "0111010100001010", 0x0000, "Kanalbefehl 15" },
{ 1016, "0111010100010010", 0x0000, "Kanalbefehl 16" },
{ 1017, "0111010100001100", 0x0000, "Kanalbefehl 17" },
{ 1018, "0111010100010100", 0x0000, "Kanalbefehl 18" },
{ 1019, "0111010100011000", 0x0000, "Kanalbefehl 19" },
{ 1020, "0111010010000011", 0x0000, "Kanalbefehl 20" },
{ 1021, "0111010010000101", 0x0000, "Kanalbefehl 21" },
{ 1022, "0111001100001001", 0x0000, "Kanalbefehl 22" }, /* 42 */
{ 1023, "0111010010010001", 0x0000, "Kanalbefehl 23" },
{ 1024, "0111010010000110", 0x0000, "Kanalbefehl 24" },
{ 1025, "0111010010001010", 0x0000, "Kanalbefehl 25" },
{ 1026, "0111010010010010", 0x0000, "Kanalbefehl 26" },
{ 1027, "0111010010001100", 0x0000, "Kanalbefehl 27" },
{ 1028, "0111010010010100", 0x0000, "Kanalbefehl 28" },
{ 1029, "0111010010011000", 0x0000, "Kanalbefehl 29" },
{ 1030, "0111010001000011", 0x0000, "Kanalbefehl 30" },
{ 1031, "0111010001000101", 0x0000, "Kanalbefehl 31" },
{ 1032, "0111010001001001", 0x0000, "Kanalbefehl 32" },
{ 1033, "0111001100010001", 0x0000, "Kanalbefehl 33" }, /* 43 */
{ 1034, "0111010001000110", 0x0000, "Kanalbefehl 34" },
{ 1035, "0111010001001010", 0x0000, "Kanalbefehl 35" },
{ 1036, "0111010001010010", 0x0000, "Kanalbefehl 36" },
{ 1037, "0111010001001100", 0x0000, "Kanalbefehl 37" },
{ 1038, "0111010001010100", 0x0000, "Kanalbefehl 38" },
{ 1039, "0111010001011000", 0x0000, "Kanalbefehl 39" },
/* Kanalbefehl B2 */
{ 1050, "0111001010000011", 0x0000, "Kanalbefehl 50" },
{ 1051, "0111001010000101", 0x0000, "Kanalbefehl 51" },
{ 1052, "0111001010001001", 0x0000, "Kanalbefehl 52" },
{ 1053, "0111001010010001", 0x0000, "Kanalbefehl 53" },
{ 1054, "0111001010000110", 0x0000, "Kanalbefehl 54" },
{ 1055, "0111001100001010", 0x0000, "Kanalbefehl 55" }, /* 45 */
{ 1056, "0111001010010010", 0x0000, "Kanalbefehl 56" },
{ 1057, "0111001010001100", 0x0000, "Kanalbefehl 57" },
{ 1058, "0111001010010100", 0x0000, "Kanalbefehl 58" },
{ 1059, "0111001010011000", 0x0000, "Kanalbefehl 59" },
{ 1060, "0111001001000011", 0x0000, "Kanalbefehl 60" },
{ 1061, "0111001001000101", 0x0000, "Kanalbefehl 61" },
{ 1062, "0111001001001001", 0x0000, "Kanalbefehl 62" },
{ 1063, "0111001001010001", 0x0000, "Kanalbefehl 63" },
{ 1064, "0111001001000110", 0x0000, "Kanalbefehl 64" },
{ 1065, "0111001001001010", 0x0000, "Kanalbefehl 65" },
{ 1066, "0111001100010010", 0x0000, "Kanalbefehl 66" }, /* 46 */
{ 1067, "0111001001001100", 0x0000, "Kanalbefehl 67" },
{ 1068, "0111001001010100", 0x0000, "Kanalbefehl 68" },
{ 1069, "0111001001011000", 0x0000, "Kanalbefehl 69" },
{ 1070, "0111000110000011", 0x0000, "Kanalbefehl 70" },
{ 1071, "0111000110000101", 0x0000, "Kanalbefehl 71" },
{ 1072, "0111000110001001", 0x0000, "Kanalbefehl 72" },
{ 1073, "0111000110010001", 0x0000, "Kanalbefehl 73" },
{ 1074, "0111000110000110", 0x0000, "Kanalbefehl 74" },
{ 1075, "0111000110001010", 0x0000, "Kanalbefehl 75" },
{ 1076, "0111000110010010", 0x0000, "Kanalbefehl 76" },
{ 1077, "0111001100001100", 0x0000, "Kanalbefehl 77" }, /* 47 */
{ 1078, "0111000110010100", 0x0000, "Kanalbefehl 78" },
{ 1079, "0111000110011000", 0x0000, "Kanalbefehl 79" },
{ 1080, "0111000101000011", 0x0000, "Kanalbefehl 80" },
{ 1081, "0111000101000101", 0x0000, "Kanalbefehl 81" },
{ 1082, "0111000101001001", 0x0000, "Kanalbefehl 82" },
{ 1083, "0111000101010001", 0x0000, "Kanalbefehl 83" },
{ 1084, "0111000101000110", 0x0000, "Kanalbefehl 84" },
{ 1085, "0111000101001010", 0x0000, "Kanalbefehl 85" },
{ 1086, "0111000101010010", 0x0000, "Kanalbefehl 86" },
/* Gruppenfreisignale */
{ 2001, "0111000011000101", 0x0000, "Gruppenfreisignal 1" }, /* 91 */
{ 2002, "0111000011001001", 0x0000, "Gruppenfreisignal 2" }, /* 92 */
{ 2003, "0111000011010001", 0x0000, "Gruppenfreisignal 3" }, /* 93 */
{ 2004, "0111000011000110", 0x0000, "Gruppenfreisignal 4" }, /* 94 */
{ 2005, "0111000011001010", 0x0000, "Gruppenfreisignal 5" }, /* 95 */
{ 2006, "0111000011010010", 0x0000, "Gruppenfreisignal 6" }, /* 96 */
{ 2007, "0111000011001100", 0x0000, "Gruppenfreisignal 7" }, /* 97 */
{ 2008, "0111000011010100", 0x0000, "Gruppenfreisignal 8" }, /* 98 */
{ 2009, "0111000011000011", 0x0000, "Gruppenfreisignal 9" }, /* 90 */
{ 2010, "0111000101000011", 0x0000, "Gruppenfreisignal 10" }, /* 80 */
{ 2011, "0111000101000101", 0x0000, "Gruppenfreisignal 11" }, /* 81 */
{ 2012, "0111000101001001", 0x0000, "Gruppenfreisignal 12" }, /* 82 */
{ 2013, "0111000101010001", 0x0000, "Gruppenfreisignal 13" }, /* 83 */
{ 2014, "0111000101000110", 0x0000, "Gruppenfreisignal 14" }, /* 84 */
{ 2015, "0111000101001010", 0x0000, "Gruppenfreisignal 15" }, /* 85 */
{ 2016, "0111000101010010", 0x0000, "Gruppenfreisignal 16" }, /* 86 */
{ 2017, "0111000101001100", 0x0000, "Gruppenfreisignal 17" }, /* 87 */
{ 2018, "0111000101010100", 0x0000, "Gruppenfreisignal 18" }, /* 88 */
{ 2019, "0111000101011000", 0x0000, "Gruppenfreisignal 19 (Kanaele kleiner Leistung)" }, /* 89 */
{ 0, NULL, 0, NULL }
};
/* Return bit sequence string by given digit. */
static struct impulstelegramme *bnetz_telegramm(int digit)
{
int i;
for (i = 0; impulstelegramme[i].digit; i++) {
if (impulstelegramme[i].digit == digit)
return &impulstelegramme[i];
}
return NULL;
}
/* switch pilot signal (tone or file) */
static void switch_channel_19(bnetz_t *bnetz, int on)
{
if (bnetz->sender.use_pilot_signal >= 0) {
bnetz->sender.pilot_on = on;
return;
}
if (bnetz->pilot_file && bnetz->pilot_is_on != on) {
FILE *fp;
fp = fopen(bnetz->pilot_file, "w");
if (!fp) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to open file '%s' to switch channel 19!\n");
return;
}
fprintf(fp, "%s\n", (on) ? bnetz->pilot_on : bnetz->pilot_off);
fclose(fp);
bnetz->pilot_is_on = on;
}
}
/* global init */
int bnetz_init(void)
{
int i, j;
for (i = 0; impulstelegramme[i].digit; i++) {
uint16_t telegramm = 0;
for (j = 0; j < strlen(impulstelegramme[i].sequence); j++) {
telegramm <<= 1;
telegramm |= (impulstelegramme[i].sequence[j] == '1');
}
impulstelegramme[i].telegramm = telegramm;
}
return 0;
}
static void bnetz_timeout(struct timer *timer);
/* Create transceiver instance and link to a list. */
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_factor, const char *pilot)
{
bnetz_t *bnetz;
int use_pilot_tone = -1;
char pilot_file[256] = "", pilot_on[256] = "", pilot_off[256] = "";
int rc;
if (!(kanal >= 1 && kanal <= 39) && !(kanal >= 50 && kanal <= 86)) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal);
return -EINVAL;
}
if (kanal == 19) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Selected calling channel ('Rufkanal') number %d can't be used as traffic channel.\n", kanal);
return -EINVAL;
}
if ((gfs < 1 || gfs > 19)) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Given 'Gruppenfreisignal' %d invalid.\n", gfs);
return -EINVAL;
}
if (!strcmp(pilot, "tone"))
use_pilot_tone = 2;
else
if (!strcmp(pilot, "positive"))
use_pilot_tone = 1;
else
if (!strcmp(pilot, "negative"))
use_pilot_tone = 0;
else {
char *p;
strncpy(pilot_file, pilot, sizeof(pilot_file) - 1);
p = strchr(pilot_file, '=');
if (!p) {
error_pilot:
PDEBUG(DBNETZ, DEBUG_ERROR, "Given pilot file (to switch to channel 19) is missing parameters. Use <file>=<on>:<off> format!\n");
return -EINVAL;
}
*p++ = '\0';
strncpy(pilot_on, p, sizeof(pilot_on) - 1);
p = strchr(pilot_file, ':');
if (!p)
goto error_pilot;
*p++ = '\0';
strncpy(pilot_off, p, sizeof(pilot_off) - 1);
}
bnetz = calloc(1, sizeof(bnetz_t));
if (!bnetz) {
PDEBUG(DBNETZ, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
PDEBUG(DBNETZ, DEBUG_DEBUG, "Creating 'B-Netz' instance for 'Kanal' = %d 'Gruppenfreisignal' = %d (sample rate %d).\n", kanal, gfs, samplerate);
/* init general part of transceiver */
rc = sender_create(&bnetz->sender, sounddev, samplerate, kanal, loopback, loss_factor, use_pilot_tone);
if (rc < 0) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init transceiver process!\n");
goto error;
}
/* init audio processing */
rc = dsp_init_sender(bnetz);
if (rc < 0) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init audio processing!\n");
goto error;
}
/* go into idle state */
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d on channel %d.\n", gfs, kanal);
bnetz->state = BNETZ_FREI;
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
bnetz->gfs = gfs;
strncpy(bnetz->pilot_file, pilot_file, sizeof(bnetz->pilot_file) - 1);
strncpy(bnetz->pilot_on, pilot_on, sizeof(bnetz->pilot_on) - 1);
strncpy(bnetz->pilot_off, pilot_off, sizeof(bnetz->pilot_off) - 1);
timer_init(&bnetz->timer, bnetz_timeout, bnetz);
switch_channel_19(bnetz, 0);
return 0;
error:
bnetz_destroy(&bnetz->sender);
return rc;
}
/* Destroy transceiver instance and unlink from list. */
void bnetz_destroy(sender_t *sender)
{
bnetz_t *bnetz = (bnetz_t *) sender;
PDEBUG(DBNETZ, DEBUG_DEBUG, "Destroying 'B-Netz' instance for 'Kanal' = %d.\n", sender->kanal);
switch_channel_19(bnetz, 0);
dsp_cleanup_sender(bnetz);
timer_exit(&bnetz->timer);
sender_destroy(&bnetz->sender);
free(bnetz);
}
/* Abort connection towards mobile station by sending idle digits. */
static void bnetz_go_idle(bnetz_t *bnetz)
{
timer_stop(&bnetz->timer);
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d.\n", bnetz->gfs);
bnetz->state = BNETZ_FREI;
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
switch_channel_19(bnetz, 0);
bnetz->station_id[0] = '\0';
}
/* Release connection towards mobile station by sending release digits. */
static void bnetz_release(bnetz_t *bnetz)
{
timer_stop(&bnetz->timer);
PDEBUG(DBNETZ, DEBUG_INFO, "Entering release state, sending 'Trennsignal'.\n");
bnetz->state = BNETZ_TRENNEN;
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
switch_channel_19(bnetz, 0);
bnetz->trenn_count = 0;
bnetz->station_id[0] = '\0';
}
/* Enter paging state and transmit station ID. */
static void bnetz_page(bnetz_t *bnetz, const char *dial_string, int try)
{
PDEBUG(DBNETZ, DEBUG_INFO, "Entering paging state (try %d), sending 'Selektivruf' to '%s'.\n", try, dial_string);
bnetz->state = BNETZ_SELEKTIVRUF;
bnetz->dsp_mode = DSP_MODE_0;
bnetz->page_mode = PAGE_MODE_NUMBER;
bnetz->page_try = try;
strcpy(bnetz->station_id, dial_string);
bnetz->station_id_pos = 0;
timer_start(&bnetz->timer, SWITCH19_TIME);
switch_channel_19(bnetz, 1);
}
/* FSK processing requests next digit after transmission of previous
digit has been finished. */
const char *bnetz_get_telegramm(bnetz_t *bnetz)
{
struct impulstelegramme *it = NULL;
if (bnetz->sender.loopback) {
bnetz->loopback_time[bnetz->loopback_count] = get_time();
it = bnetz_telegramm(bnetz->loopback_count + '0');
if (++bnetz->loopback_count > 9)
bnetz->loopback_count = 0;
} else
switch(bnetz->state) {
case BNETZ_FREI:
it = bnetz_telegramm(2000 + bnetz->gfs);
break;
case BNETZ_WAHLABRUF:
if (bnetz->station_id_pos == 5) {
bnetz->dsp_mode = DSP_MODE_1;
return NULL;
}
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
break;
case BNETZ_SELEKTIVRUF:
if (bnetz->page_mode == PAGE_MODE_KANALBEFEHL) {
PDEBUG(DBNETZ, DEBUG_INFO, "Paging mobile station %s complete, waiting for answer.\n", bnetz->station_id);
bnetz->state = BNETZ_RUFBESTAETIGUNG;
bnetz->dsp_mode = DSP_MODE_SILENCE;
switch_channel_19(bnetz, 0);
timer_start(&bnetz->timer, PAGING_TO);
return NULL;
}
if (bnetz->station_id_pos == 5) {
it = bnetz_telegramm(bnetz->sender.kanal + 1000);
bnetz->page_mode = PAGE_MODE_KANALBEFEHL;
break;
}
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
break;
case BNETZ_TRENNEN:
if (bnetz->trenn_count++ == 75) {
PDEBUG(DBNETZ, DEBUG_DEBUG, "Maximum number of release digits sent, going idle.\n");
bnetz_go_idle(bnetz);
return NULL;
}
it = bnetz_telegramm('t');
break;
default:
break;
}
if (!it)
abort();
PDEBUG(DBNETZ, DEBUG_DEBUG, "Sending telegramm '%s'.\n", it->description);
return it->sequence;
}
/* Loss of signal was detected, release active call. */
void bnetz_loss_indication(bnetz_t *bnetz)
{
if (bnetz->state == BNETZ_GESPRAECH
|| bnetz->state == BNETZ_RUFHALTUNG) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n");
bnetz_release(bnetz);
call_in_release(bnetz->sender.callref, CAUSE_TEMPFAIL);
bnetz->sender.callref = 0;
}
}
/* A continuous tone was detected or is gone. */
void bnetz_receive_tone(bnetz_t *bnetz, int bit)
{
if (bit >= 0)
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (bit)?1950:2070);
else
PDEBUG(DBNETZ, DEBUG_DEBUG, "Continuous tone is gone.\n");
if (bnetz->sender.loopback) {
return;
}
switch (bnetz->state) {
case BNETZ_FREI:
if (bit == 0) {
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Kanalbelegung' from mobile station, sending signal 'Wahlabruf'.\n");
bnetz->state = BNETZ_WAHLABRUF;
bnetz->dial_mode = DIAL_MODE_START;
bnetz->telegramm = NULL;
bnetz->dsp_mode = DSP_MODE_1;
timer_start(&bnetz->timer, DIALING_TO);
break;
}
break;
case BNETZ_RUFBESTAETIGUNG:
if (bit == 1) {
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Rufbestaetigung' from mobile station, call is ringing.\n");
timer_stop(&bnetz->timer);
bnetz->state = BNETZ_RUFHALTUNG;
bnetz->dsp_mode = DSP_MODE_1;
call_in_alerting(bnetz->sender.callref);
timer_start(&bnetz->timer, ALERTING_TO);
break;
}
break;
case BNETZ_RUFHALTUNG:
if (bit == 0) {
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Beginnsignal' from mobile station, call establised.\n");
timer_stop(&bnetz->timer);
bnetz->state = BNETZ_GESPRAECH;
bnetz->dsp_mode = DSP_MODE_AUDIO;
call_in_answer(bnetz->sender.callref, bnetz->station_id);
break;
}
default:
break;
}
}
/* A digit was received. */
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level)
{
int digit = 0;
int i;
int quality_percent = quality * 100;
int level_percent = level * 100;
/* drop any telegramm that is too bad */
if (quality_percent < 20)
return;
for (i = 0; impulstelegramme[i].digit; i++) {
if (impulstelegramme[i].telegramm == telegramm) {
digit = impulstelegramme[i].digit;
break;
}
}
if (digit == 0)
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received unknown telegramm '0x%04x'. (quality=%d%% level=%d%%)\n", telegramm, quality_percent, level_percent);
else
PDEBUG(DBNETZ, (bnetz->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received telegramm '%s'. (quality=%d%% level=%d%%)\n", impulstelegramme[i].description, quality_percent, level_percent);
if (bnetz->sender.loopback) {
if (digit >= '0' && digit <= '9') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Round trip delay is %.3f seconds\n", get_time() - bnetz->loopback_time[digit - '0'] - 0.160);
}
return;
}
switch (bnetz->state) {
case BNETZ_WAHLABRUF:
timer_start(&bnetz->timer, DIALING_TO);
switch (bnetz->dial_mode) {
case DIAL_MODE_START:
if (digit != 's' && digit != 'S') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received digit that is not a start digit ('Funkwahl'), aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (digit == 'S')
bnetz->dial_metering = 1;
else
bnetz->dial_metering = 0;
bnetz->dial_mode = DIAL_MODE_STATIONID;
memset(bnetz->station_id, 0, sizeof(bnetz->station_id));
bnetz->dial_pos = 0;
break;
case DIAL_MODE_STATIONID:
if (digit < '0' || digit > '9') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
bnetz->station_id[bnetz->dial_pos++] = digit;
if (bnetz->dial_pos == 5) {
PDEBUG(DBNETZ, DEBUG_INFO, "Received station id from mobile phone: %s\n", bnetz->station_id);
bnetz->dial_mode = DIAL_MODE_NUMBER;
memset(bnetz->dial_number, 0, sizeof(bnetz->dial_number));
bnetz->dial_pos = 0;
}
break;
case DIAL_MODE_NUMBER:
if (digit == 'e') {
PDEBUG(DBNETZ, DEBUG_INFO, "Received number from mobile phone: %s\n", bnetz->dial_number);
bnetz->dial_mode = DIAL_MODE_START2;
PDEBUG(DBNETZ, DEBUG_INFO, "Sending station id back to phone: %s.\n", bnetz->station_id);
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
bnetz->station_id_pos = 0;
break;
}
if (digit < '0' || digit > '9') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (bnetz->dial_pos == sizeof(bnetz->dial_number) - 1) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
bnetz->dial_number[bnetz->dial_pos++] = digit;
break;
case DIAL_MODE_START2:
if (digit != 's' && digit != 'S') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a start message('Funkwahl'), aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if ((digit == 'S' && bnetz->dial_metering != 1) || (digit == 's' && bnetz->dial_metering != 0)) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received start message('Funkwahl') does not match first one, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
bnetz->dial_mode = DIAL_MODE_STATIONID2;
bnetz->dial_pos = 0;
break;
case DIAL_MODE_STATIONID2:
if (digit < '0' || digit > '9') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (bnetz->station_id[bnetz->dial_pos++] != digit) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received station id does not match first one, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (bnetz->dial_pos == 5) {
bnetz->dial_mode = DIAL_MODE_NUMBER2;
bnetz->dial_pos = 0;
}
break;
case DIAL_MODE_NUMBER2:
if (digit == 'e') {
int callref = ++new_callref;
int rc;
/* add 0 in front of number */
char dialing[sizeof(bnetz->dial_number) + 1] = "0";
strcpy(dialing + 1, bnetz->dial_number);
if (bnetz->dial_pos != strlen(bnetz->dial_number)) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too few number digits the second time, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
PDEBUG(DBNETZ, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", bnetz->station_id, dialing);
timer_stop(&bnetz->timer);
bnetz->dsp_mode = DSP_MODE_AUDIO;
bnetz->state = BNETZ_GESPRAECH;
/* setup call */
PDEBUG(DBNETZ, DEBUG_INFO, "Setup call to network.\n");
rc = call_in_setup(callref, bnetz->station_id, dialing);
if (rc < 0) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc);
bnetz_release(bnetz);
return;
}
bnetz->sender.callref = callref;
break;
}
if (digit < '0' || digit > '9') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (bnetz->dial_pos == strlen(bnetz->dial_number)) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
if (bnetz->dial_number[bnetz->dial_pos++] != digit) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received number does not match first one, aborting.\n");
bnetz_go_idle(bnetz);
return;
}
}
break;
case BNETZ_GESPRAECH:
/* only good telegramms shall pass */
if (quality_percent < 70)
return;
if (digit == 't') {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received 'Schlusssignal' from mobile station\n");
bnetz_go_idle(bnetz);
call_in_release(bnetz->sender.callref, CAUSE_NORMAL);
bnetz->sender.callref = 0;
break;
}
break;
default:
break;
}
}
/* Timeout handling */
static void bnetz_timeout(struct timer *timer)
{
bnetz_t *bnetz = (bnetz_t *)timer->priv;
switch (bnetz->state) {
case BNETZ_WAHLABRUF:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while receiving call setup from mobile station, aborting.\n");
bnetz_go_idle(bnetz);
break;
case BNETZ_SELEKTIVRUF:
PDEBUG(DBNETZ, DEBUG_DEBUG, "Transmitter switched to channel 19, starting paging telegramms.\n");
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
break;
case BNETZ_RUFBESTAETIGUNG:
if (bnetz->page_try == PAGE_TRIES) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, going idle.\n");
bnetz_go_idle(bnetz);
call_in_release(bnetz->sender.callref, CAUSE_OUTOFORDER);
bnetz->sender.callref = 0;
break;
}
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, trying again.\n");
bnetz_page(bnetz, bnetz->station_id, bnetz->page_try + 1);
break;
case BNETZ_RUFHALTUNG:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for answer of mobile station, releasing.\n");
bnetz_release(bnetz);
call_in_release(bnetz->sender.callref, CAUSE_NOANSWER);
bnetz->sender.callref = 0;
break;
default:
break;
}
}
/* Call control starts call towards mobile station. */
int call_out_setup(int callref, char *dialing)
{
sender_t *sender;
bnetz_t *bnetz;
int i;
/* 1. check if number is invalid, return INVALNUMBER */
if (strlen(dialing) != 5) {
inval:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
return -CAUSE_INVALNUMBER;
}
for (i = 0; i < 5; i++) {
if (dialing[i] < '0' || dialing[i] > '9')
goto inval;
}
/* 2. check if given number is already in a call, return BUSY */
for (sender = sender_head; sender; sender = sender->next) {
bnetz = (bnetz_t *) sender;
if (!strcmp(bnetz->station_id, dialing))
break;
}
if (sender) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
return -CAUSE_BUSY;
}
/* 3. check if all senders are busy, return NOCHANNEL */
for (sender = sender_head; sender; sender = sender->next) {
bnetz = (bnetz_t *) sender;
if (bnetz->state == BNETZ_FREI)
break;
}
if (!sender) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
return -CAUSE_NOCHANNEL;
}
PDEBUG(DBNETZ, DEBUG_INFO, "Call to mobile station, paging station id '%s'\n", dialing);
/* 4. trying to page mobile station */
sender->callref = callref;
bnetz_page(bnetz, dialing, 1);
return 0;
}
/* Call control sends disconnect (with tones).
* An active call stays active, so tones and annoucements can be received
* by mobile station.
*/
void call_out_disconnect(int callref, int cause)
{
sender_t *sender;
bnetz_t *bnetz;
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been disconnected by network.\n");
for (sender = sender_head; sender; sender = sender->next) {
bnetz = (bnetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
call_in_release(callref, CAUSE_INVALCALLREF);
return;
}
/* Release when not active */
if (bnetz->state == BNETZ_GESPRAECH)
return;
switch (bnetz->state) {
case BNETZ_SELEKTIVRUF:
case BNETZ_RUFBESTAETIGUNG:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during paging, releasing!\n");
bnetz_release(bnetz);
break;
case BNETZ_RUFHALTUNG:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, releasing!\n");
bnetz_release(bnetz);
break;
default:
break;
}
call_in_release(callref, cause);
sender->callref = 0;
}
/* Call control releases call toward mobile station. */
void call_out_release(int callref, int cause)
{
sender_t *sender;
bnetz_t *bnetz;
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n");
for (sender = sender_head; sender; sender = sender->next) {
bnetz = (bnetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender) {
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
/* don't send release, because caller already released */
return;
}
sender->callref = 0;
switch (bnetz->state) {
case BNETZ_GESPRAECH:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during call, releasing!\n");
bnetz_release(bnetz);
break;
case BNETZ_SELEKTIVRUF:
case BNETZ_RUFBESTAETIGUNG:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during paging, releasing!\n");
bnetz_release(bnetz);
break;
case BNETZ_RUFHALTUNG:
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during alerting, releasing!\n");
bnetz_release(bnetz);
break;
default:
break;
}
}
/* Receive audio from call instance. */
void call_rx_audio(int callref, int16_t *samples, int count)
{
sender_t *sender;
bnetz_t *bnetz;
for (sender = sender_head; sender; sender = sender->next) {
bnetz = (bnetz_t *) sender;
if (sender->callref == callref)
break;
}
if (!sender)
return;
if (bnetz->dsp_mode == DSP_MODE_AUDIO) {
int16_t up[count * bnetz->sender.srstate.factor];
count = samplerate_upsample(&bnetz->sender.srstate, samples, count, up);
jitter_save(&bnetz->sender.audio, up, count);
}
}

99
src/bnetz/bnetz.h Normal file
View File

@ -0,0 +1,99 @@
#include "../common/sender.h"
/* fsk modes of transmission */
enum dsp_mode {
DSP_MODE_AUDIO, /* stream audio */
DSP_MODE_SILENCE, /* sending silence */
DSP_MODE_0, /* send tone 0 */
DSP_MODE_1, /* send tone 1 */
DSP_MODE_TELEGRAMM, /* send "Telegramm" */
};
/* current state of b-netz sender */
enum bnetz_state {
BNETZ_FREI, /* sending 'Gruppenfreisignal' */
BNETZ_WAHLABRUF, /* sending 'Wahlabruf', receiving call setup */
BNETZ_SELEKTIVRUF, /* paging phone */
BNETZ_RUFBESTAETIGUNG, /* waitig for acknowledge from phone */
BNETZ_RUFHALTUNG, /* phone is ringing */
BNETZ_GESPRAECH, /* during conversation */
BNETZ_TRENNEN, /* release of call */
};
/* current state of received dialing */
enum dial_mode {
DIAL_MODE_START,
DIAL_MODE_STATIONID,
DIAL_MODE_NUMBER,
DIAL_MODE_START2,
DIAL_MODE_STATIONID2,
DIAL_MODE_NUMBER2,
};
/* current state of paging mobile station */
enum page_mode {
PAGE_MODE_NUMBER,
PAGE_MODE_KANALBEFEHL,
};
/* instance of bnetz sender */
typedef struct bnetz {
sender_t sender;
/* system info */
int gfs; /* 'Gruppenfreisignal' */
/* switch sender to channel 19 */
char pilot_file[256]; /* if set, write to given file to switch to channel 19 or back */
char pilot_on[256]; /* what to write to switch to channel 19 */
char pilot_off[256]; /* what to write to switch back */
int pilot_is_on; /* set, if we are on channel 19. also used to switch back on exit */
/* all bnetz states */
enum bnetz_state state; /* main state of sender */
enum dial_mode dial_mode; /* sub state while dialing is received */
int dial_metering; /* set, if phone supports metering pulses */
char dial_number[14]; /* dial string received */
int dial_pos; /* current position while receiving digits */
char station_id[6]; /* current station ID */
int station_id_pos; /* position while transmitting */
enum page_mode page_mode; /* sub state while paging */
int page_try; /* try number (1 or 2) */
struct timer timer;
int trenn_count; /* count number of release messages */
/* dsp states */
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, "Telegramm" */
int fsk_coeff[2]; /* coefficient k = 2*cos(2*PI*f/samplerate), k << 15 */
int samples_per_bit; /* how many samples lasts one bit */
int16_t *fsk_filter_spl; /* array with samples_per_bit */
int fsk_filter_pos; /* current sample position in filter_spl */
int fsk_filter_step; /* number of samples for each analyzation */
int fsk_filter_bit; /* last bit, so we detect a bit change */
int fsk_filter_sample; /* sample counter for shifting receive bits */
uint16_t fsk_filter_telegramm; /* rx shift register for receiveing telegramm */
double fsk_filter_quality[16]; /* quality of each bit in telegramm */
double fsk_filter_level[16]; /* level of each bit in telegramm */
int fsk_filter_qualidx; /* index of quality array above */
int tone_detected; /* what tone has been detected */
int tone_count; /* how long has that tone been detected */
double phaseshift256[2]; /* how much the phase of sine wave changes per sample */
double phase256; /* current phase */
const char *telegramm; /* current telegramm sequence */
int16_t *telegramm_spl; /* 16 * samples_per_bit */
int telegramm_pos; /* current sample position in telegramm_spl */
/* loopback test for latency */
int loopback_count; /* count digits from 0 to 9 */
double loopback_time[10]; /* time stamp when sending digits 0..9 */
} bnetz_t;
double bnetz_kanal2freq(int kanal, int unterband);
int bnetz_init(void);
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_volume, const char *pilot);
void bnetz_destroy(sender_t *sender);
void bnetz_loss_indication(bnetz_t *bnetz);
void bnetz_receive_tone(bnetz_t *bnetz, int bit);
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level);
const char *bnetz_get_telegramm(bnetz_t *bnetz);

416
src/bnetz/dsp.c Normal file
View File

@ -0,0 +1,416 @@
/* B-Netz signal processing
*
* (C) 2016 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 <string.h>
#include <errno.h>
#include <math.h>
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/goertzel.h"
#include "bnetz.h"
#include "dsp.h"
#define PI 3.1415927
/* signalling */
#define TX_PEAK 10000.0 /* peak amplitude of sine wave */
#define BIT_DURATION 0.010 /* bit length: 10 ms */
#define FILTER_STEP 0.001 /* step every 1 ms */
#define METERING_HZ 2900 /* metering pulse frequency */
#define TONE_DETECT_TH 70 /* 70 milliseconds to detect continous tone */
/* carrier loss detection */
#define LOSS_INTERVAL 1000 /* filter steps (milliseconds) for one second interval */
#define LOSS_TIME 12 /* duration of signal loss before release */
/* two signalling tones */
static double fsk_bits[2] = {
2070.0,
1950.0,
};
/* table for fast sine generation */
int dsp_sine[256];
/* global init for FSK */
void dsp_init(void)
{
int i;
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table.\n");
for (i = 0; i < 256; i++) {
dsp_sine[i] = (int)(sin((double)i / 256.0 * 2.0 * PI) * TX_PEAK);
}
}
/* Init transceiver instance. */
int dsp_init_sender(bnetz_t *bnetz)
{
double coeff;
int16_t *spl;
int i;
if ((bnetz->sender.samplerate % 1000)) {
PDEBUG(DFSK, DEBUG_ERROR, "Samples rate must be a multiple of 1000 bits per second.\n");
return -EINVAL;
}
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
audio_init_loss(&bnetz->sender.loss, LOSS_INTERVAL, bnetz->sender.loss_volume, LOSS_TIME);
bnetz->samples_per_bit = bnetz->sender.samplerate * BIT_DURATION;
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per bit duration.\n", bnetz->samples_per_bit);
bnetz->fsk_filter_step = bnetz->sender.samplerate * FILTER_STEP;
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per filter step.\n", bnetz->fsk_filter_step);
spl = calloc(16, bnetz->samples_per_bit << 1);
if (!spl) {
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
bnetz->telegramm_spl = spl;
spl = calloc(1, bnetz->samples_per_bit << 1);
if (!spl) {
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
bnetz->fsk_filter_spl = spl;
bnetz->fsk_filter_bit = -1;
bnetz->tone_detected = -1;
/* count symbols */
for (i = 0; i < 2; i++) {
coeff = 2.0 * cos(2.0 * PI * fsk_bits[i] / (double)bnetz->sender.samplerate);
bnetz->fsk_coeff[i] = coeff * 32768.0;
PDEBUG(DFSK, DEBUG_DEBUG, "coeff[%d] = %d (must be -3601 and 2573 at 8000hz)\n", i, (int)bnetz->fsk_coeff[i]);
bnetz->phaseshift256[i] = 256.0 / ((double)bnetz->sender.samplerate / fsk_bits[i]);
PDEBUG(DFSK, DEBUG_DEBUG, "phaseshift[%d] = %.4f (must be arround 64 at 8000hz)\n", i, bnetz->phaseshift256[i]);
}
return 0;
}
/* Cleanup transceiver instance. */
void dsp_cleanup_sender(bnetz_t *bnetz)
{
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
if (bnetz->telegramm_spl) {
free(bnetz->telegramm_spl);
bnetz->telegramm_spl = NULL;
}
if (bnetz->fsk_filter_spl) {
free(bnetz->fsk_filter_spl);
bnetz->fsk_filter_spl = NULL;
}
}
/* Count duration of tone and indicate detection/loss to protocol handler. */
static void fsk_receive_tone(bnetz_t *bnetz, int bit, int goodtone, double level)
{
/* lost tone because it is not good anymore or has changed */
if (!goodtone || bit != bnetz->tone_detected) {
if (bnetz->tone_count >= TONE_DETECT_TH) {
PDEBUG(DFSK, DEBUG_DEBUG, "Lost %.0f Hz tone after %d ms.\n", fsk_bits[bnetz->tone_detected], bnetz->tone_count);
bnetz_receive_tone(bnetz, -1);
}
if (goodtone)
bnetz->tone_detected = bit;
else
bnetz->tone_detected = -1;
bnetz->tone_count = 0;
return;
}
bnetz->tone_count++;
if (bnetz->tone_count >= TONE_DETECT_TH)
audio_reset_loss(&bnetz->sender.loss);
if (bnetz->tone_count == TONE_DETECT_TH) {
PDEBUG(DFSK, DEBUG_DEBUG, "Detecting continous %.0f Hz tone. (level = %d%%)\n", fsk_bits[bnetz->tone_detected], (int)(level * 100));
bnetz_receive_tone(bnetz, bnetz->tone_detected);
}
}
/* Collect 16 data bits (digit) and check for sync marc '01110'. */
static void fsk_receive_bit(bnetz_t *bnetz, int bit, double quality, double level)
{
int i;
bnetz->fsk_filter_telegramm = (bnetz->fsk_filter_telegramm << 1) | bit;
bnetz->fsk_filter_quality[bnetz->fsk_filter_qualidx] = quality;
bnetz->fsk_filter_level[bnetz->fsk_filter_qualidx] = level;
if (++bnetz->fsk_filter_qualidx == 16)
bnetz->fsk_filter_qualidx = 0;
/* check if pattern 01110xxxxxxxxxxx matches */
if ((bnetz->fsk_filter_telegramm & 0xf800) != 0x7000)
return;
/* get worst bit and average level */
level = 0;
for (i = 0; i < 16; i++) {
if (bnetz->fsk_filter_quality[i] < quality)
quality = bnetz->fsk_filter_quality[i];
level = bnetz->fsk_filter_level[i];
}
/* send telegramm */
bnetz_receive_telegramm(bnetz, bnetz->fsk_filter_telegramm, quality, level);
}
char *show_level(int value)
{
static char text[22];
value /= 5;
if (value < 0)
value = 0;
if (value > 20)
value = 20;
strcpy(text, " ");
text[value] = '*';
return text;
}
//#define DEBUG_FILTER
//#define DEBUG_QUALITY
/* Filter one chunk of audio an detect tone, quality and loss of signal.
* The chunk is a window of 10ms. This window slides over audio stream
* and is processed every 1ms. (one step) */
void fsk_decode_step(bnetz_t *bnetz, int pos)
{
double level, result[2], softbit, quality;
int max;
int16_t *spl;
int bit;
max = bnetz->samples_per_bit;
spl = bnetz->fsk_filter_spl;
level = audio_level(spl, max);
if (audio_detect_loss(&bnetz->sender.loss, level))
bnetz_loss_indication(bnetz);
audio_goertzel(spl, max, pos, bnetz->fsk_coeff, result, 2);
/* calculate soft bit from both frequencies */
softbit = (result[1] / level - result[0] / level + 1.0) / 2.0;
/* scale it, since both filters overlap by some percent */
#define MIN_QUALITY 0.08
softbit = (softbit - MIN_QUALITY) / (0.850 - MIN_QUALITY - MIN_QUALITY);
if (softbit > 1)
softbit = 1;
if (softbit < 0)
softbit = 0;
#ifdef DEBUG_FILTER
printf("|%s", show_level(result[0]/level*100));
printf("|%s| low=%.3f high=%.3f level=%d\n", show_level(result[1]/level*100), result[0]/level, result[1]/level, (int)level);
#endif
if (softbit > 0.5)
bit = 1;
else
bit = 0;
// FIXME: better threshold
/* adjust level, so we get peak of sine curve */
if (level / 0.63 > 0.05 && (softbit > 0.75 || softbit < 0.25)) {
fsk_receive_tone(bnetz, bit, 1, level / 0.63662);
} else
fsk_receive_tone(bnetz, bit, 0, level / 0.63662);
if (bnetz->fsk_filter_bit != bit) {
/* if we have a bit change, reset sample counter to one half bit duration */
bnetz->fsk_filter_bit = bit;
bnetz->fsk_filter_sample = 5;
} else if (--bnetz->fsk_filter_sample == 0) {
/* if sample counter bit reaches 0, we reset sample counter to one bit duration */
// quality = result[bit] / level;
if (softbit > 0.5)
quality = softbit * 2.0 - 1.0;
else
quality = 1.0 - softbit * 2.0;
#ifdef DEBUG_QUALITY
printf("|%s| quality=%.2f ", show_level(softbit * 100), quality);
printf("|%s|\n", show_level(quality * 100));
#endif
/* adjust level, so we get peak of sine curve */
fsk_receive_bit(bnetz, bit, quality, level / 0.63662);
bnetz->fsk_filter_sample = 10;
}
}
/* Process received audio stream from radio unit. */
void sender_receive(sender_t *sender, int16_t *samples, int length)
{
bnetz_t *bnetz = (bnetz_t *) sender;
int16_t *spl;
int max, pos, step;
int i;
/* write received samples to decode buffer */
max = bnetz->samples_per_bit;
pos = bnetz->fsk_filter_pos;
step = bnetz->fsk_filter_step;
spl = bnetz->fsk_filter_spl;
for (i = 0; i < length; i++) {
spl[pos++] = samples[i];
if (pos == max)
pos = 0;
/* if filter step has been reched */
if (!(pos % step)) {
fsk_decode_step(bnetz, pos);
}
}
bnetz->fsk_filter_pos = pos;
if (bnetz->dsp_mode == DSP_MODE_AUDIO && bnetz->sender.callref) {
int16_t down[length]; /* more than enough */
int count;
count = samplerate_downsample(&bnetz->sender.srstate, samples, length, down);
spl = bnetz->sender.rxbuf;
pos = bnetz->sender.rxbuf_pos;
for (i = 0; i < count; i++) {
spl[pos++] = down[i];
if (pos == 160) {
call_tx_audio(bnetz->sender.callref, spl, 160);
pos = 0;
}
}
bnetz->sender.rxbuf_pos = pos;
} else
bnetz->sender.rxbuf_pos = 0;
}
static void fsk_tone(bnetz_t *bnetz, int16_t *samples, int length, int tone)
{
double phaseshift, phase;
int i;
phase = bnetz->phase256;
phaseshift = bnetz->phaseshift256[tone];
for (i = 0; i < length; i++) {
*samples++ = dsp_sine[((uint8_t)phase) & 0xff];
phase += phaseshift;
if (phase >= 256)
phase -= 256;
}
bnetz->phase256 = phase;
}
static int fsk_telegramm(bnetz_t *bnetz, int16_t *samples, int length)
{
int16_t *spl;
int i, j;
double phaseshift, phase;
int count, max;
next_telegramm:
if (!bnetz->telegramm) {
/* request telegramm */
// PDEBUG(DFSK, DEBUG_DEBUG, "Request new 'Telegramm'.\n");
bnetz->telegramm = bnetz_get_telegramm(bnetz);
if (!bnetz->telegramm) {
PDEBUG(DFSK, DEBUG_DEBUG, "Stop sending 'Telegramm'.\n");
return length;
}
bnetz->telegramm_pos = 0;
spl = bnetz->telegramm_spl;
/* render telegramm */
phase = bnetz->phase256;
for (i = 0; i < 16; i++) {
phaseshift = bnetz->phaseshift256[bnetz->telegramm[i] == '1'];
for (j = 0; j < bnetz->samples_per_bit; j++) {
*spl++ = dsp_sine[((uint8_t)phase) & 0xff];
phase += phaseshift;
if (phase >= 256)
phase -= 256;
}
}
bnetz->phase256 = phase;
}
/* send audio from telegramm */
max = bnetz->samples_per_bit * 16;
count = max - bnetz->telegramm_pos;
if (count > length)
count = length;
spl = bnetz->telegramm_spl + bnetz->telegramm_pos;
for (i = 0; i < count; i++)
*samples++ = *spl++;
length -= count;
bnetz->telegramm_pos += count;
/* check for end of telegramm */
if (bnetz->telegramm_pos == max) {
bnetz->telegramm = NULL;
/* we need more ? */
if (length)
goto next_telegramm;
}
return length;
}
/* Provide stream of audio toward radio unit */
void sender_send(sender_t *sender, int16_t *samples, int length)
{
bnetz_t *bnetz = (bnetz_t *) sender;
int len;
again:
switch (bnetz->dsp_mode) {
case DSP_MODE_AUDIO:
jitter_load(&bnetz->sender.audio, samples, length);
break;
case DSP_MODE_SILENCE:
memset(samples, 0, length * sizeof(*samples));
break;
case DSP_MODE_0:
fsk_tone(bnetz, samples, length, 0);
break;
case DSP_MODE_1:
fsk_tone(bnetz, samples, length, 1);
break;
case DSP_MODE_TELEGRAMM:
/* Encode telegramm into audio stream. If telegramms have
* stopped, process again for rest of stream. */
len = fsk_telegramm(bnetz, samples, length);
if (len) {
samples += length - len;
length = len;
goto again;
}
break;
}
}

5
src/bnetz/dsp.h Normal file
View File

@ -0,0 +1,5 @@
void dsp_init(void);
int dsp_init_sender(bnetz_t *bnetz);
void dsp_cleanup_sender(bnetz_t *bnetz);

65
src/bnetz/image.c Normal file
View File

@ -0,0 +1,65 @@
#include <stdio.h>
#include <string.h>
#include "image.h"
const char *image[] = {
"@g",
"",
"",
" \\",
" \\",
" \\",
" \\_ @wB-NETZ@g",
" \\ \\",
" \\_\\___",
" / __ )",
" (__\\ _\\________",
" / _______ )",
" / / \\/",
" / / ______\\___",
" / / / )",
" (__\\ / / @w~@g",
" \\/ ___ /",
" / / \\/ @w~@g",
" (______\\ \\",
" \\ \\",
" \\ \\",
" \\ \\",
" @w~@g \\ \\",
" \\ \\",
" \\ \\@G (###)@g",
" \\ @G(##))########)",
" (#)))################(#))",
" (#)#(#######)))#################)",
" ((#########)#######################)",
"@w=========================================================",
NULL
};
void print_image(void)
{
int i, j;
for (i = 0; image[i]; i++) {
for (j = 0; j < strlen(image[i]); j++) {
if (image[i][j] == '@') {
j++;
switch(image[i][j]) {
case 'g':
printf("\033[0;37m");
break;
case 'w':
printf("\033[1;37m");
break;
case 'G':
printf("\033[0;32m");
break;
}
} else
printf("%c", image[i][j]);
}
printf("\n");
}
printf("\033[0;39m");
}

3
src/bnetz/image.h Normal file
View File

@ -0,0 +1,3 @@
void print_image(void);

190
src/bnetz/main.c Normal file
View File

@ -0,0 +1,190 @@
/* B-Netz main
*
* (C) 2016 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 <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sched.h>
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/call.h"
#include "../common/mncc_sock.h"
#include "../common/main.h"
#include "bnetz.h"
#include "dsp.h"
#include "image.h"
#include "ansage-27.h"
int gfs = 2;
const char *pilot = "tone";
void print_help(const char *arg0)
{
print_help_common(arg0);
/* - - */
printf(" -g --gfs <gruppenfreisignal>\n");
printf(" Gruppenfreisignal\" 1..9 | 19 | 10..18 (default = '%d')\n", gfs);
printf(" -P --pilot tone | positive | negative | <file>=<on>:<off>\n");
printf(" Send a tone, give a signal or write to a file when switching to\n");
printf(" channel 19. (paging the phone).\n");
printf(" 'tone', 'positive', 'negative' is sent on right audio channel.\n");
printf(" 'tone' sends a tone whenever channel 19 is switchted.\n");
printf(" 'positive' sends a positive signal for channel 19, else negative.\n");
printf(" 'negative' sends a negative signal for channel 19, else positive.\n");
printf(" Example: /sys/class/gpio/gpio17/value=1:0 writes a '1' to\n");
printf(" /sys/class/gpio/gpio17/value to switching to channel 19 and a '0' to\n");
printf(" switch back. (default = %s)\n", pilot);
printf("\nstation-id: Give 5 digit station-id, you don't need to enter it for every\n");
printf(" start of this program.\n");
}
static int handle_options(int argc, char **argv)
{
int skip_args = 0;
static struct option long_options_special[] = {
{"gfs", 1, 0, 'g'},
{"pilot", 1, 0, 'P'},
{0, 0, 0, 0},
};
set_options_common("g:P:", long_options_special);
while (1) {
int option_index = 0, c;
c = getopt_long(argc, argv, optstring, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'g':
gfs = atoi(optarg);
skip_args += 2;
break;
case 'P':
pilot = strdup(optarg);
skip_args += 2;
break;
default:
opt_switch_common(c, argv[0], &skip_args);
}
}
return skip_args;
}
int main(int argc, char *argv[])
{
int rc;
int skip_args;
const char *station_id = "";
skip_args = handle_options(argc, argv);
argc -= skip_args;
argv += skip_args;
if (argc > 1) {
station_id = argv[1];
if (strlen(station_id) != 5) {
printf("Given station ID '%s' does not have 5 digits\n", station_id);
return 0;
}
}
if (!kanal) {
printf("No channel (\"Kanal\") is specified, I suggest channel 1.\n\n");
print_help(argv[0]);
return 0;
}
if (!loopback)
print_image();
/* init functions */
if (use_mncc_sock) {
rc = mncc_init("/tmp/bsc_mncc");
if (rc < 0) {
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
return -1;
}
}
init_ansage_27();
dsp_init();
bnetz_init();
rc = call_init(station_id, call_sounddev, samplerate, latency, loopback);
if (rc < 0) {
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
goto fail;
}
/* create transceiver instance */
rc = bnetz_create(sounddev, samplerate, kanal, gfs, loopback, (double)lossdetect / 100.0, pilot);
if (rc < 0) {
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
goto fail;
}
printf("Base station ready, please tune transmitter to %.3f MHz and receiver "
"to %.3f MHz.\n", bnetz_kanal2freq(kanal, 0),
bnetz_kanal2freq(kanal, 1));
printf("To call phone, switch transmitter (using pilot signal) to %.3f MHz.\n", bnetz_kanal2freq(19, 0));
signal(SIGINT,sighandler);
signal(SIGHUP,sighandler);
signal(SIGTERM,sighandler);
signal(SIGPIPE,sighandler);
if (rt_prio > 0) {
struct sched_param schedp;
int rc;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = rt_prio;
rc = sched_setscheduler(0, SCHED_RR, &schedp);
if (rc)
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
}
main_loop(&quit, latency);
if (rt_prio > 0) {
struct sched_param schedp;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &schedp);
}
fail:
/* cleanup functions */
call_cleanup();
if (use_mncc_sock)
mncc_exit();
/* destroy transceiver instance */
while(sender_head)
bnetz_destroy(sender_head);
return 0;
}

21
src/common/Makefile.am Normal file
View File

@ -0,0 +1,21 @@
AM_CPPFLAGS = -Wall -g $(all_includes)
noinst_LIBRARIES = libcommon.a
libcommon_a_SOURCES = \
../common/debug.c \
../common/timer.c \
../common/sound_alsa.c \
../common/goertzel.c \
../common/jitter.c \
../common/loss.c \
../common/filter.c \
../common/samplerate.c \
../common/call.c \
../common/freiton.c \
../common/besetztton.c \
../common/mncc_sock.c \
../common/cause.c \
../common/sender.c \
../common/main_common.c

185
src/common/besetztton.c Normal file
View File

@ -0,0 +1,185 @@
#include <stdint.h>
static int16_t pattern[] = {
0x0004, 0xffe9, 0xffc9, 0xffac, 0xff92, 0xff83, 0xff75, 0xff56,
0xff40, 0xff2b, 0xff25, 0xff2b, 0xff1f, 0xff1b, 0xff32, 0xff6a,
0xffcb, 0x00a2, 0x01cb, 0x02e2, 0x0373, 0x0369, 0x030a, 0x0268,
0x01a9, 0x00d9, 0xffc2, 0xfeb4, 0xfddc, 0xfd4f, 0xfce3, 0xfc8d,
0xfc6c, 0xfcb2, 0xfd1c, 0xfd99, 0xfe2b, 0xff27, 0x000a, 0x013e,
0x030f, 0x036f, 0x0341, 0x0374, 0x03c1, 0x03c7, 0x02f2, 0x01b9,
0x00e3, 0x004f, 0xff8f, 0xfe87, 0xfda0, 0xfd40, 0xfd32, 0xfd49,
0xfd2a, 0xfd3d, 0xfda1, 0xfe3a, 0xfee4, 0xff6d, 0x0002, 0x00b9,
0x0145, 0x01db, 0x0241, 0x025b, 0x0262, 0x025f, 0x01bf, 0x0169,
0x0134, 0x0085, 0x0028, 0xffd7, 0xfef3, 0xfe9c, 0xfec7, 0xfe45,
0xfe45, 0xfe59, 0xfe55, 0xfec3, 0xff2a, 0xffc8, 0xffe0, 0x006a,
0x0162, 0x00c2, 0x010b, 0x0179, 0x015e, 0x00f2, 0x007f, 0x009e,
0x0072, 0x00ab, 0xffb6, 0xff70, 0x003a, 0xff02, 0xfedf, 0xff4c,
0xff22, 0xff0e, 0xfeb9, 0xff3d, 0xff62, 0xff8c, 0x00ce, 0xffd2,
0xff9f, 0x017b, 0x013d, 0x00f3, 0x012e, 0x004e, 0x0081, 0x0077,
0xffba, 0xffe5, 0x002b, 0x008c, 0xffc9, 0xff4d, 0xffc5, 0x0022,
0x00d0, 0xfff4, 0xff82, 0xff9e, 0xff50, 0xff50, 0xfeb7, 0xff82,
0x0051, 0xffcb, 0xffa0, 0xfff2, 0x0050, 0xffc1, 0x004a, 0x023c,
0x012d, 0x0018, 0x0161, 0x0095, 0x0135, 0x0014, 0xff45, 0x0011,
0x0117, 0x0072, 0xfc89, 0x0370, 0x03a0, 0x05a0, 0x01f7, 0x0967,
0x1822, 0xf2cb, 0xe4e2, 0xe57f, 0xd776, 0xe9f6, 0xf5bb, 0xfc27,
0x1595, 0x23e7, 0x21f5, 0x1753, 0x1a08, 0x26eb, 0x1a72, 0x0c7c,
0x001e, 0xe797, 0xd9df, 0xda14, 0xdd93, 0xd899, 0xd4c1, 0xe25e,
0xe7ba, 0xf67b, 0x1d50, 0x2cbd, 0x2940, 0x24e7, 0x277f, 0x2ba5,
0x2361, 0x2053, 0x0e46, 0xe7d1, 0xd654, 0xd9fa, 0xdda9, 0xd6b0,
0xd1d3, 0xdaa2, 0xde9e, 0xeed9, 0x1663, 0x2855, 0x2644, 0x2489,
0x2303, 0x2583, 0x2102, 0x1de4, 0x0c20, 0xea89, 0xdd31, 0xe287,
0xe743, 0xdaf7, 0xd314, 0xde3c, 0xe481, 0xf661, 0x1c52, 0x288b,
0x1b7a, 0x1844, 0x23b5, 0x2a05, 0x2239, 0x169f, 0x04a7, 0xea9d,
0xdb4c, 0xdcca, 0xe12d, 0xdb72, 0xd4d4, 0xdea4, 0xe6d7, 0xf747,
0x1ae7, 0x2b19, 0x2582, 0x2091, 0x273c, 0x2a1e, 0x1d7e, 0x17df,
0x0837, 0xe881, 0xdb4e, 0xdf81, 0xdf33, 0xd6c6, 0xd54e, 0xdcb5,
0xe1f3, 0xf890, 0x1bbe, 0x2868, 0x227e, 0x1c92, 0x260d, 0x2b62,
0x2082, 0x1a91, 0x0864, 0xe75f, 0xd979, 0xdf72, 0xe1f7, 0xd799,
0xd499, 0xdd37, 0xe310, 0xfc1c, 0x2085, 0x2966, 0x1f41, 0x1b8d,
0x259a, 0x2a43, 0x22d9, 0x192a, 0xffe0, 0xe26e, 0xdaad, 0xe16d,
0xe1bb, 0xd62c, 0xd3d4, 0xddcf, 0xe53c, 0x008a, 0x2315, 0x28ae,
0x219d, 0x1f99, 0x2835, 0x28bf, 0x2124, 0x1baa, 0xffe2, 0xde80,
0xd886, 0xe110, 0xdf02, 0xd453, 0xd50b, 0xdbea, 0xe5a8, 0x064d,
0x242b, 0x2902, 0x2376, 0x200d, 0x2819, 0x282d, 0x21ad, 0x1879,
0xf9d0, 0xdcc7, 0xd869, 0xdfc9, 0xde60, 0xd496, 0xd62e, 0xdc40,
0xe92b, 0x0bb1, 0x2631, 0x2704, 0x20b7, 0x21fe, 0x286d, 0x25fc,
0x20a7, 0x1461, 0xf346, 0xda0b, 0xdbb7, 0xe190, 0xdaf5, 0xd396,
0xdbd2, 0xdf54, 0xeba7, 0x1426, 0x29ee, 0x26ed, 0x2160, 0x2335,
0x297c, 0x25a5, 0x1eaf, 0x0ce9, 0xec69, 0xd750, 0xd9ca, 0xe073,
0xd9ac, 0xd345, 0xd994, 0xe07d, 0xf5ce, 0x1a91, 0x2a2b, 0x25bf,
0x20a4, 0x2596, 0x29c6, 0x2377, 0x1c64, 0x064d, 0xe5e6, 0xd8bf,
0xde68, 0xe1b8, 0xd81f, 0xd360, 0xda7f, 0xe288, 0xfc65, 0x1f51,
0x294f, 0x2256, 0x1eea, 0x2641, 0x28fe, 0x24dd, 0x1b81, 0xfe12,
0xe0ca, 0xda89, 0xe0a1, 0xe057, 0xd67e, 0xd5c4, 0xdba1, 0xe31a,
0x0556, 0x266b, 0x28f2, 0x218b, 0x1f44, 0x2717, 0x27c7, 0x21d9,
0x15c9, 0xf747, 0xdd97, 0xda9b, 0xe23c, 0xdf46, 0xd419, 0xd634,
0xdd0d, 0xe9eb, 0x0c96, 0x25e2, 0x2644, 0x1fea, 0x2041, 0x28d1,
0x26c1, 0x20e0, 0x164b, 0xf5db, 0xdba5, 0xdafe, 0xe1c5, 0xddad,
0xd3c4, 0xd6ee, 0xdb8c, 0xeb0b, 0x1395, 0x294b, 0x24c4, 0x1ee4,
0x228c, 0x29a9, 0x2581, 0x2093, 0x0fad, 0xed42, 0xdad5, 0xddeb,
0xe34b, 0xdc1a, 0xd323, 0xd912, 0xdde6, 0xf057, 0x1964, 0x2aea,
0x234d, 0x1bfe, 0x2106, 0x29f9, 0x25e5, 0x20eb, 0x0e12, 0xe980,
0xd698, 0xdcad, 0xe4c9, 0xdb65, 0xd2a4, 0xd913, 0xde59, 0xf3b4,
0x1a85, 0x29cb, 0x24bc, 0x1fab, 0x2359, 0x29a8, 0x244a, 0x1e3e,
0x0bac, 0xe9bf, 0xd8b8, 0xdc3e, 0xe1a6, 0xda33, 0xd318, 0xd8ac,
0xdd33, 0xf4ec, 0x1d03, 0x2a42, 0x2527, 0x1fac, 0x2303, 0x2a52,
0x25db, 0x2117, 0x0bf0, 0xe6e8, 0xd6d7, 0xdd56, 0xe3e0, 0xda45,
0xd268, 0xd8aa, 0xdcb1, 0xf2f1, 0x1bac, 0x2a58, 0x24ed, 0x1f57,
0x22fb, 0x2a21, 0x2582, 0x2095, 0x0bbc, 0xe7b8, 0xd84e, 0xdd09,
0xe20c, 0xdaea, 0xd3a3, 0xd9fe, 0xdef1, 0xf1fb, 0x1996, 0x29f6,
0x250b, 0x1f6c, 0x228c, 0x2a58, 0x263e, 0x20f1, 0x0d9f, 0xe85d,
0xd74b, 0xdd4e, 0xe27f, 0xdb5f, 0xd335, 0xd7e2, 0xdd4f, 0xf294,
0x1a85, 0x2978, 0x24e8, 0x2044, 0x22bd, 0x2a07, 0x2698, 0x20da,
0x0b78, 0xe654, 0xd795, 0xde86, 0xe30d, 0xdbd1, 0xd447, 0xd9c3,
0xe030, 0xf3d3, 0x18e2, 0x28c7, 0x2497, 0x1f3a, 0x2259, 0x28fa,
0x2565, 0x1f47, 0x09e5, 0xe7ef, 0xd93b, 0xdd93, 0xe2cd, 0xdb76,
0xd2a9, 0xd8eb, 0xdfe5, 0xf38a, 0x1a10, 0x29f4, 0x2439, 0x1e81,
0x22ff, 0x2a74, 0x25eb, 0x20c6, 0x0bd4, 0xe7ba, 0xd781, 0xdcf1,
0xe451, 0xdc04, 0xd2a4, 0xda99, 0xe0ff, 0xf41a, 0x1999, 0x28b7,
0x2477, 0x1d6e, 0x20f7, 0x2a37, 0x2552, 0x1f54, 0x0ab8, 0xe772,
0xd8a3, 0xdd16, 0xe1a4, 0xd9bd, 0xd2e9, 0xda7c, 0xe02a, 0xf636,
0x1d2e, 0x2a1a, 0x24c8, 0x1f54, 0x237b, 0x2a4c, 0x24f2, 0x1e74,
0x07b6, 0xe5a7, 0xd903, 0xddb4, 0xe0fa, 0xd993, 0xd42a, 0xdb0b,
0xe0db, 0xf862, 0x1e38, 0x292b, 0x22f0, 0x1eb2, 0x244f, 0x299d,
0x24b2, 0x1e8b, 0x061b, 0xe3a7, 0xd7d0, 0xddc3, 0xe133, 0xd8b8,
0xd486, 0xda97, 0xe027, 0xfd4c, 0x21e4, 0x28be, 0x224f, 0x1e61,
0x25ab, 0x2a10, 0x24a1, 0x1c33, 0xffb1, 0xe073, 0xda34, 0xe103,
0xe143, 0xd6b3, 0xd4d6, 0xdabf, 0xe278, 0x050a, 0x2559, 0x2724,
0x206c, 0x1d8a, 0x2623, 0x28db, 0x222f, 0x1869, 0xfb0c, 0xdebe,
0xda88, 0xe128, 0xdfc6, 0xd4e6, 0xd695, 0xde27, 0xe839, 0x0b4a,
0x26b5, 0x25f1, 0x1f74, 0x2018, 0x28ea, 0x270e, 0x1fb1, 0x14a9,
0xf54d, 0xda7d, 0xda17, 0xe1c9, 0xde39, 0xd508, 0xd7f5, 0xddaa,
0xec1c, 0x1297, 0x2932, 0x2584, 0x1fa5, 0x215e, 0x298f, 0x2724,
0x20c7, 0x109e, 0xed5e, 0xd7c0, 0xdb2f, 0xe1bb, 0xdb69, 0xd2df,
0xd833, 0xdec4, 0xf22c, 0x18dc, 0x2a35, 0x251b, 0x1f3f, 0x22a6,
0x2a07, 0x2615, 0x1feb, 0x0d08, 0xe995, 0xd820, 0xdd3a, 0xe150,
0xd98f, 0xd300, 0xda0b, 0xdfb7, 0xf414, 0x1b1b, 0x295b, 0x2333,
0x1e69, 0x23db, 0x2a7e, 0x2537, 0x1fd9, 0x09db, 0xe659, 0xd871,
0xdede, 0xe3c8, 0xd989, 0xd253, 0xd979, 0xdf1c, 0xf7d6, 0x1ec3,
0x2a2e, 0x21e5, 0x1c2f, 0x2436, 0x2a4f, 0x241a, 0x1d05, 0x0548,
0xe44d, 0xd8ae, 0xe0d5, 0xe4c8, 0xd84f, 0xd298, 0xdb09, 0xe0bc,
0xfc25, 0x22aa, 0x2a75, 0x2218, 0x1e12, 0x2681, 0x2aa6, 0x2457,
0x1d91, 0x0159, 0xdf33, 0xd67b, 0xdf48, 0xe226, 0xd631, 0xd376,
0xdb34, 0xe091, 0xfe8f, 0x241d, 0x2a87, 0x2332, 0x1f86, 0x2611,
0x28fe, 0x23d2, 0x1ef8, 0x02cd, 0xdefa, 0xd5ef, 0xddf6, 0xe12f,
0xd735, 0xd3f5, 0xdb0c, 0xdfb9, 0xfd66, 0x248b, 0x2bca, 0x241a,
0x1ef6, 0x25b8, 0x2a2a, 0x246a, 0x1eaa, 0x0248, 0xdf8e, 0xd747,
0xde8f, 0xe1e0, 0xd827, 0xd3cf, 0xda8e, 0xe007, 0xfb3b, 0x219f,
0x2ac7, 0x231d, 0x1ea4, 0x255e, 0x2a2e, 0x2489, 0x1eeb, 0x05a9,
0xe2e8, 0xd805, 0xdeb4, 0xe283, 0xd8b3, 0xd2f3, 0xd9dd, 0xdf63,
0xf8d6, 0x1fce, 0x2a7b, 0x237f, 0x1f85, 0x2509, 0x29a5, 0x24ec,
0x1f8d, 0x06e5, 0xe2c8, 0xd692, 0xddac, 0xe1d4, 0xd909, 0xd37a,
0xd98a, 0xdf3f, 0xf7fd, 0x1eac, 0x2ac7, 0x2486, 0x1ff2, 0x24a9,
0x2a5f, 0x256b, 0x1ebc, 0x075f, 0xe45a, 0xd75e, 0xde5a, 0xe284,
0xd97f, 0xd3be, 0xd9c8, 0xdf75, 0xf6cd, 0x1c9d, 0x2996, 0x23e3,
0x1f19, 0x24a0, 0x2a81, 0x25aa, 0x1fd7, 0x0766, 0xe44d, 0xd870,
0xdf09, 0xe30d, 0xd922, 0xd249, 0xd985, 0xdf20, 0xf54d, 0x1c2b,
0x29cb, 0x2445, 0x1f54, 0x241e, 0x2aa5, 0x25c2, 0x1ff3, 0x093b,
0xe5e2, 0xd837, 0xde46, 0xe316, 0xda28, 0xd2d7, 0xd90b, 0xdecc,
0xf4d6, 0x1bff, 0x2ae4, 0x24c1, 0x1ed5, 0x237a, 0x2a79, 0x25e4,
0x1f1b, 0x08a0, 0xe65e, 0xd8c0, 0xde21, 0xe22f, 0xd9ce, 0xd2fa,
0xd8e6, 0xdf57, 0xf53f, 0x1c1f, 0x2a52, 0x2463, 0x204f, 0x2433,
0x2a69, 0x2613, 0x1f9c, 0x0920, 0xe4b9, 0xd761, 0xde2f, 0xe19a,
0xd93c, 0xd275, 0xd912, 0xdfbc, 0xf698, 0x1dbe, 0x2a7f, 0x248e,
0x20dd, 0x253c, 0x2a69, 0x2548, 0x1ef1, 0x07b5, 0xe3c2, 0xd798,
0xdda0, 0xdf93, 0xd788, 0xd3d0, 0xdbfd, 0xe14e, 0xf8b2, 0x1ec1,
0x2a28, 0x22bf, 0x1f01, 0x25ff, 0x29be, 0x23e3, 0x1c79, 0x035e,
0xe2e4, 0xd967, 0xdfab, 0xe0b6, 0xd72f, 0xd40b, 0xdb3c, 0xe221,
0xfebc, 0x2357, 0x29b3, 0x215e, 0x1f1a, 0x2713, 0x2984, 0x2351,
0x1b42, 0xfecf, 0xde84, 0xd924, 0xe111, 0xe055, 0xd556, 0xd45e,
0xdc5f, 0xe47f, 0x04ac, 0x2566, 0x2835, 0x2184, 0x2118, 0x2895,
0x28ad, 0x22d1, 0x17ec, 0xf7d3, 0xdc2b, 0xdaa9, 0xe14f, 0xde61,
0xd468, 0xd5c2, 0xdca4, 0xe7da, 0x0b3d, 0x27bf, 0x2744, 0x211e,
0x21d8, 0x2890, 0x26a2, 0x216a, 0x136c, 0xf187, 0xd894, 0xdca4,
0xe06d, 0xde67, 0xdf37, 0xd7a6, 0xda60, 0xeb63, 0x0fcd, 0x2907,
0x25e0, 0x1fb7, 0x20d5, 0x29a4, 0x26be, 0x207d, 0x1143, 0xeecc,
0xd932, 0xdb55, 0xe1fc, 0xdcc3, 0xd396, 0xd79d, 0xde85, 0xeec3,
0x1396, 0x2913, 0x25da, 0x1fe6, 0x2297, 0x2a00, 0x262c, 0x1f8f,
0x0ed2, 0xed2b, 0xd99d, 0xdcca, 0xe269, 0xdbbc, 0xd288, 0xd7ab,
0xdeb8, 0xf0ac, 0x178c, 0x29fa, 0x249a, 0x1e38, 0x2246, 0x2abe,
0x258f, 0x1dee, 0x0a53, 0xe9a5, 0xda49, 0xde75, 0xe440, 0xdc8d,
0xd2fd, 0xd93e, 0xe1aa, 0xf49a, 0x19c7, 0x2989, 0x23c5, 0x1f8b,
0x22b2, 0x2901, 0x2421, 0x1cf0, 0x099c, 0xe7f4, 0xd968, 0xde88,
0xe307, 0xdb95, 0xd2cf, 0xd945, 0xe0e2, 0xf583, 0x1c2e, 0x2a3a,
0x23c5, 0x1dac, 0x216a, 0x2a2d, 0x2614, 0x1ea8, 0x08a7, 0xe64c,
0xd801, 0xdeec, 0xe5b9, 0xdd89, 0xd1a0, 0xd4b1, 0xdbc0, 0xe7dc,
0x0d71, 0x28a8, 0x2779, 0x238a, 0x232b, 0x290c, 0x2975, 0x23b3,
0x160e, 0xf5a1, 0xd923, 0xd7ba, 0xe01a, 0xddc9, 0xd493, 0xd64a,
0xe11e, 0xe5fe, 0x0b8d, 0x296e, 0x23b4, 0x2cbc, 0x250a, 0x0cd4,
0x0257, 0x0762, 0x0e5f, 0x0a2a, 0xfb3d, 0xf1ea, 0xf764, 0xf951,
0xf4be, 0xee32, 0xed9c, 0xf596, 0xf92e, 0xf969, 0xf9ee, 0xfc48,
0x01a3, 0x057f, 0x05cb, 0x04a3, 0x06c8, 0x0a5d, 0x0ae3, 0x0932,
0x06a8, 0x03f7, 0x039e, 0x028a, 0xffd3, 0xfeeb, 0xfdcb, 0xfb6c,
0xfa68, 0xfa7b, 0xfaf0, 0xfbfb, 0xfc13, 0xfb6b, 0xfb93, 0xfc5a,
0xfd8a, 0x008d, 0x0256, 0x0152, 0x013b, 0x025e, 0x0359, 0x0474,
0x0505, 0x0495, 0x0441, 0x0288, 0x0092, 0x00f9, 0x011c, 0xff6b,
0xfe78, 0xfe18, 0xfc7f, 0xfb42, 0xfca7, 0xfd32, 0xfb8c, 0xfbeb,
0xfd9c, 0xff71, 0x0130, 0x020d, 0x02b3, 0x028c, 0x01d8, 0x033a,
0x0476, 0x03e1, 0x029b, 0x01cb, 0x0119, 0x00d2, 0x0165, 0x0025,
0xfe8a, 0xfc11, 0xf9f2, 0xfab0, 0xfaa0, 0xfa9a, 0xfce0, 0xfe08,
0xfe75, 0x0105, 0x0337, 0x03ea, 0x0434, 0x03ef, 0x03b0, 0x0404,
0x042f, 0x03c3, 0x0305, 0x00ef, 0xff59, 0xfeee, 0xfe4a, 0xfdb8,
0xfc9e, 0xfc3c, 0xfc73, 0xfcc2, 0xfd07, 0xfcb8, 0xfd99, 0xff0c,
0xffbe, 0x0064, 0x00f3, 0x012d, 0x0277, 0x034f, 0x028f, 0x028b,
0x02e7, 0x0231, 0x01b5, 0x0148, 0x0138, 0x00fc, 0x0033, 0x0007,
0xfe94, 0xfd82, 0xfda5, 0xfd78, 0xfd0f, 0xfd61, 0xfdfa, 0xfe98,
0xff23, 0xff85, 0x0009, 0x00b8, 0x0156, 0x0102, 0x0258, 0x0278,
0x01ba, 0x023a, 0x00fd, 0x00d5, 0x009d, 0xff5b, 0xff67, 0xff49,
0xff35, 0xffdb, 0xff7a, 0xff15, 0xff30, 0xfee0, 0xfe9a, 0xfebe,
0xff20, 0xff76, 0x0071, 0x00a5, 0xffef, 0x0034, 0x00c3, 0x01ce,
0x0174, 0x0035, 0x0078, 0x007c, 0x00aa, 0x0081, 0xfffd, 0xfff1,
0xff44, 0xfec8, 0xff2b, 0xff81, 0xffa6, 0x002a, 0x0010, 0xff90,
0xff80, 0x0044, 0x0012, 0xffa7, 0x0060, 0x0007, 0xffd9, 0x0079,
0x008f, 0x0099, 0x001c, 0xffa8, 0xff93, 0xff52, 0xff9a, 0x0060,
};
extern int16_t *besetztton_spl;
extern int besetztton_size;
void init_besetzton(void)
{
besetztton_spl = pattern;
besetztton_size = sizeof(pattern) / sizeof(pattern[0]);
}

3
src/common/besetztton.h Normal file
View File

@ -0,0 +1,3 @@
void init_besetzton(void);

920
src/common/call.c Normal file
View File

@ -0,0 +1,920 @@
/* built-in call control
*
* (C) 2016 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 <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <termios.h>
#include "../common/debug.h"
#include "../common/sender.h"
#include "cause.h"
#include "call.h"
#include "mncc_sock.h"
#include "freiton.h"
#include "besetztton.h"
extern int use_mncc_sock;
extern int send_patterns;
/* stream patterns/announcements */
int16_t *ansage_27_spl = NULL;
int16_t *freiton_spl = NULL;
int16_t *besetztton_spl = NULL;
int ansage_27_size = 0;
int freiton_size = 0;
int besetztton_size = 0;
enum call_state {
CALL_IDLE = 0,
CALL_SETUP_MO,
CALL_SETUP_MT,
CALL_ALERTING,
CALL_CONNECT,
CALL_DISCONNECTED,
};
enum audio_pattern {
PATTERN_NONE = 0,
PATTERN_RINGBACK,
PATTERN_BUSY,
PATTERN_OUTOFORDER,
};
static int new_callref = 0; /* toward mobile */
/* built in call instance */
typedef struct call {
int callref;
enum call_state state;
int disc_cause; /* cause that has been sent by transceiver instance for release */
char station_id[6];
char dialing[16];
void *sound; /* headphone interface */
int latspl; /* sample latency at sound interface */
samplerate_t srstate; /* patterns/announcement upsampling */
jitter_t audio; /* headphone audio dejittering */
int audio_pos; /* position when playing patterns */
int loopback; /* loopback test for echo */
} call_t;
static call_t call;
static void call_new_state(enum call_state state)
{
call.state = state;
call.audio_pos = 0;
}
static void get_call_patterns(int16_t *samples, int length, enum audio_pattern pattern)
{
const int16_t *spl = NULL;
int size = 0, max = 0, pos;
switch (pattern) {
case PATTERN_RINGBACK:
spl = freiton_spl;
size = freiton_size;
max = 8 * 5000;
break;
case PATTERN_BUSY:
busy:
spl = besetztton_spl;
size = besetztton_size;
max = 8 * 750;
break;
case PATTERN_OUTOFORDER:
spl = ansage_27_spl;
size = ansage_27_size;
if (!spl || !size)
goto busy;
max = size;
break;
default:
return;
}
/* stream sample */
pos = call.audio_pos;
while(length--) {
if (pos >= size)
*samples++ = 0;
else
*samples++ = spl[pos] >> 1;
if (++pos == max)
pos = 0;
}
call.audio_pos = pos;
}
static enum audio_pattern cause2pattern(int cause)
{
int pattern;
switch (cause) {
case CAUSE_OUTOFORDER:
pattern = PATTERN_OUTOFORDER;
break;
default:
pattern = PATTERN_BUSY;
}
return pattern;
}
/* MNCC call instance */
typedef struct process {
struct process *next;
int callref;
enum call_state state;
int audio_disconnected; /* if not associated with transceiver anymore */
enum audio_pattern pattern;
int audio_pos;
} process_t;
static process_t *process_head = NULL;
static void create_process(int callref, int state)
{
process_t *process;
process = calloc(sizeof(*process), 1);
if (!process) {
PDEBUG(DCALL, DEBUG_ERROR, "No memory!\n");
abort();
}
process->next = process_head;
process_head = process;
process->callref = callref;
process->state = state;
}
static void destroy_process(int callref)
{
process_t *process = process_head;
process_t **process_p = &process_head;
while (process) {
if (process->callref == callref) {
*process_p = process->next;
free(process);
return;
}
process_p = &process->next;
process = process->next;
}
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}
static int is_process(int callref)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref)
return 1;
process = process->next;
}
return 0;
}
static enum call_state is_process_state(int callref)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref)
return process->state;
process = process->next;
}
return CALL_IDLE;
}
static void set_state_process(int callref, enum call_state state)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref) {
process->state = state;
return;
}
process = process->next;
}
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}
static void set_pattern_process(int callref, enum audio_pattern pattern)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref) {
process->pattern = pattern;
process->audio_pos = 0;
return;
}
process = process->next;
}
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}
/* disconnect audio, now send audio directly from pattern/announcement, not from transceiver */
static void disconnect_process(int callref, int cause)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref) {
process->pattern = cause2pattern(cause);
process->audio_disconnected = 1;
process->audio_pos = 0;
return;
}
process = process->next;
}
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}
/* check if audio is disconnected */
static int is_process_disconnected(int callref)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref) {
return process->audio_disconnected;
}
process = process->next;
}
PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref);
return 0;
}
/* check if pattern is set, so we send patterns and announcements */
static int is_process_pattern(int callref)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref) {
return (process->pattern != PATTERN_NONE);
}
process = process->next;
}
PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref);
return 0;
}
static void get_process_patterns(process_t *process, int16_t *samples, int length)
{
const int16_t *spl = NULL;
int size = 0, max = 0, pos;
switch (process->pattern) {
case PATTERN_RINGBACK:
spl = freiton_spl;
size = freiton_size;
max = 8 * 5000;
break;
case PATTERN_BUSY:
spl = besetztton_spl;
size = besetztton_size;
max = 8 * 750;
break;
case PATTERN_OUTOFORDER:
spl = ansage_27_spl;
size = ansage_27_size;
max = size;
break;
default:
return;
}
/* stream sample */
pos = process->audio_pos;
while(length--) {
if (pos >= size)
*samples++ = 0;
else
*samples++ = spl[pos] >> 1;
if (++pos == max)
pos = 0;
}
process->audio_pos = pos;
}
static struct termios term_orig;
int call_init(const char *station_id, const char *sounddev, int samplerate, int latency, int loopback)
{
struct termios term;
int rc = 0;
/* init common tones */
init_freiton();
init_besetzton();
if (use_mncc_sock)
return 0;
if (!loopback) {
tcgetattr(0, &term_orig);
term = term_orig;
term.c_lflag &= ~(ISIG|ICANON|ECHO);
term.c_cc[VMIN]=1;
term.c_cc[VTIME]=2;
tcsetattr(0, TCSANOW, &term);
}
memset(&call, 0, sizeof(call));
strncpy(call.station_id, station_id, sizeof(call.station_id) - 1);
call.latspl = latency * samplerate / 1000;
call.loopback = loopback;
if (!sounddev[0])
return 0;
/* open sound device for call control */
call.sound = sound_open(sounddev, samplerate);
if (!call.sound) {
PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n");
rc = -EIO;
goto error;
}
rc = init_samplerate(&call.srstate, samplerate);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
goto error;
}
rc = jitter_create(&call.audio, samplerate / 5);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
goto error;
}
return 0;
error:
call_cleanup();
return rc;
}
void call_cleanup(void)
{
if (use_mncc_sock)
return;
if (!call.loopback)
tcsetattr(0, TCSANOW, &term_orig);
/* close sound devoice */
if (call.sound)
sound_close(call.sound);
jitter_destroy(&call.audio);
if (process_head) {
PDEBUG(DMNCC, DEBUG_ERROR, "Not all MNCC instances have been released!\n");
}
}
static int get_char()
{
struct timeval tv = {0, 0};
fd_set fds;
char c = 0;
int __attribute__((__unused__)) rc;
FD_ZERO(&fds);
FD_SET(0, &fds);
select(0+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(0, &fds)) {
rc = read(0, &c, 1);
return c;
} else
return -1;
}
static int process_ui(void)
{
int c;
c = get_char();
/* break */
if (c == 3)
return 1;
switch (call.state) {
case CALL_IDLE:
if (c > 0) {
if (c >= '0' && c <= '9' && strlen(call.station_id) < 5) {
call.station_id[strlen(call.station_id) + 1] = '\0';
call.station_id[strlen(call.station_id)] = c;
}
if ((c == 8 || c == 127) && strlen(call.station_id))
call.station_id[strlen(call.station_id) - 1] = '\0';
if (c == 'd' && strlen(call.station_id) == 5) {
int rc;
int callref = ++new_callref;
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call to %s\n", call.station_id);
call.dialing[0] = '\0';
call_new_state(CALL_SETUP_MT);
call.callref = callref;
rc = call_out_setup(callref, call.station_id);
if (rc < 0) {
PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
call_new_state(CALL_DISCONNECTED);
call.callref = 0;
call.disc_cause = -rc;
}
}
}
printf("on-hook: %s%s (enter 0..9 or d=dial)\r", call.station_id, "....." + strlen(call.station_id));
break;
case CALL_SETUP_MO:
case CALL_SETUP_MT:
case CALL_ALERTING:
case CALL_CONNECT:
case CALL_DISCONNECTED:
if (c > 0) {
if (c == 'h') {
PDEBUG(DCALL, DEBUG_INFO, "Call hangup\n");
call_new_state(CALL_IDLE);
if (call.callref) {
call_out_release(call.callref, CAUSE_NORMAL);
call.callref = 0;
}
}
}
if (call.state == CALL_SETUP_MT)
printf("call setup: %s (enter h=hangup)\r", call.station_id);
if (call.state == CALL_ALERTING)
printf("call ringing: %s (enter h=hangup)\r", call.station_id);
if (call.state == CALL_CONNECT) {
if (call.dialing[0])
printf("call active: %s->%s (enter h=hangup)\r", call.station_id, call.dialing);
else
printf("call active: %s (enter h=hangup)\r", call.station_id);
}
if (call.state == CALL_DISCONNECTED)
printf("call disconnected: %s (enter h=hangup)\r", cause_name(call.disc_cause));
break;
}
fflush(stdout);
return 0;
}
/* get keys from keyboad to control call via console
* returns 1 on exit (ctrl+c) */
int process_call(void)
{
if (use_mncc_sock) {
mncc_handle();
return 0;
}
if (!call.loopback) {
if (process_ui())
return 1;
}
if (!call.sound)
return 0;
/* handle audio, if sound device is used */
int16_t samples[call.latspl];
int count;
int rc;
count = sound_get_inbuffer(call.sound);
if (count < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count);
if (count == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
return 0;
}
if (count < call.latspl) {
int16_t up[count];
count = call.latspl - count;
switch(call.state) {
case CALL_ALERTING:
count = count / call.srstate.factor;
get_call_patterns(samples, count, PATTERN_RINGBACK);
count = samplerate_upsample(&call.srstate, samples, count, up);
/* prevent click after hangup */
jitter_clear(&call.audio);
break;
case CALL_DISCONNECTED:
count = count / call.srstate.factor;
get_call_patterns(samples, count, cause2pattern(call.disc_cause));
count = samplerate_upsample(&call.srstate, samples, count, up);
/* prevent click after hangup */
jitter_clear(&call.audio);
break;
default:
jitter_load(&call.audio, up, count);
}
rc = sound_write(call.sound, up, up, count);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc);
if (rc == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
return 0;
}
}
count = sound_read(call.sound, samples, call.latspl);
if (count < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count);
if (count == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
return 0;
}
if (count) {
int16_t down[count]; /* more than enough */
if (call.loopback == 3)
jitter_save(&call.audio, samples, count);
count = samplerate_downsample(&call.srstate, samples, count, down);
call_rx_audio(call.callref, down, count);
}
return 0;
}
/* Setup is received from transceiver. */
int call_in_setup(int callref, const char *callerid, const char *dialing)
{
if (callref < 0x4000000) {
PDEBUG(DCALL, DEBUG_ERROR, "Invalid callref from mobile station, please fix!\n");
abort();
}
if (!strcmp(dialing, "0"))
dialing = "operator";
PDEBUG(DCALL, DEBUG_INFO, "Incomming call from '%s' to '%s'\n", callerid, dialing);
if (use_mncc_sock) {
uint8_t buf[sizeof(struct gsm_mncc)];
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
int rc;
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_SETUP_IND;
mncc->callref = callref;
mncc->fields |= MNCC_F_CALLING;
strncpy(mncc->calling.number, callerid, sizeof(mncc->calling.number) - 1);
mncc->calling.type = 4; /* caller ID is of type 'subscriber' */
mncc->fields |= MNCC_F_CALLED;
strncpy(mncc->called.number, dialing, sizeof(mncc->called.number) - 1);
mncc->called.type = 0; /* dialing is of type 'unknown' */
mncc->lchan_type = GSM_LCHAN_TCH_F;
mncc->fields |= MNCC_F_BEARER_CAP;
mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
mncc->bearer_cap.speech_ver[1] = -1;
PDEBUG(DMNCC, DEBUG_INFO, "Sending MNCC call towards Network\n");
create_process(callref, CALL_SETUP_MO);
rc = mncc_write(buf, sizeof(struct gsm_mncc));
if (rc < 0) {
PDEBUG(DCALL, DEBUG_NOTICE, "We have no MNCC connection, rejecting.\n");
destroy_process(callref);
return -CAUSE_TEMPFAIL;
}
return 0;
}
/* setup is also allowed on disconnected call */
if (call.state == CALL_DISCONNECTED)
call_new_state(CALL_IDLE);
if (call.state != CALL_IDLE) {
PDEBUG(DCALL, DEBUG_NOTICE, "We are busy, rejecting.\n");
return -CAUSE_BUSY;
}
call.callref = callref;
call_new_state(CALL_CONNECT);
if (callerid[0]) {
strncpy(call.station_id, callerid, 5);
call.station_id[5] = '\0';
}
strncpy(call.dialing, dialing, sizeof(call.dialing) - 1);
call.dialing[sizeof(call.dialing) - 1] = '\0';
return 0;
}
/* Transceiver indicates alerting. */
void call_in_alerting(int callref)
{
PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n");
if (use_mncc_sock) {
uint8_t buf[sizeof(struct gsm_mncc)];
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
if (!send_patterns) {
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_ALERT_IND;
mncc->callref = callref;
PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC alerting towards Network\n");
mncc_write(buf, sizeof(struct gsm_mncc));
} else
set_pattern_process(callref, PATTERN_RINGBACK);
return;
}
if (call.callref != callref) {
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
call_out_release(callref, CAUSE_INVALCALLREF);
return;
}
call_new_state(CALL_ALERTING);
}
/* Transceiver indicates answer. */
static void _indicate_answer(int callref, const char *connectid)
{
uint8_t buf[sizeof(struct gsm_mncc)];
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_SETUP_CNF;
mncc->callref = callref;
mncc->fields |= MNCC_F_CONNECTED;
strncpy(mncc->connected.number, connectid, sizeof(mncc->connected.number) - 1);
mncc->connected.type = 0;
PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC answer towards Network\n");
mncc_write(buf, sizeof(struct gsm_mncc));
}
void call_in_answer(int callref, const char *connectid)
{
PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connectid);
if (use_mncc_sock) {
_indicate_answer(callref, connectid);
set_pattern_process(callref, PATTERN_NONE);
set_state_process(callref, CALL_CONNECT);
return;
}
if (call.callref != callref) {
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
call_out_release(callref, CAUSE_INVALCALLREF);
return;
}
call_new_state(CALL_CONNECT);
strncpy(call.station_id, connectid, 5);
call.station_id[5] = '\0';
}
/* Transceiver indicates release. */
void call_in_release(int callref, int cause)
{
PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause);
if (use_mncc_sock) {
uint8_t buf[sizeof(struct gsm_mncc)];
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_REL_IND;
mncc->callref = callref;
mncc->fields |= MNCC_F_CAUSE;
mncc->cause.location = 1; /* private local */
mncc->cause.value = cause;
if (is_process(callref)) {
if (!send_patterns
|| is_process_state(callref) == CALL_DISCONNECTED
|| is_process_state(callref) == CALL_SETUP_MO) {
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
destroy_process(callref);
mncc_write(buf, sizeof(struct gsm_mncc));
} else {
disconnect_process(callref, cause);
}
} else {
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
mncc_write(buf, sizeof(struct gsm_mncc));
}
return;
}
if (call.callref != callref) {
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
/* don't send release, because caller already released */
return;
}
call_new_state(CALL_DISCONNECTED);
call.callref = 0;
call.disc_cause = cause;
}
/* forward audio to MNCC or call instance */
void call_tx_audio(int callref, int16_t *samples, int count)
{
if (use_mncc_sock) {
uint8_t buf[sizeof(struct gsm_data_frame) + count * sizeof(int16_t)];
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
/* if we are disconnected, ignore audio */
if (is_process_pattern(callref))
return;
/* forward audio */
data->msg_type = ANALOG_8000HZ;
data->callref = callref;
memcpy(data->data, samples, count * sizeof(int16_t));
mncc_write(buf, sizeof(buf));
return;
}
/* save audio from transceiver to jitter buffer */
if (call.sound) {
int16_t up[count * call.srstate.factor];
count = samplerate_upsample(&call.srstate, samples, count, up);
jitter_save(&call.audio, up, count);
}
}
/* clock that is used to transmit patterns */
void call_mncc_clock(void)
{
process_t *process = process_head;
uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)];
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
while(process) {
if (process->pattern != PATTERN_NONE) {
data->msg_type = ANALOG_8000HZ;
data->callref = process->callref;
/* try to get patterns, else copy the samples we got */
get_process_patterns(process, (int16_t *)data->data, 160);
mncc_write(buf, sizeof(buf));
}
process = process->next;
}
}
/* mncc messages received from network */
void call_mncc_recv(uint8_t *buf, int length)
{
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
char number[sizeof(mncc->called.number)];
int callref;
int rc;
if (mncc->msg_type == ANALOG_8000HZ) {
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
int count = (length - sizeof(struct gsm_data_frame)) / 2;
/* if we are disconnected, ignore audio */
if (is_process_pattern(data->callref))
return;
call_rx_audio(data->callref, (int16_t *)data->data, count);
return;
}
callref = mncc->callref;
strcpy(number, mncc->called.number);
if (is_process_disconnected(callref)) {
switch(mncc->msg_type) {
case MNCC_DISC_REQ:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value);
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected, releasing!\n");
destroy_process(callref);
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
mncc->msg_type = MNCC_REL_IND;
mncc_write(buf, sizeof(struct gsm_mncc));
break;
case MNCC_REL_REQ:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value);
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
destroy_process(callref);
break;
}
return;
}
switch(mncc->msg_type) {
case MNCC_SETUP_REQ:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC call from Network to '%s'\n", mncc->called.number);
if (mncc->callref >= 0x4000000) {
fprintf(stderr, "Invalid callref from network, please fix!\n");
abort();
}
PDEBUG(DMNCC, DEBUG_INFO, "Confirming MNCC call to Network\n");
memset(buf, 0, length);
mncc->msg_type = MNCC_CALL_CONF_IND;
mncc->callref = callref;
mncc->lchan_type = GSM_LCHAN_TCH_F;
mncc->fields |= MNCC_F_BEARER_CAP;
mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
mncc->bearer_cap.speech_ver[1] = -1;
mncc_write(buf, sizeof(struct gsm_mncc));
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from to '%s'\n", number);
create_process(callref, CALL_SETUP_MT);
rc = call_out_setup(callref, number);
if (rc < 0) {
PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
if (send_patterns) {
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
_indicate_answer(callref, number);
disconnect_process(callref, -rc);
break;
}
PDEBUG(DMNCC, DEBUG_INFO, "Rejecting MNCC call towards Network (cause=%d)\n", -rc);
memset(buf, 0, length);
mncc->msg_type = MNCC_REL_IND;
mncc->callref = callref;
mncc->fields |= MNCC_F_CAUSE;
mncc->cause.location = 1; /* private local */
mncc->cause.value = -rc;
mncc_write(buf, sizeof(struct gsm_mncc));
destroy_process(callref);
break;
}
if (send_patterns) {
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
set_state_process(callref, CALL_CONNECT);
_indicate_answer(callref, number);
break;
}
break;
case MNCC_SETUP_RSP:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC answer from Network\n");
set_state_process(callref, CALL_CONNECT);
break;
case MNCC_DISC_REQ:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value);
set_state_process(callref, CALL_DISCONNECTED);
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n");
call_out_disconnect(callref, mncc->cause.value);
break;
case MNCC_REL_REQ:
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value);
destroy_process(callref);
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
call_out_release(callref, mncc->cause.value);
break;
}
}
/* break down of MNCC socket */
void call_mncc_flush(void)
{
while(process_head) {
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket closed, releasing call\n");
call_out_release(process_head->callref, CAUSE_TEMPFAIL);
destroy_process(process_head->callref);
/* note: callref is released by sender's instance */
}
}

26
src/common/call.h Normal file
View File

@ -0,0 +1,26 @@
int call_init(const char *station_id, const char *sounddev, int samplerate, int latency, int loopback);
void call_cleanup(void);
int process_call(void);
/* received messages */
int call_in_setup(int callref, const char *callerid, const char *dialing);
void call_in_alerting(int callref);
void call_in_answer(int callref, const char *connecid);
void call_in_release(int callref, int cause);
/* send messages */
int call_out_setup(int callref, char *dialing);
void call_out_disconnect(int callref, int cause);
void call_out_release(int callref, int cause);
/* send and receive audio */
void call_rx_audio(int callref, int16_t *samples, int count);
void call_tx_audio(int callref, int16_t *samples, int count);
/* receive from mncc */
void call_mncc_recv(uint8_t *buf, int length);
void call_mncc_flush(void);
/* clock to transmit to */
void call_mncc_clock(void);

48
src/common/cause.c Normal file
View File

@ -0,0 +1,48 @@
/* Clear cause names
*
* (C) 2016 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 "cause.h"
const char *cause_name(int cause)
{
static char cause_str[16];
switch (cause) {
case CAUSE_NORMAL:
return "hangup";
case CAUSE_BUSY:
return "busy";
case CAUSE_NOANSWER:
return "no-answer";
case CAUSE_OUTOFORDER:
return "out-of-order";
case CAUSE_INVALNUMBER:
return "invalid-number";
case CAUSE_NOCHANNEL:
return "no-channel";
case CAUSE_TEMPFAIL:
return "link-failure";
default:
sprintf(cause_str, "cause=%d\n", cause);
return cause_str;
}
}

12
src/common/cause.h Normal file
View File

@ -0,0 +1,12 @@
#define CAUSE_NORMAL 16
#define CAUSE_BUSY 17
#define CAUSE_NOANSWER 19
#define CAUSE_OUTOFORDER 27
#define CAUSE_INVALNUMBER 28
#define CAUSE_NOCHANNEL 34
#define CAUSE_TEMPFAIL 41
#define CAUSE_INVALCALLREF 81
const char *cause_name(int cause);

69
src/common/debug.c Normal file
View File

@ -0,0 +1,69 @@
/* Simple debug functions for level and category filtering
*
* (C) 2016 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "debug.h"
const char *debug_level[] = {
"debug ",
"info ",
"notice ",
"error ",
};
struct debug_cat {
const char *name;
const char *color;
} debug_cat[] = {
{ "sender", "\033[1;33m" },
{ "sound", "\033[0;35m" },
{ "fsk", "\033[0;31m" },
{ "audio", "\033[0;31m" },
{ "anetz", "\033[1;34m" },
{ "bnetz", "\033[1;34m" },
{ "call", "\033[1;37m" },
{ "mncc", "\033[1;32m" },
};
int debuglevel = DEBUG_INFO;
void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *fmt, ...)
{
char buffer[4096];
const char *p;
va_list args;
if (debuglevel > level)
return;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
buffer[sizeof(buffer) - 1] = '\0';
va_end(args);
while ((p = strchr(file, '/')))
file = p + 1;
// printf("%s%s:%d %s() %s: %s\033[0;39m", debug_cat[cat].color, file, line, function, debug_level[level], buffer);
printf("%s%s:%d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer);
fflush(stdout);
}

20
src/common/debug.h Normal file
View File

@ -0,0 +1,20 @@
#define DEBUG_DEBUG 0 /* debug info, not for normal use */
#define DEBUG_INFO 1 /* all info about process */
#define DEBUG_NOTICE 2 /* something unexpected happens */
#define DEBUG_ERROR 3 /* there is an error with this software */
#define DSENDER 0
#define DSOUND 1
#define DFSK 2
#define DAUDIO 3
#define DANETZ 4
#define DBNETZ 5
#define DCALL 6
#define DMNCC 7
#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, fmt, ## arg)
void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *fmt, ...);
extern int debuglevel;

81
src/common/filter.c Normal file
View File

@ -0,0 +1,81 @@
/* cut-off filter (biquad) based on Nigel Redmon (www.earlevel.com)
*
* (C) 2016 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 <string.h>
#include <stdlib.h>
#include <math.h>
#include "filter.h"
#define PI M_PI
//#define CASCADE
void biquad_init(biquad_low_pass_t *bq, double frequency, int samplerate)
{
double Fc, Q, K, norm;
memset(bq, 0, sizeof(*bq));
Q = sqrt(0.5); /* 0.7071... */
Fc = frequency / (double)samplerate;
K = tan(PI * Fc);
norm = 1 / (1 + K / Q + K * K);
bq->a0 = K * K * norm;
bq->a1 = 2 * bq->a0;
bq->a2 = bq->a0;
bq->b1 = 2 * (K * K - 1) * norm;
bq->b2 = (1 - K / Q + K * K) * norm;
}
void biquad_process(biquad_low_pass_t *bq, double *samples, int length, int iterations)
{
double a0, a1, a2, b1, b2;
double *z1, *z2;
double in, out;
int i, j;
if (iterations > 10) {
fprintf(stderr, "%s failed: too many iterations, please fix!\n", __func__);
abort();
}
/* get states */
a0 = bq->a0;
a1 = bq->a1;
a2 = bq->a2;
b1 = bq->b1;
b2 = bq->b2;
z1 = bq->z1;
z2 = bq->z2;
/* process filter */
for (i = 0; i < length; i++) {
in = *samples;
for (j = 0; j < iterations; j++) {
out = in * a0 + z1[j];
z1[j] = in * a1 + z2[j] - b1 * out;
z2[j] = in * a2 - b2 * out;
in = out;
}
*samples++ = in;
}
}

9
src/common/filter.h Normal file
View File

@ -0,0 +1,9 @@
typedef struct biquad_low_pass {
double a0, a1, a2, b1, b2;
double z1[10], z2[10];
} biquad_low_pass_t;
void biquad_init(biquad_low_pass_t *bq, double frequency, int samplerate);
void biquad_process(biquad_low_pass_t *bq, double *samples, int length, int iterations);

1073
src/common/freiton.c Normal file

File diff suppressed because it is too large Load Diff

3
src/common/freiton.h Normal file
View File

@ -0,0 +1,3 @@
void init_freiton(void);

100
src/common/goertzel.c Normal file
View File

@ -0,0 +1,100 @@
/* Goertzel functions
*
* (C) 2016 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../common/debug.h"
#include "goertzel.h"
/*
* audio level calculation
*/
/* return average value (rectified value), that can be 0..1 */
double audio_level(int16_t *samples, int length)
{
int bias;
double level;
int sk;
int n;
/* level calculation */
bias = 0;
for (n = 0; n < length; n++)
bias += samples[n];
bias = bias / length;
level = 0;
for (n = 0; n < length; n++) {
sk = samples[n] - bias;
if (sk < 0)
level -= (double)sk;
if (sk > 0)
level += (double)sk;
}
level = level / (double)length / 32767.0;
return level;
}
/*
* goertzel filter
*/
/* filter frequencies and return their levels
*
* samples: pointer to sample buffer
* length: length of buffer
* offset: for ring buffer, start here and wrap arround to 0 when length has been hit
* coeff: array of coefficients (coeff << 15)
* result: array of result levels (average value of the sine, that is 1 / (PI/2) of the sine's peak)
* k: number of frequencies to check
*/
void audio_goertzel(int16_t *samples, int length, int offset, int *coeff, double *result, int k)
{
int32_t sk, sk1, sk2;
int64_t cos2pik;
int i, n;
/* we do goertzel */
for (i = 0; i < k; i++) {
sk = 0;
sk1 = 0;
sk2 = 0;
cos2pik = coeff[i];
/* note: after 'length' cycles, offset is restored to its initial value */
for (n = 0; n < length; n++) {
sk = ((cos2pik * sk1) >> 15) - sk2 + samples[offset++];
sk2 = sk1;
sk1 = sk;
if (offset == length)
offset = 0;
}
/* compute level of signal */
result[i] = sqrt(
((double)sk * (double)sk) -
((double)((cos2pik * sk) >> 15) * (double)sk2) +
((double)sk2 * (double)sk2)
) / (double)length / 32767.0 * 2.0 * 0.63662; /* 1 / (PI/2) */
}
}

5
src/common/goertzel.h Normal file
View File

@ -0,0 +1,5 @@
double audio_level(int16_t *samples, int length);
void audio_goertzel(int16_t *samples, int length, int offset, int *coeff, double *result, int k);

114
src/common/jitter.c Normal file
View File

@ -0,0 +1,114 @@
/* Jitter buffering functions
*
* (C) 2016 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../common/debug.h"
#include "jitter.h"
/* create jitter buffer */
int jitter_create(jitter_t *jitter, int length)
{
memset(jitter, 0, sizeof(jitter));
jitter->spl = calloc(length * sizeof(int16_t), 1);
if (!jitter->spl) {
PDEBUG(DAUDIO, DEBUG_ERROR, "No memory for jitter buffer.\n");
return -ENOMEM;
}
jitter->len = length;
return 0;
}
void jitter_destroy(jitter_t *jitter)
{
if (jitter->spl) {
free(jitter->spl);
jitter->spl = NULL;
}
}
/* store audio in jitterbuffer
*
* stop if buffer is completely filled
*/
void jitter_save(jitter_t *jb, int16_t *samples, int length)
{
int16_t *spl;
int inptr, outptr, len, space;
int i;
spl = jb->spl;
inptr = jb->inptr;
outptr = jb->outptr;
len = jb->len;
space = (outptr - inptr + len - 1) % len;
if (space < length)
length = space;
for (i = 0; i < length; i++) {
spl[inptr++] = *samples++;
if (inptr == len)
inptr = 0;
}
jb->inptr = inptr;
}
/* get audio from jitterbuffer
*/
void jitter_load(jitter_t *jb, int16_t *samples, int length)
{
int16_t *spl;
int inptr, outptr, len, fill;
int i, ii;
spl = jb->spl;
inptr = jb->inptr;
outptr = jb->outptr;
len = jb->len;
fill = (inptr - outptr + len) % len;
if (fill < length)
ii = fill;
else
ii = length;
/* 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;
}
jb->outptr = outptr;
}
void jitter_clear(jitter_t *jb)
{
jb->inptr = jb->outptr = 0;
}

13
src/common/jitter.h Normal file
View File

@ -0,0 +1,13 @@
typedef struct jitter {
int16_t *spl; /* pointer to sample buffer */
int len; /* buffer size: number of samples */
int inptr, outptr; /* write pointer and read pointer */
} jitter_t;
int jitter_create(jitter_t *jitter, int length);
void jitter_destroy(jitter_t *jitter);
void jitter_save(jitter_t *jb, int16_t *samples, int length);
void jitter_load(jitter_t *jb, int16_t *samples, int length);
void jitter_clear(jitter_t *jb);

93
src/common/loss.c Normal file
View File

@ -0,0 +1,93 @@
/* Loss detection
*
* (C) 2016 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../common/debug.h"
#include "loss.h"
/* initialize detector
*
* interval: number of detector calls for one interval of one second
* threshold: intervals may differ by this factor, to be declared as similar
* 0 to disable, e.g. 1.3 for 30 percent change
*/
void audio_init_loss(loss_t *loss, int interval, double threshold, int seconds)
{
memset(loss, 0, sizeof(*loss));
loss->interval = interval;
loss->threshold = threshold;
loss->interval_num = seconds;
}
/* call this when tones/telegrams are detected */
void audio_reset_loss(loss_t *loss)
{
if (loss->interval_count > 0) {
PDEBUG(DAUDIO, DEBUG_DEBUG, "Signal is recovered (loss is gone).\n");
loss->interval_count = 0;
}
loss->level = 0;
loss->level_count = 0;
}
#define LOSS_MAX_DIFF 1.1 /* 10 % difference */
/* call this for every interval */
int audio_detect_loss(loss_t *loss, double level)
{
double diff;
/* disabled */
if (loss->threshold == 0.0)
return 0;
/* calculate a total level to detect loss */
loss->level += level;
if (++loss->level_count < loss->interval)
return 0;
/* normalize level */
loss->level = loss->level / loss->level_count;
PDEBUG(DAUDIO, DEBUG_DEBUG, "Noise level = %.0f%%\n", loss->level * 100);
diff = loss->level / loss->level_last;
if (diff < 1.0)
diff = 1.0 / diff;
loss->level_last = loss->level;
loss->level = 0;
loss->level_count = 0;
if (diff < LOSS_MAX_DIFF && loss->level_last > loss->threshold) {
loss->interval_count++;
PDEBUG(DAUDIO, DEBUG_DEBUG, "Detected signal loss %d for intervals level change %.0f%% (below %.0f%%).\n", loss->interval_count, diff * 100 - 100, LOSS_MAX_DIFF * 100 - 100);
} else if (loss->interval_count > 0) {
audio_reset_loss(loss);
}
if (loss->interval_count == loss->interval_num)
return 1;
return 0;
}

15
src/common/loss.h Normal file
View File

@ -0,0 +1,15 @@
typedef struct loss {
int interval; /* levels in one interval */
int interval_num; /* number of similar intervals until loss */
double threshold; /* how much volume change is accedped during loss */
double level_last; /* received level of last block */
double level; /* received level of current block */
int level_count; /* counter of levels inside interval */
int interval_count; /* counter of cosecutive intervals with loss */
} loss_t;
void audio_init_loss(loss_t *loss, int interval, double threshold, int seconds);
void audio_reset_loss(loss_t *loss);
int audio_detect_loss(loss_t *loss, double level);

22
src/common/main.h Normal file
View File

@ -0,0 +1,22 @@
extern int kanal;
extern const char *sounddev;
extern const char *call_sounddev;
extern int samplerate;
extern int latency;
extern int use_mncc_sock;
extern int send_patterns;
extern int loopback;
extern double lossdetect;
extern int rt_prio;
void print_help(const char *arg0);
void print_help_common(const char *arg0);
extern struct option *long_options;
extern char *optstring;
void set_options_common(const char *optstring_special, struct option *long_options_special);
void opt_switch_common(int c, char *arg0, int *skip_args);
extern int quit;
void sighandler(int sigset);

183
src/common/main_common.c Normal file
View File

@ -0,0 +1,183 @@
/* Common part for main.c of each base station type
*
* (C) 2016 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 <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "main.h"
#include "debug.h"
/* common settings */
int kanal = 0;
const char *sounddev = "hw:0,0";
const char *call_sounddev = "";
int samplerate = 48000;
int latency = 50;
int use_mncc_sock = 0;
int send_patterns = 1;
int loopback = 0;
double lossdetect = 0;
int rt_prio = 0;
void print_help_common(const char *arg0)
{
printf("Usage: %s -k kanal [options] [station-id]\n", arg0);
printf("\noptions:\n");
/* - - */
printf(" -h --help\n");
printf(" This help\n");
printf(" -D --debug <level>\n");
printf(" Debug level: 0 = debug | 1 = info | 2 = notice (default = '%d')\n", debuglevel);
printf(" -k --kanal <channel>\n");
printf(" Channel number of \"Sender\" (default = '%d')\n", kanal);
printf(" -d --device hw:<card>,<device>\n");
printf(" Sound card and device number (default = '%s')\n", sounddev);
printf(" -s --samplerate <rate>\n");
printf(" Sample rate of sound device (default = '%d')\n", samplerate);
printf(" -l --latency <delay>\n");
printf(" How many milliseconds processed in advance (default = '%d')\n", latency);
printf(" -0 --loss <volume>\n");
printf(" Detect loss of carrier by detecting steady noise above given volume in\n");
printf(" percent. (disabled by default)\n");
printf(" -m --mncc-sock\n");
printf(" Disable built-in call contol and offer socket (to LCR)\n");
printf(" -c --call-device hw:<card>,<device>\n");
printf(" Sound card and device number for headset (default = '%s')\n", call_sounddev);
printf(" -p --send-patterns 0 | 1\n");
printf(" Connect call on setup/release to provide classic tones (default = '%d')\n", send_patterns);
printf(" -L --loopback <type>\n");
printf(" Loopback test: 1 = internal | 2 = external | 3 = echo\n");
printf(" -r --realtime <prio>\n");
printf(" Set prio: 0 to diable, 99 for maximum (default = %d)\n", rt_prio);
}
static struct option long_options_common[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'D'},
{"kanal", 1, 0, 'k'},
{"device", 1, 0, 'd'},
{"call-device", 1, 0, 'c'},
{"samplerate", 1, 0, 's'},
{"latency", 1, 0, 'l'},
{"loss", 1, 0, '0'},
{"mncc-sock", 0, 0, 'm'},
{"send-patterns", 0, 0, 'p'},
{"loopback", 1, 0, 'L'},
{"realtime", 1, 0, 'r'},
{0, 0, 0, 0}
};
const char *optstring_common = "hD:k:d:s:c:l:0:mp:L:r:";
struct option *long_options;
char *optstring;
void set_options_common(const char *optstring_special, struct option *long_options_special)
{
int i;
long_options = calloc(sizeof(*long_options), 100);
for (i = 0; long_options_common[i].name; i++)
memcpy(&long_options[i], &long_options_common[i], sizeof(*long_options));
for (; long_options_special->name; i++)
memcpy(&long_options[i], long_options_special++, sizeof(*long_options));
optstring = calloc(strlen(optstring_common) + strlen(optstring_special) + 1, 1);
strcpy(optstring, optstring_common);
strcat(optstring, optstring_special);
}
void opt_switch_common(int c, char *arg0, int *skip_args)
{
switch (c) {
case 'h':
print_help(arg0);
exit(0);
case 'D':
debuglevel = atoi(optarg);
if (debuglevel > 2)
debuglevel = 2;
if (debuglevel < 0)
debuglevel = 0;
*skip_args += 2;
break;
case 'k':
kanal = atoi(optarg);
*skip_args += 2;
break;
case 'd':
sounddev = strdup(optarg);
*skip_args += 2;
break;
case 's':
samplerate = atoi(optarg);
*skip_args += 2;
break;
case 'c':
call_sounddev = strdup(optarg);
*skip_args += 2;
break;
case 'l':
latency = atoi(optarg);
*skip_args += 2;
break;
case '0':
lossdetect = atoi(optarg);
*skip_args += 2;
break;
case 'm':
use_mncc_sock = 1;
*skip_args += 1;
break;
case 'p':
send_patterns = atoi(optarg);
*skip_args += 2;
break;
case 'L':
loopback = atoi(optarg);
*skip_args += 2;
break;
case 'r':
rt_prio = atoi(optarg);
*skip_args += 2;
break;
default:
exit (0);
}
}
/* global variable to quit main loop */
int quit = 0;
void sighandler(int sigset)
{
if (sigset == SIGHUP)
return;
if (sigset == SIGPIPE)
return;
fprintf(stderr, "Signal received: %d\n", sigset);
quit = 1;
}

347
src/common/mncc.h Normal file
View File

@ -0,0 +1,347 @@
#define MNCC_SETUP_REQ 0x0101
#define MNCC_SETUP_IND 0x0102
#define MNCC_SETUP_RSP 0x0103
#define MNCC_SETUP_CNF 0x0104
#define MNCC_SETUP_COMPL_REQ 0x0105
#define MNCC_SETUP_COMPL_IND 0x0106
/* MNCC_REJ_* is perfomed via MNCC_REL_* */
#define MNCC_CALL_CONF_IND 0x0107
#define MNCC_CALL_PROC_REQ 0x0108
#define MNCC_PROGRESS_REQ 0x0109
#define MNCC_ALERT_REQ 0x010a
#define MNCC_ALERT_IND 0x010b
#define MNCC_NOTIFY_REQ 0x010c
#define MNCC_NOTIFY_IND 0x010d
#define MNCC_DISC_REQ 0x010e
#define MNCC_DISC_IND 0x010f
#define MNCC_REL_REQ 0x0110
#define MNCC_REL_IND 0x0111
#define MNCC_REL_CNF 0x0112
#define MNCC_FACILITY_REQ 0x0113
#define MNCC_FACILITY_IND 0x0114
#define MNCC_START_DTMF_IND 0x0115
#define MNCC_START_DTMF_RSP 0x0116
#define MNCC_START_DTMF_REJ 0x0117
#define MNCC_STOP_DTMF_IND 0x0118
#define MNCC_STOP_DTMF_RSP 0x0119
#define MNCC_MODIFY_REQ 0x011a
#define MNCC_MODIFY_IND 0x011b
#define MNCC_MODIFY_RSP 0x011c
#define MNCC_MODIFY_CNF 0x011d
#define MNCC_MODIFY_REJ 0x011e
#define MNCC_HOLD_IND 0x011f
#define MNCC_HOLD_CNF 0x0120
#define MNCC_HOLD_REJ 0x0121
#define MNCC_RETRIEVE_IND 0x0122
#define MNCC_RETRIEVE_CNF 0x0123
#define MNCC_RETRIEVE_REJ 0x0124
#define MNCC_USERINFO_REQ 0x0125
#define MNCC_USERINFO_IND 0x0126
#define MNCC_REJ_REQ 0x0127
#define MNCC_REJ_IND 0x0128
#define MNCC_PROGRESS_IND 0x0129
#define MNCC_CALL_PROC_IND 0x012a
#define MNCC_CALL_CONF_REQ 0x012b
#define MNCC_START_DTMF_REQ 0x012c
#define MNCC_STOP_DTMF_REQ 0x012d
#define MNCC_HOLD_REQ 0x012e
#define MNCC_RETRIEVE_REQ 0x012f
#define MNCC_BRIDGE 0x0200
#define MNCC_FRAME_RECV 0x0201
#define MNCC_FRAME_DROP 0x0202
#define MNCC_LCHAN_MODIFY 0x0203
#define MNCC_RTP_CREATE 0x0204
#define MNCC_RTP_CONNECT 0x0205
#define MNCC_RTP_FREE 0x0206
#define GSM_TCHF_FRAME 0x0300
#define GSM_TCHF_FRAME_EFR 0x0301
#define GSM_TCHH_FRAME 0x0302
#define GSM_TCH_FRAME_AMR 0x0303
#define ANALOG_8000HZ 0x0380
#define GSM_BAD_FRAME 0x03ff
#define MNCC_SOCKET_HELLO 0x0400
#define GSM_MAX_FACILITY 128
#define GSM_MAX_SSVERSION 128
#define GSM_MAX_USERUSER 128
#define MNCC_F_BEARER_CAP 0x0001
#define MNCC_F_CALLED 0x0002
#define MNCC_F_CALLING 0x0004
#define MNCC_F_REDIRECTING 0x0008
#define MNCC_F_CONNECTED 0x0010
#define MNCC_F_CAUSE 0x0020
#define MNCC_F_USERUSER 0x0040
#define MNCC_F_PROGRESS 0x0080
#define MNCC_F_EMERGENCY 0x0100
#define MNCC_F_FACILITY 0x0200
#define MNCC_F_SSVERSION 0x0400
#define MNCC_F_CCCAP 0x0800
#define MNCC_F_KEYPAD 0x1000
#define MNCC_F_SIGNAL 0x2000
#define GSM_MAX_FACILITY 128
#define GSM_MAX_SSVERSION 128
#define GSM_MAX_USERUSER 128
/* GSM 04.08 Bearer Capability: Information Transfer Capability */
enum gsm48_bcap_itcap {
GSM48_BCAP_ITCAP_SPEECH = 0,
GSM48_BCAP_ITCAP_UNR_DIG_INF = 1,
GSM48_BCAP_ITCAP_3k1_AUDIO = 2,
GSM48_BCAP_ITCAP_FAX_G3 = 3,
GSM48_BCAP_ITCAP_OTHER = 5,
GSM48_BCAP_ITCAP_RESERVED = 7,
};
/* GSM 04.08 Bearer Capability: Transfer Mode */
enum gsm48_bcap_tmod {
GSM48_BCAP_TMOD_CIRCUIT = 0,
GSM48_BCAP_TMOD_PACKET = 1,
};
/* GSM 04.08 Bearer Capability: Coding Standard */
enum gsm48_bcap_coding {
GSM48_BCAP_CODING_GSM_STD = 0,
};
/* GSM 04.08 Bearer Capability: Radio Channel Requirements */
enum gsm48_bcap_rrq {
GSM48_BCAP_RRQ_FR_ONLY = 1,
GSM48_BCAP_RRQ_DUAL_HR = 2,
GSM48_BCAP_RRQ_DUAL_FR = 3,
};
/* GSM 04.08 Bearer Capability: Rate Adaption */
enum gsm48_bcap_ra {
GSM48_BCAP_RA_NONE = 0,
GSM48_BCAP_RA_V110_X30 = 1,
GSM48_BCAP_RA_X31 = 2,
GSM48_BCAP_RA_OTHER = 3,
};
/* GSM 04.08 Bearer Capability: Signalling access protocol */
enum gsm48_bcap_sig_access {
GSM48_BCAP_SA_I440_I450 = 1,
GSM48_BCAP_SA_X21 = 2,
GSM48_BCAP_SA_X28_DP_IN = 3,
GSM48_BCAP_SA_X28_DP_UN = 4,
GSM48_BCAP_SA_X28_NDP = 5,
GSM48_BCAP_SA_X32 = 6,
};
/* GSM 04.08 Bearer Capability: User Rate */
enum gsm48_bcap_user_rate {
GSM48_BCAP_UR_300 = 1,
GSM48_BCAP_UR_1200 = 2,
GSM48_BCAP_UR_2400 = 3,
GSM48_BCAP_UR_4800 = 4,
GSM48_BCAP_UR_9600 = 5,
GSM48_BCAP_UR_12000 = 6,
GSM48_BCAP_UR_1200_75 = 7,
};
/* GSM 04.08 Bearer Capability: Parity */
enum gsm48_bcap_parity {
GSM48_BCAP_PAR_ODD = 0,
GSM48_BCAP_PAR_EVEN = 2,
GSM48_BCAP_PAR_NONE = 3,
GSM48_BCAP_PAR_ZERO = 4,
GSM48_BCAP_PAR_ONE = 5,
};
/* GSM 04.08 Bearer Capability: Intermediate Rate */
enum gsm48_bcap_interm_rate {
GSM48_BCAP_IR_8k = 2,
GSM48_BCAP_IR_16k = 3,
};
/* GSM 04.08 Bearer Capability: Transparency */
enum gsm48_bcap_transp {
GSM48_BCAP_TR_TRANSP = 0,
GSM48_BCAP_TR_RLP = 1,
GSM48_BCAP_TR_TR_PREF = 2,
GSM48_BCAP_TR_RLP_PREF = 3,
};
/* GSM 04.08 Bearer Capability: Modem Type */
enum gsm48_bcap_modem_type {
GSM48_BCAP_MT_NONE = 0,
GSM48_BCAP_MT_V21 = 1,
GSM48_BCAP_MT_V22 = 2,
GSM48_BCAP_MT_V22bis = 3,
GSM48_BCAP_MT_V23 = 4,
GSM48_BCAP_MT_V26ter = 5,
GSM48_BCAP_MT_V32 = 6,
GSM48_BCAP_MT_UNDEF = 7,
GSM48_BCAP_MT_AUTO_1 = 8,
};
/* GSM 04.08 Bearer Capability: Speech Version Indication */
enum gsm48_bcap_speech_ver {
GSM48_BCAP_SV_FR = 0,
GSM48_BCAP_SV_HR = 1,
GSM48_BCAP_SV_EFR = 2,
GSM48_BCAP_SV_AMR_F = 4,
GSM48_BCAP_SV_AMR_H = 5,
BCAP_ANALOG_8000HZ = 0x80,
};
/* Expanded fields from GSM TS 04.08, Table 10.5.102 */
struct gsm_mncc_bearer_cap {
int transfer; /* Information Transfer Capability */
int mode; /* Transfer Mode */
int coding; /* Coding Standard */
int radio; /* Radio Channel Requirement */
int speech_ctm; /* CTM text telephony indication */
int speech_ver[8]; /* Speech version indication */
struct {
enum gsm48_bcap_ra rate_adaption;
enum gsm48_bcap_sig_access sig_access;
int async;
int nr_stop_bits;
int nr_data_bits;
enum gsm48_bcap_user_rate user_rate;
enum gsm48_bcap_parity parity;
enum gsm48_bcap_interm_rate interm_rate;
enum gsm48_bcap_transp transp;
enum gsm48_bcap_modem_type modem_type;
} data;
};
struct gsm_mncc_number {
int type;
int plan;
int present;
int screen;
char number[33];
};
struct gsm_mncc_cause {
int location;
int coding;
int rec;
int rec_val;
int value;
int diag_len;
char diag[32];
};
struct gsm_mncc_useruser {
int proto;
char info[GSM_MAX_USERUSER + 1]; /* + termination char */
};
struct gsm_mncc_progress {
int coding;
int location;
int descr;
};
struct gsm_mncc_facility {
int len;
char info[GSM_MAX_FACILITY];
};
struct gsm_mncc_ssversion {
int len;
char info[GSM_MAX_SSVERSION];
};
struct gsm_mncc_cccap {
int dtmf;
int pcp;
};
enum {
GSM_MNCC_BCAP_SPEECH = 0,
GSM_MNCC_BCAP_UNR_DIG = 1,
GSM_MNCC_BCAP_AUDIO = 2,
GSM_MNCC_BCAP_FAX_G3 = 3,
GSM_MNCC_BCAP_OTHER_ITC = 5,
GSM_MNCC_BCAP_RESERVED = 7,
};
enum {
GSM_LCHAN_NONE,
GSM_LCHAN_SDCCH,
GSM_LCHAN_TCH_F,
GSM_LCHAN_TCH_H,
GSM_LCHAN_UNKNOWN,
GSM_LCHAN_CCCH,
GSM_LCHAN_PDTCH,
_GSM_LCHAN_MAX
};
struct gsm_mncc {
/* context based information */
uint32_t msg_type;
uint32_t callref;
/* which fields are present */
uint32_t fields;
/* data derived informations (MNCC_F_ based) */
struct gsm_mncc_bearer_cap bearer_cap;
struct gsm_mncc_number called;
struct gsm_mncc_number calling;
struct gsm_mncc_number redirecting;
struct gsm_mncc_number connected;
struct gsm_mncc_cause cause;
struct gsm_mncc_progress progress;
struct gsm_mncc_useruser useruser;
struct gsm_mncc_facility facility;
struct gsm_mncc_cccap cccap;
struct gsm_mncc_ssversion ssversion;
struct {
int sup;
int inv;
} clir;
int signal;
/* data derived information, not MNCC_F based */
int keypad;
int more;
int notify; /* 0..127 */
int emergency;
char imsi[16];
unsigned char lchan_type;
unsigned char lchan_mode;
};
struct gsm_data_frame {
uint32_t msg_type;
uint32_t callref;
unsigned char data[0];
};
struct gsm_mncc_rtp {
uint32_t msg_type;
uint32_t callref;
uint32_t ip;
uint16_t port;
uint32_t payload_type;
uint32_t payload_msg_type;
};
#define MNCC_SOCK_VERSION 5
struct gsm_mncc_hello {
uint32_t msg_type;
uint32_t version;
/* send the sizes of the structs */
uint32_t mncc_size;
uint32_t data_frame_size;
/* send some offsets */
uint32_t called_offset;
uint32_t signal_offset;
uint32_t emergency_offset;
uint32_t lchan_type_offset;
};

236
src/common/mncc_sock.c Normal file
View File

@ -0,0 +1,236 @@
/* Mobie Network Call Control (MNCC) socket handling
*
* (C) 2016 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 <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
#include <unistd.h>
#include "../common/debug.h"
#include "call.h"
#include "mncc_sock.h"
static int listen_sock = -1;
static int mncc_sock = -1;
/* write to mncc socket, return error or -EIO if no socket connection */
int mncc_write(uint8_t *buf, int length)
{
int rc;
if (mncc_sock <= 0) {
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC not connected.\n");
return -EIO;
}
rc = send(mncc_sock, buf, length, 0);
if (rc < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "MNCC connection failed (errno = %d).\n", errno);
mncc_sock_close();
return 0;
}
if (rc != length) {
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC write failed.\n");
mncc_sock_close();
return 0;
}
return rc;
}
/* read from mncc socket */
static int mncc_read(void)
{
uint8_t buf[sizeof(struct gsm_mncc)+1024];
int rc;
memset(buf, 0, sizeof(buf));
rc = recv(mncc_sock, buf, sizeof(buf), 0);
if (rc == 0) {
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC connection closed.\n");
mncc_sock_close();
return 0;
}
if (rc < 0) {
if (errno == EWOULDBLOCK)
return -errno;
PDEBUG(DMNCC, DEBUG_ERROR, "MNCC connection failed (errno = %d).\n", errno);
mncc_sock_close();
return -errno;
}
call_mncc_recv(buf, rc);
return rc;
}
static void mncc_hello(void)
{
struct gsm_mncc_hello hello;
memset(&hello, 0, sizeof(hello));
hello.msg_type = MNCC_SOCKET_HELLO;
hello.version = MNCC_SOCK_VERSION;
hello.mncc_size = sizeof(struct gsm_mncc);
hello.data_frame_size = sizeof(struct gsm_data_frame);
hello.called_offset = offsetof(struct gsm_mncc, called);
hello.signal_offset = offsetof(struct gsm_mncc, signal);
hello.emergency_offset = offsetof(struct gsm_mncc, emergency);
hello.lchan_type_offset = offsetof(struct gsm_mncc, lchan_type);
mncc_write((uint8_t *) &hello, sizeof(hello));
}
static int mncc_accept(void)
{
struct sockaddr_un __attribute__((__unused__)) un_addr;
socklen_t __attribute__((__unused__)) len;
int flags;
int rc;
len = sizeof(un_addr);
rc = accept(listen_sock, (struct sockaddr *) &un_addr, &len);
if (rc < 0) {
if (errno == EWOULDBLOCK)
return 0;
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to accept incomming connection (errno=%d).\n", errno);
return rc;
}
if (mncc_sock > 0) {
PDEBUG(DMNCC, DEBUG_NOTICE, "Rejecting multiple incomming connections.\n");
close(rc);
return -EIO;
}
mncc_sock = rc;
flags = fcntl(mncc_sock, F_GETFL, 0);
flags = 0;
rc = fcntl(mncc_sock, F_SETFL, flags | O_NONBLOCK);
if (rc < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to set socket into non-blocking IO mode.\n");
mncc_sock_close();
return rc;
}
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket connected.\n");
mncc_hello();
return 1;
}
void mncc_handle(void)
{
mncc_accept();
if (mncc_sock > 0) {
while ((mncc_read()) > 0)
;
}
}
void mncc_sock_close(void)
{
if (mncc_sock > 0) {
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket disconnected.\n");
close(mncc_sock);
mncc_sock = -1;
/* clear all call instances */
call_mncc_flush();
}
}
int mncc_init(const char *sock_name)
{
struct sockaddr_un local;
unsigned int namelen;
int flags;
int rc;
listen_sock = socket(PF_UNIX, SOCK_SEQPACKET, 0);
if (listen_sock < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to create socket.\n");
return listen_sock;
}
local.sun_family = AF_UNIX;
strncpy(local.sun_path, sock_name, sizeof(local.sun_path));
local.sun_path[sizeof(local.sun_path) - 1] = '\0';
unlink(local.sun_path);
/* we use the same magic that X11 uses in Xtranssock.c for
* calculating the proper length of the sockaddr */
#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
local.sun_len = strlen(local.sun_path);
#endif
#if defined(BSD44SOCKETS) || defined(SUN_LEN)
namelen = SUN_LEN(&local);
#else
namelen = strlen(local.sun_path) +
offsetof(struct sockaddr_un, sun_path);
#endif
rc = bind(listen_sock, (struct sockaddr *) &local, namelen);
if (rc < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to bind the unix domain "
"socket. '%s'\n", local.sun_path);
mncc_exit();
return rc;
}
rc = listen(listen_sock, 0);
if (rc < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to listen.\n");
mncc_exit();
return rc;
}
flags = fcntl(listen_sock, F_GETFL, 0);
flags = 0;
rc = fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK);
if (rc < 0) {
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to set socket into non-blocking IO mode.\n");
mncc_exit();
return rc;
}
PDEBUG(DMNCC, DEBUG_DEBUG, "MNCC socket at '%s' initialized, waiting for connection.\n", sock_name);
return 0;
}
void mncc_exit(void)
{
mncc_sock_close();
if (listen_sock > 0) {
close(listen_sock);
listen_sock = -1;
}
PDEBUG(DMNCC, DEBUG_DEBUG, "MNCC socket removed.\n");
}

8
src/common/mncc_sock.h Normal file
View File

@ -0,0 +1,8 @@
#include "mncc.h"
int mncc_write(uint8_t *buf, int length);
void mncc_handle(void);
void mncc_sock_close(void);
int mncc_init(const char *sock_name);
void mncc_exit(void);

159
src/common/samplerate.c Normal file
View File

@ -0,0 +1,159 @@
/* Sample rate conversion
*
* (C) 2016 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 <errno.h>
#include <string.h>
#include "samplerate.h"
/* generally use filter, but disable for test using quick and dirty replacement */
#define USE_FILTER
/* NOTE: This is quick and dirtry. */
int init_samplerate(samplerate_t *state, int samplerate)
{
if ((samplerate % 8000)) {
fprintf(stderr, "Sample rate must be a muliple of 8000 to support MNCC socket interface, aborting!\n");
return -EINVAL;
}
memset(state, 0, sizeof(*state));
state->factor = samplerate / 8000;
biquad_init(&state->up.bq, 4000.0, samplerate);
biquad_init(&state->down.bq, 4000.0, samplerate);
return 0;
}
/* convert input sample rate to 8000 Hz */
int samplerate_downsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output)
{
#ifdef USE_FILTER
int output_num, i, j;
int factor = state->factor;
double spl[input_num];
int32_t value;
/* convert samples to double */
for (i = 0; i < input_num; i++)
spl[i] = *input++ / 32768.0;
/* filter down */
biquad_process(&state->down.bq, spl, input_num, 1);
output_num = input_num / factor;
/* resample filtered result */
for (i = 0, j = 0; i < output_num; i++, j += factor) {
value = spl[j] * 32768.0;
if (value < -32768)
value = -32768;
else if (value > 32767)
value = 32767;
*output++ = value;
}
return output_num;
#else
int output_num = 0, i;
double sum;
int factor, sum_count;
//memcpy(output, input, input_num*2);
//return input_num;
sum = state->down.sum;
sum_count = state->down.sum_count;
factor = state->factor;
for (i = 0; i < input_num; i++) {
sum += *input++;
sum_count++;
if (sum_count == factor) {
*output++ = sum / (double)sum_count;
output_num++;
sum = 0;
sum_count = 0;
}
}
state->down.sum = sum;
state->down.sum_count = sum_count;
return output_num;
#endif
}
/* convert 8000 Hz sample rate to output sample rate */
int samplerate_upsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output)
{
#ifdef USE_FILTER
int output_num, i;
int factor = state->factor;
double spl[input_num * factor];
int32_t value;
output_num = input_num * factor;
/* resample input */
for (i = 0; i < output_num; i++)
spl[i] = input[i / factor] / 32768.0;
/* filter up */
biquad_process(&state->up.bq, spl, output_num, 1);
/* convert double to samples */
for (i = 0; i < output_num; i++) {
value = spl[i] * 32768.0;
if (value < -32768)
value = -32768;
else if (value > 32767)
value = 32767;
*output++ = value;
}
return output_num;
#else
int output_num = 0, i, j;
double last_sample, sample, slope;
int factor;
last_sample = state->up.last_sample;
factor = state->factor;
for (i = 0; i < input_num; i++) {
sample = *input++;
slope = (double)(sample - last_sample) / (double)factor;
//int jolly = (int)last_sample;
for (j = 0; j < factor; j++) {
// if (last_sample > 32767 || last_sample < -32767)
// printf("%.5f sample=%.0f, last_sample=%d, slope=%.5f\n", last_sample, sample, jolly, slope);
*output++ = last_sample;
output_num++;
last_sample += slope;
}
last_sample = sample;
}
state->up.last_sample = last_sample;
return output_num;
#endif
}

18
src/common/samplerate.h Normal file
View File

@ -0,0 +1,18 @@
#include "filter.h"
typedef struct samplerate {
int factor;
struct {
double sum;
int sum_count;
biquad_low_pass_t bq;
} down;
struct {
double last_sample;
biquad_low_pass_t bq;
} up;
} samplerate_t;
int init_samplerate(samplerate_t *state, int samplerate);
int samplerate_downsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output);
int samplerate_upsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output);

228
src/common/sender.c Normal file
View File

@ -0,0 +1,228 @@
/* Common transceiver functions
*
* (C) 2016 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 <errno.h>
#include <string.h>
#include "debug.h"
#include "sender.h"
#include "timer.h"
#include "call.h"
sender_t *sender_head = NULL;
static sender_t **sender_tailp = &sender_head;
/* Init transceiver instance and link to list of transceivers. */
int sender_create(sender_t *sender, const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume, int use_pilot_signal)
{
int rc = 0;
PDEBUG(DSENDER, DEBUG_DEBUG, "Creating 'Sender' instance\n");
sender->samplerate = samplerate;
sender->kanal = kanal;
sender->loopback = loopback;
sender->loss_volume = loss_volume;
sender->use_pilot_signal = use_pilot_signal;
sender->pilotton_phaseshift = 1.0 / ((double)samplerate / 1000.0);
sender->sound = sound_open(sounddev, samplerate);
if (!sender->sound) {
PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n");
rc = -EIO;
goto error;
}
rc = init_samplerate(&sender->srstate, samplerate);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
goto error;
}
rc = jitter_create(&sender->audio, samplerate / 5);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
goto error;
}
*sender_tailp = sender;
sender_tailp = &sender->next;
return 0;
error:
sender_destroy(sender);
return rc;
}
/* Destroy transceiver instance and unlink from list. */
void sender_destroy(sender_t *sender)
{
PDEBUG(DSENDER, DEBUG_DEBUG, "Destroying 'Sender' instance\n");
sender_tailp = &sender_head;
while (*sender_tailp) {
if (sender == *sender_tailp)
*sender_tailp = sender->next;
sender_tailp = &sender->next;
}
if (sender->sound)
sound_close(sender->sound);
jitter_destroy(&sender->audio);
}
static void gen_pilotton(sender_t *sender, int16_t *samples, int length)
{
double phaseshift, phase;
int i;
phaseshift = sender->pilotton_phaseshift;
phase = sender->pilotton_phase;
for (i = 0; i < length; i++) {
if (phase < 0.5)
*samples++ = 30000;
else
*samples++ = -30000;
phase += phaseshift;
if (phase >= 1.0)
phase -= 1.0;
}
sender->pilotton_phase = phase;
}
/* Handle audio streaming of one transceiver. */
void process_sender(sender_t *sender, int latspl)
{
int16_t samples[latspl], pilot[latspl];
int rc, count;
count = sound_get_inbuffer(sender->sound);
if (count < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count);
if (count == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
return;
}
if (count < latspl) {
count = latspl - count;
if (sender->loopback == 3)
jitter_load(&sender->audio, samples, count);
else
sender_send(sender, samples, count);
switch (sender->use_pilot_signal) {
case 2:
/* tone if pilot signal is on */
if (sender->pilot_on)
gen_pilotton(sender, pilot, count);
else
memset(pilot, 0, count << 1);
rc = sound_write(sender->sound, samples, pilot, count);
break;
case 1:
/* positive signal if pilot signal is on */
if (sender->pilot_on)
memset(pilot, 127, count << 1);
else
memset(pilot, 128, count << 1);
rc = sound_write(sender->sound, samples, pilot, count);
break;
case 0:
/* negative signal if pilot signal is on */
if (sender->pilot_on)
memset(pilot, 128, count << 1);
else
memset(pilot, 127, count << 1);
rc = sound_write(sender->sound, samples, pilot, count);
break;
default:
rc = sound_write(sender->sound, samples, samples, count);
}
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc);
if (rc == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
return;
}
if (sender->loopback == 1)
sender_receive(sender, samples, count);
}
count = sound_read(sender->sound, samples, latspl);
//printf("count=%d time= %.4f\n", count, (double)count * 1000 / sender->samplerate);
if (count < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count);
if (count == -EPIPE)
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
return;
}
if (count) {
if (sender->loopback != 1)
sender_receive(sender, samples, count);
if (sender->loopback == 3) {
jitter_save(&sender->audio, samples, count);
}
}
}
/* Loop through all transceiver instances of one network. */
void main_loop(int *quit, int latency)
{
int latspl;
sender_t *sender;
double last_time = 0, now;
while(!(*quit)) {
/* process sound of all transceivers */
sender = sender_head;
while (sender) {
latspl = sender->samplerate * latency / 1000;
process_sender(sender, latspl);
sender = sender->next;
}
/* process timers */
process_timer();
/* process audio for mncc call instances */
now = get_time();
if (now - last_time >= 0.1)
last_time = now;
if (now - last_time >= 0.020) {
last_time += 0.020;
/* call clock every 20ms */
call_mncc_clock();
}
/* process audio of built-in call control */
if (process_call())
break;
/* sleep a while */
usleep(1000);
}
}

50
src/common/sender.h Normal file
View File

@ -0,0 +1,50 @@
#include "sound.h"
#include "samplerate.h"
#include "jitter.h"
#include "loss.h"
/* common structure of each transmitter */
typedef struct sender {
struct sender *next;
/* call reference */
int callref;
/* system info */
int kanal; /* channel number */
/* sound */
void *sound;
int samplerate;
samplerate_t srstate; /* sample rate conversion state */
/* loopback test */
int loopback; /* 0 = off, 1 = internal, 2 = external */
/* audio buffer for audio to send to transmitter (also used as loopback buffer) */
jitter_t audio;
/* audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz) */
int16_t rxbuf[160];
int rxbuf_pos; /* current fill of buffer */
/* loss of carrier detection */
double loss_volume;
loss_t loss;
/* pilot tone */
int use_pilot_signal; /* -1 if not used, 1 for positive, 0 for negative, 2 for tone */
int pilot_on; /* 1 or 0 for on or off */
double pilotton_phaseshift; /* phase to shift every sample */
double pilotton_phase; /* current phase */
} sender_t;
/* list of all senders */
extern sender_t *sender_head;
int sender_create(sender_t *sender, const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume, int use_pilot_signal);
void sender_destroy(sender_t *sender);
void sender_send(sender_t *sender, int16_t *samples, int count);
void sender_receive(sender_t *sender, int16_t *samples, int count);
void main_loop(int *quit, int latency);

6
src/common/sound.h Normal file
View File

@ -0,0 +1,6 @@
void *sound_open(const char *device, int samplerate);
void sound_close(void *inst);
int sound_write(void *inst, int16_t *samples_left, int16_t *samples_right, int num);
int sound_read(void *inst, int16_t *samples, int num);
int sound_get_inbuffer(void *inst);

260
src/common/sound_alsa.c Normal file
View File

@ -0,0 +1,260 @@
/* Sound device access
*
* (C) 2016 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 <stdlib.h>
#include <stdint.h>
#include <alsa/asoundlib.h>
#include "debug.h"
#include "sound.h"
typedef struct sound {
snd_pcm_t *phandle, *chandle;
int pchannels, cchannels;
} sound_t;
static int set_hw_params(snd_pcm_t *handle, int samplerate, int *channels)
{
snd_pcm_hw_params_t *hw_params = NULL;
int rc;
unsigned int rrate;
rc = snd_pcm_hw_params_malloc(&hw_params);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to allocate hw_params! (%s)\n", snd_strerror(rc));
goto error;
}
rc = snd_pcm_hw_params_any(handle, hw_params);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(rc));
goto error;
}
rc = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set real hardware rate (%s)\n", snd_strerror(rc));
goto error;
}
rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set access to interleaved (%s)\n", snd_strerror(rc));
goto error;
}
rc = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample format (%s)\n", snd_strerror(rc));
goto error;
}
rrate = samplerate;
rc = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample rate (%s)\n", snd_strerror(rc));
goto error;
}
if (rrate != samplerate) {
PDEBUG(DSOUND, DEBUG_ERROR, "Rate doesn't match (requested %dHz, get %dHz)\n", samplerate, rrate);
rc = -EIO;
goto error;
}
*channels = 1;
rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels);
if (rc < 0) {
*channels = 2;
rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set channel count to 1 nor 2 (%s)\n", snd_strerror(rc));
goto error;
}
}
rc = snd_pcm_hw_params(handle, hw_params);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set parameters (%s)\n", snd_strerror(rc));
goto error;
}
snd_pcm_hw_params_free(hw_params);
return 0;
error:
if (hw_params) {
snd_pcm_hw_params_free(hw_params);
}
return rc;
}
void *sound_open(const char *device, int samplerate)
{
sound_t *sound;
int rc;
sound = calloc(1, sizeof(sound_t));
if (!sound) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to alloc memory!\n");
return NULL;
}
rc = snd_pcm_open(&sound->phandle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s)\n", device, snd_strerror(rc));
goto error;
}
rc = snd_pcm_open(&sound->chandle, device, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s)\n", device, snd_strerror(rc));
goto error;
}
rc = set_hw_params(sound->phandle, samplerate, &sound->pchannels);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n");
goto error;
}
PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels);
rc = set_hw_params(sound->chandle, samplerate, &sound->cchannels);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n");
goto error;
}
PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels);
rc = snd_pcm_prepare(sound->phandle);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc));
goto error;
}
rc = snd_pcm_prepare(sound->chandle);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc));
goto error;
}
return sound;
error:
sound_close(sound);
return NULL;
}
void sound_close(void *inst)
{
sound_t *sound = (sound_t *)inst;
if (sound->phandle > 0)
snd_pcm_close(sound->phandle);
if (sound->chandle > 0)
snd_pcm_close(sound->chandle);
free(sound);
}
int sound_write(void *inst, int16_t *samples_left, int16_t *samples_right, int num)
{
sound_t *sound = (sound_t *)inst;
int16_t buff[num << 1], *samples;
int rc;
int i, ii;
if (sound->pchannels == 2) {
for (i = 0, ii = 0; i < num; i++) {
buff[ii++] = *samples_right++;
buff[ii++] = *samples_left++;
}
samples = buff;
} else
samples = samples_left;
rc = snd_pcm_writei(sound->phandle, samples, num);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "failed to write audio to interface (%s)\n", snd_strerror(rc));
if (rc == -EPIPE)
snd_pcm_prepare(sound->phandle);
return rc;
}
if (rc != num)
PDEBUG(DSOUND, DEBUG_ERROR, "short write to audio interface, written %d bytes, got %d bytes\n", num, rc);
return rc;
}
int sound_read(void *inst, int16_t *samples, int num)
{
sound_t *sound = (sound_t *)inst;
int16_t buff[num << 1];
int32_t s32;
int rc;
int i, ii;
if (sound->cchannels == 2)
rc = snd_pcm_readi(sound->chandle, buff, num);
else
rc = snd_pcm_readi(sound->chandle, samples, num);
if (rc < 0) {
if (errno == EAGAIN)
return 0;
PDEBUG(DSOUND, DEBUG_ERROR, "failed to read audio from interface (%s)\n", snd_strerror(rc));
/* recover read */
if (rc == -EPIPE)
snd_pcm_prepare(sound->chandle);
return rc;
}
if (sound->cchannels == 2) {
for (i = 0, ii = 0; i < rc; i++) {
s32 = buff[ii++];
s32 += buff[ii++];
*samples++ = s32 >> 1;
}
}
return rc;
}
/*
* get playback buffer fill
*
* return number of frames */
int sound_get_inbuffer(void *inst)
{
sound_t *sound = (sound_t *)inst;
int rc;
snd_pcm_sframes_t delay;
rc = snd_pcm_delay(sound->phandle, &delay);
if (rc < 0) {
PDEBUG(DSOUND, DEBUG_ERROR, "failed to get delay from interface (%s)\n", snd_strerror(rc));
if (rc == -EPIPE)
snd_pcm_prepare(sound->phandle);
return rc;
}
return delay;
}

107
src/common/timer.c Normal file
View File

@ -0,0 +1,107 @@
/* Timer handling
*
* (C) 2016 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 <string.h>
#include <sys/time.h>
#include "timer.h"
static struct timer *timer_head = NULL;
static struct timer **timer_tail_p = &timer_head;
double get_time(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
}
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv)
{
if (timer->linked) {
fprintf(stderr, "Timer is already initialized, aborting!\n");
abort();
}
timer->timeout = 0;
timer->fn = fn;
timer->priv = priv;
timer->next = NULL;
*timer_tail_p = timer;
timer_tail_p = &timer->next;
timer->linked = 1;
}
void timer_exit(struct timer *timer)
{
timer_tail_p = &timer_head;
while (*timer_tail_p) {
if (timer == *timer_tail_p)
*timer_tail_p = timer->next;
timer_tail_p = &timer->next;
}
timer->linked = 0;
}
void timer_start(struct timer *timer, double duration)
{
struct timeval tv;
if (!timer->linked) {
fprintf(stderr, "Timer is not initialized, aborting!\n");
abort();
}
gettimeofday(&tv, NULL);
timer->timeout = get_time() + duration;
}
void timer_stop(struct timer *timer)
{
if (!timer->linked) {
fprintf(stderr, "Timer is not initialized, aborting!\n");
abort();
}
timer->timeout = 0;
}
void process_timer(void)
{
struct timer *timer = timer_head;
double now;
now = get_time();
again:
while (timer) {
if (timer->linked && timer->timeout > 0 && now >= timer->timeout) {
timer->timeout = 0;
timer->fn(timer);
goto again;
}
timer = timer->next;
}
}

16
src/common/timer.h Normal file
View File

@ -0,0 +1,16 @@
struct timer {
struct timer *next;
int linked; /* set is timer is initialized and linked */
double timeout;
void (*fn)(struct timer *timer);
void *priv;
};
double get_time(void);
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv);
void timer_exit(struct timer *timer);
void timer_start(struct timer *timer, double duration);
void timer_stop(struct timer *timer);
void process_timer(void);