Initial import of linmodem-0.2.5.tgz

This commit is contained in:
Geoffrey Thomas 2014-07-20 18:28:02 -07:00
commit 0457e3a587
40 changed files with 9660 additions and 0 deletions

33
CHANGES Normal file
View File

@ -0,0 +1,33 @@
* from version 0.2.4 to 0.2.5
=============================
- major rewrite, the API becomes stable.
- V8/V21/V23 should be usable.
- added X11 interface for the simulator.
- better line model for the simulator: generic FIR filter.
Programmable echo generator (both neat & far end).
- V22 tx code (needed for V34 phase 2).
- V34: added adaptive equalizer, symbol timing recovery, phase
recovery. Tests for fast training on the PP sequence (but not satisfactory).
- added AT parser.
- added serial protocol.
- added working soundcard hw support.
* from version 0.2.3 to 0.2.4
=============================
- major V90 update, the algebraic part is finished, and the CP packet
parsing is done.
- added patches from Pavel (serial.c, lmreal.c)
* from version 0.2.2 to 0.2.3
=============================
- added V90 core, but no usable code yet.
- more patches from pavel for ltmodem.
* from version 0.2.1 to 0.2.2
=============================
- added extended V34 modulation for 31200 & 33600 speeds.
- added V34 negociation (not finished)
- added Pavel patches for ltmodem

340
COPYING Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

47
Makefile Normal file
View File

@ -0,0 +1,47 @@
# uncomment to use X11 debug interface
USE_X11=y
CFLAGS= -O2 -Wall -g
LDFLAGS= -g
OBJS= lm.o lmsim.o lmreal.o lmsoundcard.o serial.o atparser.o \
dsp.o fsk.o v8.o v21.o v23.o dtmf.o \
v34.o v34table.o v22.o v34eq.o \
v90.o v90table.o
INCLUDES= display.h fsk.h v21.h v34priv.h v90priv.h \
dsp.h lm.h v23.h v8.h \
dtmf.h lmstates.h v34.h v90.h
PROG= lm
ifdef USE_X11
OBJS += display.o
LDFLAGS += -L/usr/X11R6/lib -lX11
endif
all: $(PROG)
$(PROG): $(OBJS)
gcc $(LDFLAGS) -o $(PROG) $(OBJS) -lm
v34gen: v34gen.o dsp.o
gcc $(LDFLAGS) -o $@ v34gen.o dsp.o -lm
v34table.c: v34gen
./v34gen > $@
v90gen: v90gen.o
gcc $(LDFLAGS) -o $@ $< -lm
v90table.c: v90gen
./v90gen > $@
linmodem.dvi: linmodem.tex
latex2e linmodem.tex
clean:
rm -f *.o *~ *.dat core gmon.out *.sw v34table.c v90table.c $(PROG) v34gen v90gen *.aux *.dvi *.log
tar:
( cd .. ; tar zcvf linmodem.tgz linmodem --exclude CVS )
%.o: %.c $(INCLUDES)
gcc $(CFLAGS) -c $*.c

99
README Normal file
View File

@ -0,0 +1,99 @@
Here is the generic Linux Modem. This modem is totally software,
it means that all the DSP stuff is done by the main CPU, as in some so
called "winmodems". See the main project page at
http://www.enst.fr/~bellard/linmodem.html.
Linmodem is also a research project. It contains all the necessary
stuff to test new digital communication algorithms. The line simulator
and the X11 interface can be used to simulate a complete communication
chain.
Linmodem is the first modem to integrate a graphical user interface
which show the data transmitted graphically (QAM constellation, real
time FFT of the received signal, etc...) and which will allow you to
monitor the line quality.
What's done:
-----------
- V34 modulator (sampling rate of 8000 Hz)
- V34 demodulator (sampling rate of 8000 Hz), but no echo cancellor.
- Algebraic part of V90.
- DTMF dialing/receive.
- V8 protocol.
- V21 modulation & demodulation
- V23 modulation & demodulation
- sample code to test the protocol.
- sample code to test V21, V22, V23, V34 and V90 independently from the modem.
- a basic phone line simulator (with echos & typical line
amplitude/phase distortion).
- an X11 interface (see README.x11)
- soundcard interface.
- AT command parser & sample tty simulator.
- asynchronous protocol.
See the homepage of the project at
http://www.enst.fr/~bellard/linmodem.html to know what are the tasks
you could do.
Read the file README.arch to know the details of the
implementation. Next versions will contain the first draft on the
algorithms which are implemented.
Testing:
-------
Yes, you can already hear the modem !
compile everything, then type:
lm -sv
You will see a lot of debug stuff. Then you can press Control C to
stop the call. If you play the files 'ans.sw' and 'cal.sw' on your
soundcard (16 bit, signed 8kHz), you will hear the DTMF pulses, the V8
negociation, and a sample V21 connection. The X11 interface allows you
to see the signals exchanged.
Data pump testing:
-----------------
With the option '-m modulation_name', you can test the V21, V22, V23,
V34 and V90 data pumps. You can understand what's going on in this
test only if you have a basic knowledge of the modulations.
The X11 interface can be used to monitor all the main data pump
parameters, except for V90 which is not yet completely integrated in
the tests (see README.x11).
Real modems:
-----------
Linmodem won't contain any
hardware modem to support modems directly, but it will use kernel
drivers which give a unified API to every driver (see README.arch to
have an idea of the API)
Some test code is included in lmreal.c to work with the LTModem stuff
available at http://www.close.u-net.com/ltmodem.html. However, it was
not tested so don't expect it to work in this version.
Soundcard testing:
-----------------
If you have two PCs connected by soundcards (connect line in -> line
out), you may try the soundcard support of linmodem (not tested now,
but should work):
'lm -tv' launches linmodem on your soundcard (device '/dev/dsp'). Then
you can type 'ATDTxxx' to compose a number and launch a connection. On
the other PC, type 'ATA' to receive the connection. It should work in
V23 (or V21 if you change the defaults modulations in lm.c).
With 'lm -t', linmodem simulates a serial line on '/dev/ttyz0'. You
can use minicom or any other terminal emulator to send AT commands.
Enjoy :-)
Fabrice Bellard - bellard@email.enst.fr - http://www.enst.fr/~bellard

112
README.arch Normal file
View File

@ -0,0 +1,112 @@
Linmodem architecture
Copyright (c) 2000 Fabrice Bellard
1) Overview:
-----------
The hardware dependent layer (for example lmreal.c, lmsim.c,
lmsoundcard.c) must call the function
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples);
at least every 10 ms. It must supply 'nb_samples' 16 bit input samples
at 8000 Hz. The function sm_process returns the 'output' samples it
has computed. These samples can them be sent to the phone line.
'sm_process' handles the dialing, ring detection and Vxx protocol
initization. Then, each protocol can supply a similar function. For
example, 'v8.c' supplies the function:
int V8_process(V8State *sm, s16 *output, s16 *input, int nb_samples);
It returns the modulation to which the modem must go after the V8
phase.
Note that no threads are used. It implies that each 'xx_process'
function must use an internal state machine. The first parameter
('V8State' here) is usually the structure in which the state is
stored.
2) Timing:
---------
The protocols must use only 'nb_samples' as a timing, not the real
time of the system. It allows to simulate the whole modem without
changing anything.
3) Reentrancy:
-------------
Each protocol must be reentrant, so that multiple modems can be
instanciated at the same time. It is needed for example to do
simulations.
4) Data handling:
----------------
When a protocol gets a bit from the input samples, it should can a function
int get_bit(void *opaque);
provided at its initialization with the opaque parameter 'opaque'. The
same should be done for output with :
void put_bit(void *opaque, int bit);
'sm_process' instanciate each protocol by giving it one of its
standard bit I/O functions. These functions are responsible from
handling the higher level protocol (asynchronous, LAPM, V42bis).
See the file 'serial.c' to see how the data can be handled. V42/V42bis
should be added the same way.
The decoded bytes are put in the FIFO sm->rx_fifo. This fifo is then
return to the modem tty. The inverse is done with sm->tx_fifo.
5) Modem configuration:
----------------------
The configuration parameters are defined in 'lm.c'. See 'struct
LinModemConfig default_lm_config'.
6) Linmodem harware interface:
-----------------------------
See the file lmsoundcard.c which gives an example to interface to a
sound card with the Open Sound System API.
7) Linmodem kernel interface:
----------------------------
The hardware interface to an actual modem is not completely defined
yet. However, it will be very close to the Open Sound System API:
- the write() & read() syscalls write & read 16 bit samples from the
modem.
- An ioctl can select the sampling rate (at least 8000 Hz must be
available).
- an ioctl set/reset the offhook relay.
- an ioctl set/reset the pulse relay.
- an ioctl gives the state of the ring detection. Each time the ring
rings, the event POLLPRI (urgent data) should be set so that the
poll() syscall can handle it.
- an ioctl changes the input & output gains, as the volume for a sound
card.
8) AT command parser:
--------------------
See the file atparse.c: The parser must remain sample. We do not want
to support all the AT commands, but only the minimal subset so that
linux programs can work : 'chat' for PPP, 'mgetty/vgetty' for
voice/data/fax calls, 'sendfax' to send faxes.

32
README.x11 Normal file
View File

@ -0,0 +1,32 @@
Linmodem contains an X11 interface which can be used to monitor the
modem parameters. It is used for example in v34 testing (lm -m v34). The
X11 simulation works only in 16 bit mode. If you are interested by
other screen depths, please implement them :-)
The function keys are used to change the parameters which are dumped
on the screen:
* for V34:
F1 : dump the brut samples which enter into the modem (at 8000Hz). A
hamming windowed FFT is done to output the spectral power.
F2 : dump the echo cancellor parameters (not implemented yet).
F3 : dump the samples after frequency normalization & symbol timing
recovery. You have 3 samples per baud for V34. A hamming windowed FFT
is done to output the spectral power.
F4 : dump the equalizer filter in both time & frequency domains.
F5 : dump the QAM points just after the equalizer and phase
recovery. This graphic can be used to monitor the overall modem
quality.
* for V21/V23 tests
F1: same as V34
F3: decision samples

176
atparser.c Normal file
View File

@ -0,0 +1,176 @@
/*
* Simple AT command parser
*
* Copyright (c) 2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include "lm.h"
#define DEBUG
static void parse_at_line(struct lm_at_state *s, const char *line);
static void at_putc(struct lm_at_state *s, int c);
static void at_printf(struct lm_at_state *s, char *fmt, ...);
/*
* This AT command parser is not intended to be complete nor accurate
* but to provide the minimum functionality so that the programs can
* make a data/voice/fax connection.
*/
void lm_at_parser_init(struct lm_at_state *s, struct sm_state *sm)
{
s->sm = sm;
s->at_line_ptr = 0;
s->at_state = AT_MODE_COMMAND;
}
void lm_at_parser(struct lm_at_state *s)
{
int state, c;
switch(s->at_state) {
case AT_MODE_COMMAND:
for(;;) {
/* handle incoming character */
c = sm_get_bit(&s->sm->tx_fifo);
if (c == -1)
break;
if (c == '\n' || c == '\r') {
/* line validation */
s->at_line[s->at_line_ptr] = '\0';
if (s->at_line_ptr != 0) {
at_putc(s, '\n');
parse_at_line(s, s->at_line);
}
s->at_line_ptr = 0;
} else if (c == '\b') {
/* backspace */
if (s->at_line_ptr > 0) {
s->at_line_ptr--;
at_printf(s, "\b \b");
}
} else {
/* add new char */
if (s->at_line_ptr < (sizeof(s->at_line) - 1)) {
s->at_line[s->at_line_ptr++] = c;
at_putc(s, c);
}
}
}
break;
case AT_MODE_DIALING:
state = lm_get_state(s->sm);
if (state == LM_STATE_IDLE) {
at_printf(s, "ERROR\n");
s->at_state = AT_MODE_COMMAND;
} else if (state == LM_STATE_CONNECTED) {
at_printf(s, "CONNECT\n");
s->at_state = AT_MODE_CONNECTED;
}
break;
case AT_MODE_CONNECTED:
if (lm_get_state(s->sm) == LM_STATE_IDLE) {
at_printf(s, "OK\n");
s->at_state = AT_MODE_COMMAND;
}
break;
}
}
/* return TRUE if val is a prefix of str. If it returns TRUE, ptr is
set to the next character in 'str' after the prefix */
#if 0
static int strstart(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
p = str;
q = val;
while (*q != '\0') {
if (*p != *q)
return 0;
p++;
q++;
}
*ptr = p;
return 1;
}
#endif
static int strcasestart(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
p = str;
q = val;
while (*q != '\0') {
if (toupper(*p) != toupper(*q))
return 0;
p++;
q++;
}
*ptr = p;
return 1;
}
static void at_putc(struct lm_at_state *s, int c)
{
if (c == '\n')
sm_put_bit(&s->sm->rx_fifo, '\r');
sm_put_bit(&s->sm->rx_fifo, c);
}
static void at_printf(struct lm_at_state *s, char *fmt, ...)
{
char buf[256], *p;
int c;
va_list ap;
va_start(ap, fmt);
snprintf(buf, sizeof(buf), fmt, ap);
p = buf;
while (*p) {
c = *p++;
at_putc(s, c);
}
va_end(ap);
}
static void parse_at_line(struct lm_at_state *s, const char *line)
{
const char *p;
struct sm_state *sm = s->sm;
if (strcasestart(line, "ATD", &p)) {
int pulse;
pulse = sm->lm_config->pulse_dial;
if (strcasestart(p, "P", &p))
pulse = 1;
if (strcasestart(p, "T", &p))
pulse = 0;
lm_start_dial(sm, pulse, p);
s->at_state = AT_MODE_DIALING;
} else if (strcasestart(line, "ATI", &p)) {
at_printf(s, "Linmodem " LM_VERSION "\n");
} else if (strcasestart(line, "ATZ", &p)) {
at_printf(s, "OK\n");
} else if (strcasestart(line, "ATA", &p)) {
lm_start_receive(sm);
s->at_state = AT_MODE_DIALING;
} else if (strcasestart(line, "ATH", &p)) {
lm_hangup(sm);
}
}

557
display.c Normal file
View File

@ -0,0 +1,557 @@
/*
* X11 interface for linmodem
*
* Copyright (c) 2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/keysym.h>
#include "lm.h"
#define NB_MODES 5
enum {
DISP_MODE_SAMPLE, /* display the brut samples */
DISP_MODE_ECHOCANCEL, /* display the echo cancellor info */
DISP_MODE_SAMPLESYNC, /* display the samples after freq normalize
& symbol recovery */
DISP_MODE_EQUALIZER, /* display the equalizer filter */
DISP_MODE_QAM, /* display the qam */
} disp_state;
char *mode_str[NB_MODES] = {
"Sample",
"Echo Cancel",
"Sample Sync",
"Equalizer",
"QAM",
};
#define QAM_SIZE 460
static void set_state(int state);
/**************************/
#define RGB(r, g, b) ((((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3))
Display *display;
Window window;
GC gc;
XFontStruct *xfont;
unsigned int fg, bg;
int minx, miny;
int font_xsize, font_ysize;
int lm_display_init(void)
{
XSizeHints hint;
int screen;
XVisualInfo vinfo;
int size_x = 640;
int size_y = 480;
XSetWindowAttributes xwa;
char *fontname = "fixed";
display = XOpenDisplay("");
if (display == NULL) {
return -1;
}
screen = DefaultScreen(display);
bg = RGB(0, 0, 0);
fg = RGB(255, 255, 255);
/* Fill in hint structure */
hint.x = 0;
hint.y = 0;
hint.width = size_x;
hint.height = size_y;
hint.flags = PPosition | PSize;
/* Make the window */
if (!XMatchVisualInfo(display, screen, 16, TrueColor, &vinfo)) {
printf("A 16 bit visual is need by this program\n");
return -1;
}
window = XCreateSimpleWindow(display,
DefaultRootWindow(display),
hint.x, hint.y,
hint.width, hint.height,
4, fg, bg);
/* Enable backing store */
xwa.backing_store = Always;
XChangeWindowAttributes(display, window, CWBackingStore, &xwa);
XSelectInput(display, window, StructureNotifyMask);
/* Tell other applications about this window */
XSetStandardProperties(display, window,
"linmodem", "linmodem",
None, NULL, 0, &hint);
/* Map window. */
XMapWindow(display, window);
/* Wait for map. */
while (1) {
XEvent xev;
XNextEvent(display, &xev);
if (xev.type == MapNotify && xev.xmap.event == window)
break;
}
XSelectInput(display, window, KeyPressMask
| ButtonPressMask);
gc = XCreateGC(display, window, 0, 0);
xfont = XLoadQueryFont (display, fontname);
if (!xfont) {
fprintf(stderr, "Could not load font '%s'\n", fontname);
return -1;
}
font_xsize = xfont->max_bounds.rbearing - xfont->min_bounds.lbearing;
font_ysize = xfont->max_bounds.ascent + xfont->max_bounds.descent;
set_state(DISP_MODE_SAMPLE);
return 0;
}
void lm_display_close(void)
{
XCloseDisplay(display);
}
void printf_at(int x, int y, char *fmt, ...)
{
va_list ap;
char buf[1024];
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
XSetFont(display, gc, xfont->fid);
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc,
x * font_xsize, y * font_ysize,
font_xsize * strlen(buf), font_ysize);
XSetForeground(display, gc, fg);
XDrawString(display, window, gc, x * font_xsize, (y + 1) * font_ysize - 1,
buf, strlen(buf));
va_end(ap);
}
/* draw a graph at (x1, y1, x1 + w, y1 + h). The points are computed
by calc_func */
#define DG_AUTOSCALE_YMIN 0x0001
#define DG_AUTOSCALE_YMAX 0x0002
void draw_graph(char *title,
int x1, int y1, int w, int h,
float xmin, float xmax, float ymin, float ymax,
int flags, float (*calc_func)(float))
{
int i, yy, xx;
float x, y;
int ly;
float tab[1024];
/* auto scale */
for(i=0;i<w;i++) {
x = ((float)i / (float)(w-1)) * (xmax - xmin) + xmin;
y = calc_func(x);
tab[i] = y;
if (i == 0) {
if (flags & DG_AUTOSCALE_YMIN)
ymin = y;
if (flags & DG_AUTOSCALE_YMAX)
ymax = y;
} else {
if (flags & DG_AUTOSCALE_YMIN && y < ymin)
ymin = y;
if (flags & DG_AUTOSCALE_YMAX && y > ymax)
ymax = y;
}
}
/* draw ! */
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc, x1, y1, w, h);
XSetForeground(display, gc, RGB(255, 0, 0));
XDrawRectangle(display, window, gc, x1, y1, w, h);
/* axis */
XSetForeground(display, gc, RGB(0, 255, 0));
if (xmin <= 0.0 && 0.0 <= xmax) {
xx = (int)rint(((0.0 - xmin) / (xmax - xmin)) * (w-1)) + x1;
XDrawLine(display, window, gc,
xx, y1, xx, y1 + h - 1);
}
if (ymin <= 0.0 && 0.0 <= ymax) {
yy = y1 + h - 1 - (int)rint(((0.0 - ymin) / (ymax - ymin)) * (h-1));
XDrawLine(display, window, gc,
x1, yy, x1 + w - 1, yy);
}
XSetForeground(display, gc, RGB(255, 255, 255));
ly = -1;
for(i=0;i<w;i++) {
y = tab[i];
if (y >= ymin && y <= ymax) {
yy = y1 + h - 1 - (int)rint(((y - ymin) / (ymax - ymin)) * (h-1));
if (ly >= 0) {
XDrawLine(display, window, gc,
i - 1, ly, i, yy);
} else {
XDrawPoint(display, window, gc,
i, yy);
}
ly = yy;
} else {
ly = -1;
}
}
/* title */
XSetForeground(display, gc, RGB(0, 255, 0));
printf_at((x1+font_xsize-1) / font_xsize,
(y1+font_ysize-1) / font_ysize,
"%s - ymin=%6.3e ymax=%6.3e", title, ymin, ymax);
}
/***************************************************/
/* utilities */
/***************************************************/
/* si and sq must be betwen -1.0 and 1.0 */
int nb_samples = 0;
void lm_dump_qam(float si, float sq)
{
int x, y;
if (disp_state != DISP_MODE_QAM)
return;
x = (int)(si * (QAM_SIZE/2)) + (QAM_SIZE/2);
y = (int)(sq * (QAM_SIZE/2)) + (QAM_SIZE/2);
if (x < 0 || x >= QAM_SIZE ||
y < 0 || y >= QAM_SIZE)
return;
XSetForeground(display, gc, RGB(255, 255, 255));
XDrawPoint(display, window, gc, x, y);
nb_samples++;
printf_at(minx, 1, "# samples: %d", nb_samples);
}
/* print samples */
#define NB_SAMPLES 512
float sample_mem[NB_CHANNELS][NB_SAMPLES];
int sample_pos[NB_CHANNELS];
float sample_hamming[NB_SAMPLES];
int sample_hamming_init = 0;
complex sample_fft[NB_SAMPLES];
int sample_channel;
float calc_sample(float x)
{
return sample_mem[sample_channel][(int)x];
}
float calc_sample_pow(float x)
{
complex *p = &sample_fft[(int)x];
return p->re * p->re + p->im * p->im;
}
void draw_samples(int channel)
{
int i;
sample_channel = channel;
draw_graph("Sample",
0, 0, QAM_SIZE, QAM_SIZE/2,
0.0, NB_SAMPLES - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX | DG_AUTOSCALE_YMIN,
calc_sample);
if (!sample_hamming_init) {
calc_hamming(sample_hamming, NB_SAMPLES);
sample_hamming_init = 1;
}
for(i=0;i<NB_SAMPLES;i++) {
sample_fft[i].re = sample_mem[channel][i] * sample_hamming[i];
sample_fft[i].im = 0;
}
fft_calc(sample_fft, NB_SAMPLES, 0);
draw_graph("Spectral power",
0, QAM_SIZE/2, QAM_SIZE, QAM_SIZE/2,
0.0, NB_SAMPLES/2 - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX, calc_sample_pow);
}
void lm_dump_sample(int channel, float val)
{
sample_mem[channel][sample_pos[channel]] = val;
if (++sample_pos[channel] == NB_SAMPLES) {
sample_pos[channel] = 0;
if ((disp_state == DISP_MODE_SAMPLE &&
channel == CHANNEL_SAMPLE) ||
(disp_state == DISP_MODE_SAMPLESYNC &&
channel == CHANNEL_SAMPLESYNC)) {
draw_samples(channel);
}
}
}
#if 0
int last_eye_y[2];
int last_eye_x[2];
/* print an eye diagram (each symbol is sampled at integer time
values). We print 1 centered period */
void lm_dump_eye(int channel, float time, float val)
{
int x, y;
if (disp_state != DISP_MODE_EYE)
return;
if (val < -1)
val = -1;
else if (val > 1)
val = 1;
y = (int)(val * (QAM_SIZE/2)) + (QAM_SIZE/2);
time += 0.5;
if (time >= 1.0)
time -= 1.0;
x = (int)(time * QAM_SIZE);
XSetForeground(display, gc, RGB(255, 255, 255));
if (x > last_eye_x[channel] && 0) {
XDrawLine(display, window, gc,
last_eye_x[channel], last_eye_y[channel], x, y);
} else {
XDrawPoint(display, window, gc, x, y);
}
last_eye_x[channel] = x;
last_eye_y[channel] = y;
}
#endif
/* print equalizer */
#define EQ_FFT_SIZE 144
static int eq_count = 0;
static s32 (*eq_filter)[2];
static int eq_norm;
static complex eq_fft[EQ_FFT_SIZE];
float calc_eq_re(float x)
{
return (float)eq_filter[(int)rint(x)][0] / (float)eq_norm;
}
float calc_eq_im(float x)
{
return (float)eq_filter[(int)rint(x)][1] / (float)eq_norm;
}
float calc_eq_pow(float x)
{
complex *p = &eq_fft[(int)x];
return p->re * p->re + p->im * p->im;
}
float calc_eq_phase(float x)
{
complex *p = &eq_fft[(int)x];
return atan2(p->im, p->re);
}
void lm_dump_equalizer(s32 eq_filter1[][2], int norm, int size)
{
int i;
if (disp_state != DISP_MODE_EQUALIZER)
return;
if (++eq_count == 1) {
eq_count = 0;
eq_filter = eq_filter1;
eq_norm = norm;
draw_graph("Eqz real",
0, 0, QAM_SIZE, QAM_SIZE/4,
0.0, size - 1, -1.5, 1.5,
0,
calc_eq_re);
draw_graph("Eqz imag",
0, QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, size - 1, -1.5, 1.5,
0,
calc_eq_im);
for(i=0;i<EQ_FFT_SIZE;i++) {
if (i < size) {
eq_fft[i].re = eq_filter[i][0] / (float)norm;
eq_fft[i].im = eq_filter[i][1] / (float)norm;
} else {
eq_fft[i].re = eq_fft[i].im = 0;
}
}
if (EQ_FFT_SIZE == 144) {
complex eq_fft1[144];
slow_fft(eq_fft1, eq_fft, EQ_FFT_SIZE, 0);
for(i=0;i<EQ_FFT_SIZE;i++)
eq_fft[i] = eq_fft1[i];
} else {
fft_calc(eq_fft, EQ_FFT_SIZE, 0);
}
draw_graph("Eqz spec pow",
0, 2*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, EQ_FFT_SIZE - 1, 0.0, 0.0,
DG_AUTOSCALE_YMAX, calc_eq_pow);
draw_graph("Eqz spec phase",
0, 3*QAM_SIZE/4, QAM_SIZE, QAM_SIZE/4,
0.0, EQ_FFT_SIZE - 1, -M_PI, M_PI,
0, calc_eq_phase);
}
}
/* agc */
void lm_dump_agc(float gain)
{
printf_at(minx, 2, "AGC: %10.5f", gain);
}
void lm_dump_linesim_power(float tx_db, float rx_db, float noise_db)
{
printf_at(minx, 3, "TX: %6.2f dB SNR: %6.2f dB", tx_db, rx_db - noise_db);
printf_at(minx, 4, "RX: %6.2f dB N0: %6.2f dB", rx_db, noise_db);
}
static void set_state(int state)
{
int i;
minx = ((QAM_SIZE + font_xsize) / font_xsize) + 1;
miny = ((QAM_SIZE + font_ysize) / font_ysize);
disp_state = state;
XSetForeground(display, gc, bg);
XFillRectangle(display, window, gc, 0, 0, 640, 480);
XSetForeground(display, gc, RGB(255, 0, 0));
XDrawRectangle(display, window, gc, 0, 0, QAM_SIZE, QAM_SIZE);
switch(disp_state) {
case DISP_MODE_QAM:
XDrawLine(display, window, gc, 0, QAM_SIZE/2, QAM_SIZE-1, QAM_SIZE/2);
XDrawLine(display, window, gc, QAM_SIZE/2, 0, QAM_SIZE/2, QAM_SIZE-1);
break;
default:
break;
}
printf_at(minx, 0, "Mode: %s", mode_str[disp_state]);
for(i=0;i<NB_MODES;i++) {
printf_at(1 + 15 * i, miny, "F%d:%s", i + 1, mode_str[i]);
}
}
int lm_display_poll_event(void)
{
char buf[80];
XEvent xev;
KeySym keysym;
XComposeStatus status;
if (XPending(display) <= 0)
return 0;
XNextEvent(display, &xev);
switch(xev.type) {
case KeyPress:
XLookupString((XKeyEvent *) & xev, buf, 80, &keysym, &status);
switch(keysym) {
case XK_q:
return 1;
case XK_F1:
case XK_F2:
case XK_F3:
case XK_F4:
case XK_F5:
case XK_F6:
case XK_F7:
case XK_F8:
{
int mode;
mode = keysym - XK_F1;
if (mode < NB_MODES) {
set_state(mode);
}
}
break;
}
break;
}
return 0;
}

19
display.h Normal file
View File

@ -0,0 +1,19 @@
/* display.c */
int lm_display_init(void);
void lm_display_close(void);
int lm_display_poll_event(void);
void lm_dump_qam(float si, float sq);
void lm_dump_eye(int channel, float time, float val);
#define NB_CHANNELS 2
enum {
CHANNEL_SAMPLE = 0,
CHANNEL_SAMPLESYNC,
};
void lm_dump_sample(int channel, float val);
void lm_dump_agc(float gain);
void lm_dump_equalizer(s32 eq_filter1[][2], int norm, int size);
void lm_dump_echocancel(s32 eq_filter1[][2], int norm, int size);
void lm_dump_linesim_power(float tx_db, float rx_db, float noise_db);

204
dsp.c Normal file
View File

@ -0,0 +1,204 @@
#include "lm.h"
s16 cos_tab[COS_TABLE_SIZE];
void dsp_init(void)
{
int i;
for(i=0;i<COS_TABLE_SIZE;i++) {
cos_tab[i] = (int) (cos( 2 * M_PI * i / COS_TABLE_SIZE) * COS_BASE);
}
}
/* DFT computation with Goertzel algorithm */
int compute_DFT(s16 *cos_tab, s16 *sin_tab, s16 *x, int k,int n)
{
int y_re,y_im,i,j,y_2;
j = 0;
#if 0
int s0,s1,s2;
s0 = s1 = 0;
for(i=0;i<n;i++) {
s2 = x[i] + ((cos_tab[j] * s1) >> (COS_BITS-1)) - s0;
s0 = s1;
s1 = s2;
j += k;
if (j >= n) j -= n;
}
y_re = s1 - ((cos_tab[k] * s0) >> COS_BITS);
/* cannot use cos_tab because n is even */
y_im = s1 - ((sin_tab[k] * s0) >> COS_BITS);
#else
y_re = y_im = 0;
for(i=0;i<n;i++) {
y_re += cos_tab[j] * x[i];
y_im += sin_tab[j] * x[i];
j += k;
if (j >= n) j -= n;
}
y_re = y_re >> COS_BITS;
y_im = y_im >> COS_BITS;
#endif
y_2 = y_re * y_re + y_im * y_im;
return y_2;
}
/* horrible fft code - only needed to debug or fixed table generation */
#define FFT_MAX_SIZE 2048
static float norm;
static float wfft[FFT_MAX_SIZE];
static short tinv[FFT_MAX_SIZE];
static int nf = 0, nf2, nf4;
int fft_init(int n)
{
float p,k;
int i,j,a,c, nk;
nf=n;
nf2=nf/2;
nf4=nf/4;
nk=0;
while (n>>=1) nk++;
norm=1/sqrt(nf);
k=0;
p=(2*M_PI)/nf;
for(i=0;i<=nf4;i++) {
wfft[i]=cos(k);
k+=p;
}
for(i=0;i<nf;i++) {
a=i;
c=0;
for(j=0;j<nk;j++) {
c=(c<<1)|(a&1);
a>>=1;
}
tinv[i]= c<=i ? -1 : c;
}
return 0;
}
/* r = TRUE : reverse FFT */
void fft_calc(complex *x,int n, int r)
{
int i,j,k,l;
complex a,b,c;
complex *p,*q;
/* auto init of coefficients */
if (n != nf) {
fft_init(n);
}
k=nf2;
l=1;
do {
p=x;
q=x+k;
i=l;
do {
j=0;
do {
a=*p;
b=*q;
p->re=a.re+b.re;
p->im=a.im+b.im;
b.re=a.re-b.re;
b.im=a.im-b.im;
if (j==0) {
*q=b;
} else if (j==nf4) {
if (r) {
q->re=b.im;
q->im=-b.re;
} else {
q->re=-b.im;
q->im=b.re;
}
q->re=-b.im;
q->im=b.re;
} else if (j<nf4) {
c.re=wfft[j];
c.im=wfft[nf4-j];
if (r) c.im=-c.im;
q->re=b.re*c.re-b.im*c.im;
q->im=b.im*c.re+b.re*c.im;
} else {
c.re=-wfft[nf2-j];
c.im=wfft[j-nf4];
if (r) c.im=-c.im;
q->re=b.re*c.re-b.im*c.im;
q->im=b.im*c.re+b.re*c.im;
}
p++;
q++;
j+=l;
} while (j<nf2);
p+=k;
q+=k;
} while (--i);
k>>=1;
l<<=1;
} while (k);
for(i=0,p=x;i<nf;i++,p++) {
p->re*=norm;
p->im*=norm;
}
for(i=0,p=x;i<nf;i++,p++) if ((j=tinv[i])!=-1) {
a=*p;
*p=x[j];
x[j]=a;
}
}
/* hamming window */
void calc_hamming(float *ham, int NF)
{
int i;
for(i=0;i<NF;i++) {
ham[i]=0.54-0.46*cos((2*M_PI*i)/NF);
}
}
/* slow floating point fft, for testing & init */
void slow_fft(complex *output, complex *input, int n, int r)
{
complex coef[n], a, b, c;
int i,j;
for(i=0;i<n;i++) {
float a;
a = - 2 * M_PI * i / (float) n;
if (r)
a = -a;
coef[i].re = cos(a);
coef[i].im = sin(a);
}
for(i=0;i<n;i++) {
a.re = 0;
a.im = 0;
for(j=0;j<n;j++) {
b = input[j];
c = coef[(j * i) % n];
a.re += b.re*c.re-b.im*c.im;
a.im += b.im*c.re+b.re*c.im;
}
output[i] = a;
}
}

98
dsp.h Normal file
View File

@ -0,0 +1,98 @@
typedef signed char s8;
typedef unsigned char u8;
typedef short s16;
typedef unsigned short u16;
typedef int s32;
typedef unsigned int u32;
typedef long long s64;
typedef unsigned long long u64;
#define PHASE_BITS 16
#define PHASE_BASE (1 << PHASE_BITS)
#define COS_BITS 14
#define COS_BASE (1 << COS_BITS)
#define COS_TABLE_BITS 13
#define COS_TABLE_SIZE (1 << COS_TABLE_BITS)
extern s16 cos_tab[COS_TABLE_SIZE];
void dsp_init(void);
/* unoptimized DSP C functions */
/* XXX: optimize them for each architecture */
static inline int dsp_cos(int phase)
{
return cos_tab[(phase >> (PHASE_BITS - COS_TABLE_BITS)) & (COS_TABLE_SIZE-1)];
}
static inline int dsp_dot_prod(const s16 *tab1, const s16 *tab2,
int n, int sum)
{
int i;
for(i=0;i<n;i++) {
sum += tab1[i] * tab2[i];
}
return sum;
}
static inline int dsp_norm2(s16 *tab, int n, int sum)
{
int i;
for(i=0;i<n;i++) {
sum += tab[i] * tab[i];
}
return sum;
}
static inline void dsp_sar_tab(s16 *tab, int n, int shift)
{
int i;
for(i=0;i<n;i++) {
tab[i] >>= shift;
}
}
static inline int dsp_max_bits(s16 *tab, int n)
{
int i, max, v, b;
max = 0;
for(i=0;i<n;i++) {
v = abs(tab[i]);
if (v > max)
max = v;
}
b = 0;
while (max != 0) {
b++;
max>>=1;
}
return b;
}
static inline int dsp_sqr(int n)
{
return n*n;
}
int compute_DFT(s16 *cos_tab, s16 *sin_tab, s16 *x, int k,int n);
typedef struct {
float re,im;
} complex;
/* medium speed FFT */
void fft_calc(complex *x,int n, int r);
/* slow FFT for any size */
void slow_fft(complex *output, complex *input, int n, int r);
/* compute the hamming window */
void calc_hamming(float *ham, int NF);

192
dtmf.c Normal file
View File

@ -0,0 +1,192 @@
/*
* DTMF demodulator, inspirated from the multimon project of Thomas
* Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu). It is different
* in the sense that it uses a true block based DFT estimator.
*
* Copyright (c) 1999 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include "lm.h"
/*
* DTMF frequencies
*
* 1209 1336 1477 1633
* 697 1 2 3 A
* 770 4 5 6 B
* 852 7 8 9 C
* 941 * 0 # D
* */
static const char *dtmf_transl = "123A456B789C*0#D";
static int dtmf_freq[] = {
1209, 1336, 1477, 1633,
697, 770, 852, 941,
};
#define SAMPLE_RATE 8000
/* Each DTMF digit is estimed on N samples by estimating the DFT of
the signal. It is quite reliable, but the frequency resolution is
not accurate enough to meet the very strict ITU requirements. */
/* DTMF modulation */
void DTMF_mod_init(DTMF_mod_state *s)
{
s->t1 = s->t2 = 0;
s->omega1 = 0;
s->samples_left = 0;
}
/* compute parameters for a new digit */
static void compute_params(DTMF_mod_state *s)
{
const char *p;
int v,digit,f1,f2;
/* if we were playing a digit, we make a pause */
if (s->omega1 != 0)
goto nodigit;
digit = s->get_digit(s->opaque);
if (digit == -1)
goto nodigit;
p = dtmf_transl;
while (*p != digit && *p) p++;
if (*p) {
v = p - dtmf_transl;
f1 = dtmf_freq[v & 3];
f2 = dtmf_freq[4 + (v >> 2)];
s->omega1 = (PHASE_BASE * f1) / SAMPLE_RATE;
s->omega2 = (PHASE_BASE * f2) / SAMPLE_RATE;
/* amplitude */
s->amp = (int) (pow(10, s->dtmf_level / 20.0) * 32768.0);
/* number of samples to play */
s->samples_left = (s->digit_length_ms * SAMPLE_RATE) / 1000;
} else {
nodigit:
s->omega1 = 0;
s->samples_left = (s->digit_pause_ms * SAMPLE_RATE) / 1000;
}
}
void DTMF_mod(DTMF_mod_state *s, s16 *samples, unsigned int nb)
{
int len, t1, t2, amp, i;
while (nb > 0) {
if (s->samples_left == 0) {
compute_params(s);
}
len = nb;
if (len > s->samples_left)
len = s->samples_left;
if (s->omega1 != 0) {
t1 = s->t1;
t2 = s->t2;
amp = s->amp;
for(i=0;i<len;i++) {
int v;
v = ((dsp_cos(t1) + dsp_cos(t2)) * amp) >> COS_BITS;
samples[i] = v;
t1 += s->omega1;
t2 += s->omega2;
}
s->t1 = t1;
s->t2 = t2;
} else {
memset(samples, 0, nb * 2); /* silence between digits */
}
nb -= len;
samples += len;
s->samples_left -= len;
}
}
/* DTMF demodulation */
void DTMF_demod_init(DTMF_demod_state *s)
{
int i;
for(i=0;i<DTMF_N;i++) {
s->cos_tab[i] = (int) (cos( 2 * M_PI * i / DTMF_N) * COS_BASE);
s->sin_tab[i] = (int) (sin( 2 * M_PI * i / DTMF_N) * COS_BASE);
}
for(i=0;i<8;i++) {
float v;
v = (float)dtmf_freq[i] / (float)SAMPLE_RATE * (float)DTMF_N;
s->dtmf_coefs[i] = (int)rint(v);
}
s->buf_ptr = 0;
s->last_digit = 0;
}
int find_max(int *val,int n)
{
int i,j,max;
j = 0;
max = val[0];
for(i=1;i<n;i++) {
if (val[i] > max) {
j = i;
max = val[i];
}
}
return j;
}
void DTMF_demod(DTMF_demod_state *s,
const s16 *samples, unsigned int nb)
{
int i, j, power[8], power0, v1, v2, digit, bits, p1, p2, p0;
for(j=0;j<nb;j++) {
s->buf[s->buf_ptr++] = samples[j];
if (s->buf_ptr >= DTMF_N) {
/* decision based on one block */
bits = dsp_max_bits(s->buf, DTMF_N);
if (bits < 8) bits = 8;
dsp_sar_tab(s->buf, DTMF_N, bits - 8);
power0 = dsp_norm2(s->buf, DTMF_N, 0);
for(i=0;i<8;i++) {
power[i] = compute_DFT(s->cos_tab, s->sin_tab, s->buf,
s->dtmf_coefs[i], DTMF_N);
}
v1 = find_max(power, 4);
v2 = find_max(power + 4, 4);
p0 = (int)(power0 * 0.3);
p1 = (2 * power[v1]) / DTMF_N;
p2 = (2 * power[v2 + 4]) / DTMF_N;
#if 0
printf("%d %d %d %f %f\n",p0, v1, v2, (float) p1 / p0,(float) p2 / p0);
#endif
if (p1 > p0 && p2 > p0)
digit = dtmf_transl[v1 | (v2 << 2)];
else
digit = 0;
if (digit != s->last_digit) {
if (digit != 0) {
s->put_digit(s->opaque, digit);
}
s->last_digit = digit;
}
s->buf_ptr = 0;
}
}
}

39
dtmf.h Normal file
View File

@ -0,0 +1,39 @@
/* modulation */
typedef struct {
/* parameters */
int dtmf_level; /* in dB */
int digit_length_ms;
int digit_pause_ms;
void *opaque;
int (*get_digit)(void *opaque);
/* internal state */
int omega1,omega2,t1,t2;
int samples_left;
int amp;
} DTMF_mod_state;
void DTMF_mod_init(DTMF_mod_state *s);
void DTMF_mod(DTMF_mod_state *s, s16 *samples, unsigned int nb);
/* demodulation */
#define DTMF_N 205
typedef struct {
/* parameters */
void *opaque;
void (*put_digit)(void *opaque, int digit);
/* internal state */
s16 buf[DTMF_N];
int buf_ptr;
s16 cos_tab[DTMF_N];
s16 sin_tab[DTMF_N];
int last_digit;
int dtmf_coefs[8]; /* coefficient number of the DFT */
} DTMF_demod_state;
void DTMF_demod_init(DTMF_demod_state *s);
void DTMF_demod(DTMF_demod_state *s,
const s16 *samples, unsigned int nb);

259
fsk.c Normal file
View File

@ -0,0 +1,259 @@
/*
* generic FSK modulator & demodulator
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include "lm.h"
void FSK_mod_init(FSK_mod_state *s)
{
int b;
s->omega[0] = (PHASE_BASE * s->f_lo) / s->sample_rate;
s->omega[1] = (PHASE_BASE * s->f_hi) / s->sample_rate;
s->baud_incr = (s->baud_rate * 0x10000) / s->sample_rate;
s->phase = 0;
s->baud_frac = 0;
b = 0;
s->current_bit = b;
}
void FSK_mod(FSK_mod_state *s, s16 *samples, unsigned int nb)
{
int phase,baud_frac,b,i;
phase = s->phase;
baud_frac = s->baud_frac;
b = s->current_bit;
for(i=0;i<nb;i++) {
baud_frac += s->baud_incr;
if (baud_frac >= 0x10000) {
baud_frac -= 0x10000;
b = s->get_bit(s->opaque);
}
samples[i] = dsp_cos(phase);
phase += s->omega[b];
}
s->phase = phase;
s->baud_frac = baud_frac;
s->current_bit = b;
}
void FSK_demod_init(FSK_demod_state *s)
{
float phase;
int i, a;
s->baud_incr = (s->baud_rate * 0x10000) / s->sample_rate;
s->baud_pll = 0;
s->baud_pll_adj = s->baud_incr / 4;
s->filter_size = s->sample_rate / s->baud_rate;
memset(s->filter_buf, 0, sizeof(s->filter_buf));
s->buf_ptr = s->filter_size;
s->lastsample = 0;
/* compute the filters */
for(i=0;i<s->filter_size;i++) {
phase = 2 * M_PI * s->f_lo * i / (float)s->sample_rate;
s->filter_lo_i[i] = (int) (cos(phase) * COS_BASE);
s->filter_lo_q[i] = (int) (sin(phase) * COS_BASE);
phase = 2 * M_PI * s->f_hi * i / (float)s->sample_rate;
s->filter_hi_i[i] = (int) (cos(phase) * COS_BASE);
s->filter_hi_q[i] = (int) (sin(phase) * COS_BASE);
}
s->shift = -2;
a = s->filter_size;
while (a != 0) {
s->shift++;
a /= 2;
}
printf("shift=%d\n", s->shift);
}
void FSK_demod(FSK_demod_state *s, const s16 *samples, unsigned int nb)
{
int buf_ptr, corr, newsample, baud_pll, i;
int sum;
baud_pll = s->baud_pll;
buf_ptr = s->buf_ptr;
for(i=0;i<nb;i++) {
/* add a new sample in the demodulation filter */
s->filter_buf[buf_ptr++] = samples[i] >> s->shift;
if (buf_ptr == FSK_FILTER_BUF_SIZE) {
memmove(s->filter_buf,
s->filter_buf + FSK_FILTER_BUF_SIZE - s->filter_size,
s->filter_size * sizeof(s16));
buf_ptr = s->filter_size;
}
/* non coherent FSK demodulation - not optimal, but it seems
very difficult to do another way */
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
s->filter_hi_i, s->filter_size, 0);
corr = corr >> COS_BITS;
sum = corr * corr;
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
s->filter_hi_q, s->filter_size, 0);
corr = corr >> COS_BITS;
sum += corr * corr;
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
s->filter_lo_i, s->filter_size, 0);
corr = corr >> COS_BITS;
sum -= corr * corr;
corr = dsp_dot_prod(s->filter_buf + buf_ptr - s->filter_size,
s->filter_lo_q, s->filter_size, 0);
corr = corr >> COS_BITS;
sum -= corr * corr;
lm_dump_sample(CHANNEL_SAMPLESYNC, sum / 32768.0);
// printf("sum=%0.3f\n", sum / 65536.0);
newsample = sum > 0;
/* baud PLL synchronisation : when we see a transition of
frequency, we tend to modify the baud phase so that it is
in the middle of two bits */
if (s->lastsample != newsample) {
s->lastsample = newsample;
// printf("pll=%0.3f (%d)\n", baud_pll / 65536.0, newsample);
if (baud_pll < 0x8000)
baud_pll += s->baud_pll_adj;
else
baud_pll -= s->baud_pll_adj;
}
baud_pll += s->baud_incr;
if (baud_pll >= 0x10000) {
baud_pll -= 0x10000;
// printf("baud=%f (%d)\n", baud_pll / 65536.0, s->lastsample);
s->put_bit(s->opaque, s->lastsample);
}
}
s->baud_pll = baud_pll;
s->buf_ptr = buf_ptr;
}
/* test for FSK using V21 or V23 */
#define NB_SAMPLES 40
#define MAXDELAY 32
static int tx_bits[MAXDELAY], tx_ptr = 0, rx_ptr = 0;
static int tx_blank = 32;
/* transmit random bits with a sync header (31 ones, 1 zero) */
static int test_get_bit(void *opaque)
{
int bit;
if (tx_blank != 0) {
/* send 1 at the beginning for synchronization */
bit = (tx_blank > 1);
tx_blank--;
} else {
bit = random() % 2;
tx_bits[tx_ptr] = bit;
if (++tx_ptr == MAXDELAY)
tx_ptr = 0;
}
return bit;
}
static int nb_bits = 0, errors = 0, sync_count = 0, got_sync = 0;
static void test_put_bit(void *opaque, int bit)
{
int tbit;
if (!got_sync) {
if (bit) {
sync_count++;
} else {
if (sync_count > 16)
got_sync = 1;
sync_count = 0;
}
} else {
tbit = tx_bits[rx_ptr];
if (++rx_ptr == MAXDELAY)
rx_ptr = 0;
if (bit != tbit) {
errors++;
}
nb_bits++;
}
}
void FSK_test(int do_v23)
{
FSK_mod_state tx;
FSK_demod_state rx;
int err, calling;
struct LineModelState *line_state;
s16 buf[NB_SAMPLES];
s16 buf1[NB_SAMPLES];
s16 buf2[NB_SAMPLES];
s16 buf3[NB_SAMPLES];
FILE *f1;
err = lm_display_init();
if (err < 0) {
fprintf(stderr, "Could not init X display\n");
exit(1);
}
line_state = line_model_init();
f1 = fopen("cal.sw", "wb");
if (f1 == NULL) {
perror("cal.sw");
exit(1);
}
calling = 0;
if (do_v23) {
V23_mod_init(&tx, calling, test_get_bit, NULL);
V23_demod_init(&rx, 1 - calling, test_put_bit, NULL);
} else {
V21_mod_init(&tx, calling, test_get_bit, NULL);
V21_demod_init(&rx, 1 - calling, test_put_bit, NULL);
}
nb_bits = 0;
errors = 0;
for(;;) {
if (lm_display_poll_event())
break;
FSK_mod(&tx, buf, NB_SAMPLES);
memset(buf3, 0, sizeof(buf3));
line_model(line_state, buf1, buf, buf2, buf3, NB_SAMPLES);
fwrite(buf, 1, NB_SAMPLES * 2, f1);
FSK_demod(&rx, buf1, NB_SAMPLES);
}
fclose(f1);
printf("errors=%d nb_bits=%d Pe=%f\n",
errors, nb_bits, (float) errors / (float)nb_bits);
}

55
fsk.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef FSK_H
#define FSK_H
typedef struct {
/* parameters */
int f_lo,f_hi;
int sample_rate;
int baud_rate;
/* local variables */
int phase, baud_frac, baud_incr;
int omega[2];
int current_bit;
void *opaque;
get_bit_func get_bit;
} FSK_mod_state;
/* max = 106 for 75 bauds */
#define FSK_FILTER_SIZE 128
#define FSK_FILTER_BUF_SIZE 256
typedef struct {
/* parameters */
int f_lo,f_hi;
int sample_rate;
int baud_rate;
/* local variables */
int filter_size;
s16 filter_lo_i[FSK_FILTER_SIZE];
s16 filter_lo_q[FSK_FILTER_SIZE];
s16 filter_hi_i[FSK_FILTER_SIZE];
s16 filter_hi_q[FSK_FILTER_SIZE];
s16 filter_buf[FSK_FILTER_BUF_SIZE];
int buf_ptr;
int baud_incr;
int baud_pll, baud_pll_adj, baud_pll_threshold;
int lastsample;
int shift;
void *opaque;
put_bit_func put_bit;
} FSK_demod_state;
void FSK_mod_init(FSK_mod_state *s);
void FSK_mod(FSK_mod_state *s, s16 *samples, unsigned int nb);
void FSK_demod_init(FSK_demod_state *s);
void FSK_demod(FSK_demod_state *s, const s16 *samples, unsigned int nb);
void FSK_test(int do_v23);
#endif

579
lm.c Normal file
View File

@ -0,0 +1,579 @@
/*
* The Generic Linux Soft Modem
*
* Copyright (c) 1999,2000 Fabrice Bellard.
* Copyright (c) 1999 Pavel Machek
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the ITU specification and by using basic signal processing
* knowledge.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
#include "lm.h"
#include "v34.h"
#include "v90.h"
#define DEBUG
int lm_debug = 0;
int debug_state = 0;
/* default parameters */
static LinModemConfig default_lm_config =
{
pulse_dial: 0,
dtmf_level: -9,
dtmf_digit_length: 150,
dtmf_pause_length: 100,
available_modulations: V8_MOD_V21 | V8_MOD_V23,
};
/* fifo handling */
void sm_init_fifo(struct sm_fifo *f, u8 *buf, int size)
{
f->sptr = buf;
f->eptr = buf + size;
f->wptr = f->rptr = f->sptr;
f->size = 0;
f->max_size = size;
}
void sm_flush(struct sm_fifo *f)
{
f->wptr = f->rptr = f->sptr;
f->size = 0;
}
int sm_size(struct sm_fifo *f)
{
return f->size;
}
void sm_put_bit(struct sm_fifo *f, int v)
{
if (f->size < f->max_size) {
*f->wptr++ = v;
if (f->wptr == f->eptr) f->wptr = f->sptr;
f->size++;
}
}
/* put bits, from MSB to LSB */
void sm_put_bits(struct sm_fifo *f, int v, int n)
{
int i;
for(i=n-1;i>=0;i--) {
sm_put_bit(f, (v >> i) & 1);
}
}
int sm_peek_bit(struct sm_fifo *f)
{
if (f->size > 0)
return *f->rptr;
else
return -1;
}
int sm_get_bit(struct sm_fifo *f)
{
int v;
if (f->size > 0) {
f->size--;
v = *f->rptr++;
if (f->rptr == f->eptr) f->rptr = f->sptr;
return v;
} else {
/* fifo empty */
return -1;
}
}
/* return -1 if not enough bits */
int sm_get_bits(struct sm_fifo *f, int n)
{
int v,i;
if (f->size < n)
return -1;
v = 0;
for(i=n-1;i>=0;i--) {
v |= (sm_get_bit(f) << i);
}
return v;
}
/* timer handling */
/* note: the current time handling is horrible because we use a global
state which is initialized when entering in sm_process() */
static unsigned int sim_time;
/* current time, in samples */
int sm_time(void)
{
return sim_time;
}
/* delay is in ms */
void sm_set_timer(struct sm_timer *t, int delay)
{
t->timeout = sm_time() + (delay * 8000) / 1000;
}
/* return 1 if timer expired */
int sm_check_timer(struct sm_timer *t)
{
long timeout;
timeout = sm_time();
return (timeout >= t->timeout);
}
/*
Main modem state machine. It handles the dialing & rings, and then
call the corresponding protocol handlers.
*/
char *sm_states_str[] = {
#define TAG(s) #s ,
#include "lmstates.h"
};
static int dtmf_get_digit(void *opaque)
{
struct sm_state *sm = opaque;
if (sm->dtmf_ptr < sm->dtmf_len) {
return sm->call_num[sm->dtmf_ptr++];
} else {
return -1;
}
}
static void dtmf_put_digit(void *opaque, int digit)
{
printf("DTMF: got digit '%c'\n", digit);
}
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples)
{
/* XXX: time hack */
sim_time = sm->time;
/* modulation */
switch(sm->state) {
case SM_DTMF_DIAL_WAIT:
case SM_DTMF_DIAL_WAIT1:
DTMF_mod(&sm->dtmf_tx, output, nb_samples);
break;
default:
memset(output, 0, nb_samples * sizeof(s16));
break;
}
/* demodulation */
switch(sm->state) {
case SM_TEST_RING2:
DTMF_demod(&sm->dtmf_rx, input, nb_samples);
break;
}
/* write state transition */
if (lm_debug) {
if (sm->state != sm->debug_laststate) {
sm->debug_laststate = sm->state;
printf("%s: state: %s\n", sm->name, sm_states_str[sm->state]);
}
}
switch(sm->state) {
/* nothing to do (except waiting for a connection) */
case SM_IDLE:
break;
case SM_GO_ONHOOK:
{
sm->hw->set_offhook(sm->hw_state, 0);
sm->state = SM_IDLE;
}
break;
/* calling modem */
case SM_CALL:
{
sm->calling = 1;
sm->hangup_request = 0;
sm->state = SM_DTMF_DIAL;
sm->hw->set_offhook(sm->hw_state, 1);
sm_set_timer(&sm->dtmf_timer, 2000);
}
break;
case SM_DTMF_DIAL:
if (sm->hangup_request) {
sm->state = SM_GO_ONHOOK;
} else if (sm_check_timer(&sm->dtmf_timer)) {
DTMF_mod_state *p = &sm->dtmf_tx;
sm->dtmf_ptr = 0;
sm->dtmf_len = strlen(sm->call_num);
p->get_digit = dtmf_get_digit;
p->opaque = sm;
p->dtmf_level = sm->lm_config->dtmf_level;
p->digit_length_ms = sm->lm_config->dtmf_digit_length;
p->digit_pause_ms = sm->lm_config->dtmf_pause_length;
DTMF_mod_init(p);
sm->state = SM_DTMF_DIAL_WAIT;
}
break;
case SM_DTMF_DIAL_WAIT:
{
if (sm->dtmf_ptr >= sm->dtmf_len) {
/* wait some time after dialing */
sm_set_timer(&sm->dtmf_timer, 1000);
sm->state = SM_DTMF_DIAL_WAIT1;
}
}
break;
case SM_DTMF_DIAL_WAIT1:
{
if (sm_check_timer(&sm->dtmf_timer)) {
/* start of V8 */
V8_init(&sm->u.v8_state, 1, sm->lm_config->available_modulations);
sm->state = SM_V8;
}
}
break;
/* answer modem */
/* wait 2 sec until ring detected (for simulation only) */
/* we try to recognize the DTMF value for fun :-) */
case SM_TEST_RING:
{
sm->calling = 0;
sm->dtmf_rx.opaque = sm;
sm->dtmf_rx.put_digit = dtmf_put_digit;
DTMF_demod_init(&sm->dtmf_rx);
sm_set_timer(&sm->ring_timer, 5000);
sm->state = SM_TEST_RING2;
}
break;
case SM_TEST_RING2:
{
if (sm_check_timer(&sm->ring_timer)) {
sm->state = SM_RECEIVE;
}
}
break;
/* entry point to receive a connection */
case SM_RECEIVE:
{
sm->calling = 0;
sm->hangup_request = 0;
sm->hw->set_offhook(sm->hw_state, 1);
V8_init(&sm->u.v8_state, 0, sm->lm_config->available_modulations);
sm->state = SM_V8;
}
break;
/* V8 handling (both calling & receive) */
case SM_V8:
{
int ret;
if (sm->hangup_request) {
printf("ddezde\n");
sm->state = SM_GO_ONHOOK;
} else {
ret = V8_process(&sm->u.v8_state, output, input, nb_samples);
switch(ret) {
case V8_MOD_HANGUP:
sm->state = SM_GO_ONHOOK;
break;
case V8_MOD_V21:
V21_init(&sm->u.v21_state, sm->calling,
serial_get_bit, serial_put_bit, sm);
sm->state = SM_V21;
break;
case V8_MOD_V23:
V23_init(&sm->u.v23_state, sm->calling,
serial_get_bit, serial_put_bit, sm);
sm->state = SM_V23;
break;
}
}
}
break;
/* V21 handling (both calling & receive) */
case SM_V21:
{
int ret;
ret = V21_process(&sm->u.v21_state, output, input, nb_samples);
if (ret || sm->hangup_request)
sm->state = SM_GO_ONHOOK;
}
break;
/* V23 handling (both calling & receive) */
case SM_V23:
{
int ret;
ret = V23_process(&sm->u.v23_state, output, input, nb_samples);
if (ret || sm->hangup_request)
sm->state = SM_GO_ONHOOK;
}
break;
}
/* XXX: time hack */
sm->time = sim_time + nb_samples;
}
/*
* Send a dial request.
*/
int lm_start_dial(struct sm_state *s, int pulse, const char *number)
{
if (s->state != SM_IDLE)
return -1;
s->pulse_dial = pulse;
strcpy(s->call_num, number);
s->state = SM_CALL;
return 0;
}
/*
* Send a receive request (for example to respond to a ring)
*/
int lm_start_receive(struct sm_state *s)
{
if (s->state != SM_IDLE)
return -1;
s->state = SM_RECEIVE;
return 0;
}
/*
* disconnect the modem
*/
int lm_hangup(struct sm_state *s)
{
if (s->state == SM_IDLE)
return -1;
s->hangup_request = 1;
return 0;
}
/*
* return a simplified state of the modem.
*/
enum lm_get_state_val lm_get_state(struct sm_state *s)
{
switch(s->state) {
case SM_IDLE:
return LM_STATE_IDLE;
case SM_V21:
case SM_V23:
return LM_STATE_CONNECTED;
default:
return LM_STATE_CONNECTING;
}
}
void lm_init(struct sm_state *sm, struct sm_hw_info *hw, const char *name)
{
memset(sm, 0, sizeof(*sm));
sm->hw = hw;
/* pretty name */
strcpy(sm->name, name);
/* init fifos */
sm_init_fifo(&sm->tx_fifo, sm->tx_fifo_buf, SM_FIFO_SIZE);
sm_init_fifo(&sm->rx_fifo, sm->rx_fifo_buf, SM_FIFO_SIZE);
/* we open the hardware driver */
sm->hw_state = malloc(sizeof(struct lm_interface_state));
sm->hw_state->sm = sm;
sm->hw->open(sm->hw_state);
sm->debug_laststate = -1;
sm->state = SM_IDLE;
/* config */
sm->lm_config = &default_lm_config;
}
void sigusr1_debug(int dummy)
{
printf( "<<<Debugging signal came>>>\n" );
debug_state = 1;
}
void help(void)
{
printf("Linux Generic Software Modem\n"
"Copyright (c) 1999, 2000 Fabrice Bellard\n"
"\n"
"usage: lm [options]"
"Test options:\n"
"-v : verbose mode (additive)\n"
"-s : modem test with the line simulator\n"
"-m mod: test the modulation 'mod'. 'mod' can be:\n"
" v21, v23, v22, v34, v90\n"
"\n"
"Sound card support\n"
"-t : use sound card as modem\n"
"\n"
"LTmodem support:\n"
"-a : test answer mode with ltmodem\n"
"-c command: use 'command' as modem driver\n"
"-d number: dial 'number'\n"
);
}
enum {
MODE_NONE,
MODE_LINESIM,
MODE_V21TEST,
MODE_V22TEST,
MODE_V23TEST,
MODE_V34TEST,
MODE_V90TEST,
MODE_LTMODEM_CALL,
MODE_LTMODEM_ANSWER,
MODE_SOUNDCARD,
};
extern char *modem_command, *dial_number;
int main(int argc, char **argv)
{
int c, mode, calling;
signal(SIGUSR1, sigusr1_debug);
mode = MODE_NONE;
calling = 0;
for(;;) {
c = getopt(argc, argv, "hvstrac:d:m:");
if (c == -1) break;
switch(c) {
case 'v':
lm_debug++;
break;
case 'm':
if (!strcasecmp(optarg, "v21"))
mode = MODE_V21TEST;
else if (!strcasecmp(optarg, "v22"))
mode = MODE_V22TEST;
else if (!strcasecmp(optarg, "v23"))
mode = MODE_V23TEST;
else if (!strcasecmp(optarg, "v34"))
mode = MODE_V34TEST;
else if (!strcasecmp(optarg, "v90"))
mode = MODE_V90TEST;
else {
fprintf(stderr, "incorrect modulation: '%s'\n", optarg);
exit(1);
}
break;
case 's':
mode = MODE_LINESIM;
break;
case 't':
mode = MODE_SOUNDCARD;
break;
/* LTmodem options */
case 'c':
modem_command = optarg;
break;
case 'r':
break;
case 'a': /* "Answer" mode */
mode = MODE_LTMODEM_ANSWER;
break;
case 'd': /* Dial given number and exit mode */
mode = MODE_LTMODEM_CALL;
dial_number = optarg;
break;
default:
help();
exit(1);
}
}
if (mode == MODE_NONE) {
help();
exit(1);
}
srandom(0); /* we want a deterministic test */
dsp_init();
V34_static_init();
switch(mode) {
case MODE_V21TEST:
FSK_test(0);
break;
case MODE_V22TEST:
V22_test();
break;
case MODE_V23TEST:
FSK_test(1);
break;
case MODE_V34TEST:
V34_test();
break;
case MODE_V90TEST:
V90_test();
break;
case MODE_LINESIM:
line_simulate();
break;
case MODE_LTMODEM_CALL:
case MODE_LTMODEM_ANSWER:
real_test(mode == MODE_LTMODEM_CALL);
system("ltmodem -c");
break;
case MODE_SOUNDCARD:
soundcard_modem();
break;
}
return 0;
}

213
lm.h Normal file
View File

@ -0,0 +1,213 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include "dsp.h"
#define LM_VERSION "0.2.5"
/* bit fifo */
struct sm_fifo {
unsigned char *sptr, *wptr, *rptr, *eptr;
int size, max_size;
};
void sm_flush(struct sm_fifo *f);
int sm_size(struct sm_fifo *f);
void sm_put_bit(struct sm_fifo *f, int v);
void sm_put_bits(struct sm_fifo *f, int v, int n);
int sm_get_bit(struct sm_fifo *f);
int sm_get_bits(struct sm_fifo *f, int n);
void sm_init_fifo(struct sm_fifo *f, u8 *buf, int size);
/* bit I/O for data pumps */
typedef void (*put_bit_func)(void *opaque, int bit);
typedef int (*get_bit_func)(void *opaque);
/* timer */
struct sm_timer {
long timeout;
};
int sm_time(void);
void sm_set_timer(struct sm_timer *t, int delay);
int sm_check_timer(struct sm_timer *t);
/* debug */
extern int lm_debug;
extern char *sm_states_str[];
/* protocol description */
#include "dtmf.h"
#include "fsk.h"
#include "v21.h"
#include "v22.h"
#include "v23.h"
#include "v8.h"
#include "v34.h"
/* modem state */
#define SM_FIFO_SIZE 4096
struct sm_state {
/* pretty name of the modem (to debug) */
char name[16];
struct sm_hw_info *hw;
struct lm_interface_state *hw_state;
/* bytes to be transmitted */
struct sm_fifo tx_fifo;
u8 tx_fifo_buf[SM_FIFO_SIZE];
/* received chars */
struct sm_fifo rx_fifo;
u8 rx_fifo_buf[SM_FIFO_SIZE];
/* true if we are the caller */
int calling;
/* phone number to call */
char call_num[64];
int pulse_dial; /* TRUE if we must use pulse dialing */
/* dialing */
struct sm_timer dtmf_timer;
DTMF_mod_state dtmf_tx;
int dtmf_ptr, dtmf_len; /* pointer in call_num */
/* dtmf receive: for testing (or voice mode in the future) */
struct sm_timer ring_timer;
DTMF_demod_state dtmf_rx;
/* modulation state */
union {
V8State v8_state;
V21State v21_state;
V23State v23_state;
} u;
/* serial state */
int serial_data_bits; /* 5 to 8 */
int serial_parity, serial_use_parity;
int serial_wordsize;
unsigned int serial_buf;
int serial_cnt;
unsigned int serial_tx_buf;
int serial_tx_cnt;
/* main modem state */
int state;
int debug_laststate;
int hangup_request;
unsigned int time; /* current time (in samples) */
/* config */
struct LinModemConfig *lm_config;
};
/* linmodem configuration registers */
typedef struct LinModemConfig {
int pulse_dial; /* default dial type */
int dtmf_level; /* in dB */
int dtmf_digit_length; /* in ms */
int dtmf_pause_length; /* in ms */
int available_modulations; /* mask of available modulations */
} LinModemConfig;
/* abstract line interface driver */
struct lm_interface_state {
struct sm_state *sm;
int handle;
};
/* modem hardware interface abstraction */
/* XXX: must be slightly modified for kernel mode operation */
struct sm_hw_info {
int (*open)(struct lm_interface_state *s);
void (*close)(struct lm_interface_state *s);
/* off hook or on hook modem */
void (*set_offhook)(struct lm_interface_state *s, int v);
/* when the ring is enabled, an event E_RING is sent to the
process if a ring occured */
void (*set_ring)(struct lm_interface_state *s, int v);
/* the main modem loop is here */
void (*main_loop)(struct lm_interface_state *s);
};
/* general state of the modem */
enum {
#define TAG(s) s,
#include "lmstates.h"
};
void lm_init(struct sm_state *sm, struct sm_hw_info *hw, const char *name);
/* main modem process */
void sm_process(struct sm_state *sm, s16 *output, s16 *input, int nb_samples);
int lm_start_dial(struct sm_state *s, int pulse, const char *number);
int lm_start_receive(struct sm_state *s);
int lm_hangup(struct sm_state *s);
enum lm_get_state_val {
LM_STATE_IDLE,
LM_STATE_CONNECTING,
LM_STATE_CONNECTED,
};
enum lm_get_state_val lm_get_state(struct sm_state *s);
/* lmsim.c */
void line_simulate(void);
struct LineModelState;
struct LineModelState *line_model_init(void);
void line_model(struct LineModelState *s,
s16 *output1, const s16 *input1,
s16 *output2, const s16 *input2,
int nb_samples);
/* lmreal.c */
void real_test(int calling);
/* lmsoundcard.c */
void soundcard_modem(void);
/* serial.c */
void serial_init(struct sm_state *s, int data_bits, int parity);
int serial_get_bit(void *opaque);
void serial_put_bit(void *opaque, int bit);
/* atparser.c */
enum lm_at_state_type {
AT_MODE_COMMAND,
AT_MODE_DIALING,
AT_MODE_CONNECTED,
};
struct lm_at_state {
char at_line[256];
int at_line_ptr;
int at_state;
struct sm_state *sm; /* corresponding modem state */
};
void lm_at_parser_init(struct lm_at_state *s, struct sm_state *sm);
void lm_at_parser(struct lm_at_state *s);
#include "display.h"

95
lmreal.c Normal file
View File

@ -0,0 +1,95 @@
/* real modem: suitable for ltmodem */
/* Copyright 1999 Pavel Machek <pavel@suse.cz>, distribute under GPL v2 */
#include "lm.h"
#include <unistd.h>
#define NB_SAMPLES 0x50
extern struct sm_hw_info sm_hw_null;
void readit(int f, void *buf, int len)
{
int res;
while (len > 0) {
if ((res = read(f, buf, len)) <= 0) {
printf("Read failed: %m");
exit(5);
}
len -= res;
buf += res;
}
}
char *modem_command = "tee /tmp/delme.xmit | ltmodem -u 2> /dev/null | tee /tmp/delme.rec\n",
*dial_number = "31415926";
void real_test(int calling)
{
int fildesin[2];
int fildesout[2];
int i;
struct sm_state sm, *dce = &sm;
s16 in_buf[NB_SAMPLES];
s16 out_buf[NB_SAMPLES];
lm_init(dce, &sm_hw_null, "real");
strcpy(dce->call_num,dial_number);
pipe(fildesin);
pipe(fildesout);
printf( "Starting communication with ltmodem\n" );
fflush(stdout); fflush(stderr);
if (!fork()) {
close(0);
dup(fildesin[0]);
close(1);
dup(fildesout[1]);
close(fildesin[0]);
close(fildesin[1]);
close(fildesout[0]);
close(fildesout[1]);
system( modem_command );
exit(0);
}
/* test call */
if (calling) {
lm_start_dial(dce, 0, dial_number);
} else {
lm_start_receive(dce);
}
close(fildesin[0]);
close(fildesout[1]);
printf( "Kicking modem..." ); fflush(stdout);
bzero(out_buf, 2*NB_SAMPLES);
{
int i;
for (i=0; i<100; i++) {
write(fildesin[1], out_buf, 2*NB_SAMPLES);
}
}
printf( "Modem kicked\n" );
/* answer_dce->state = SM_TEST_RING; */
for(;;) {
if (lm_get_state(dce) == LM_STATE_IDLE)
break;
readit(fildesout[0], in_buf, NB_SAMPLES*2);
sm_process(dce, out_buf, in_buf, NB_SAMPLES);
if ((i = write(fildesin[1], out_buf, NB_SAMPLES*2)) != NB_SAMPLES*2) {
printf( "Error writing -- got short sample (%d,%m)\n",i );
}
}
printf( "Looks like we are done\n" );
}

467
lmsim.c Normal file
View File

@ -0,0 +1,467 @@
/*
* Implementation of the phone line simulator
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
*/
#include "lm.h"
#define NB_SAMPLES 40 /* 5 ms */
#define SAMPLE_RATE 8000
#define SAMPLE_REF 0x4000 /* 0 dB reference (sample level) */
/* 'calling' is used to know in which direction we transmit for echo cancellation */
struct sm_hw_info sm_hw_null;
/* transmit from 'A' to 'B' */
static void tx_rx(struct sm_state *A, struct sm_state *B, int dump)
{
/* transmit data from call_dce */
if (sm_size(&A->tx_fifo) < 10) {
int i;
for(i=0;i<256;i++)
sm_put_bit(&A->tx_fifo, i);
}
/* receive data from answer_dce */
for(;;) {
int c;
c = sm_get_bit(&B->rx_fifo);
if (c == -1)
break;
if (dump) {
printf("[%02x]", c);
fflush(stdout);
}
}
}
void line_simulate(void)
{
struct sm_state sm1, sm2, *call_dce = &sm1, *answer_dce = &sm2;
s16 answer_buf[NB_SAMPLES], call_buf[NB_SAMPLES];
s16 answer_buf1[NB_SAMPLES], call_buf1[NB_SAMPLES];
FILE *f1,*f2;
struct LineModelState *line_state;
int err;
err = lm_display_init();
if (err < 0) {
fprintf(stderr, "Could not init X display\n");
exit(1);
}
line_state = line_model_init();
/* init two modems */
lm_init(call_dce, &sm_hw_null, "cal");
lm_init(answer_dce, &sm_hw_null, "ans");
/* start calls */
lm_start_dial(call_dce, 0, "1234567890");
lm_start_receive(answer_dce);
answer_dce->state = SM_TEST_RING;
f1 = fopen("cal.sw", "wb");
if (f1 == NULL) {
perror("cal.sw");
exit(1);
}
f2 = fopen("ans.sw", "wb");
if (f1 == NULL) {
perror("ans.sw");
exit(1);
}
memset(answer_buf, 0, sizeof(answer_buf));
/* init asynchronous tx & rx */
serial_init(call_dce, 8, 'N');
serial_init(answer_dce, 8, 'N');
for(;;) {
lm_display_poll_event();
/* transmit & receive in both direction & dump call to ans modem */
tx_rx(call_dce, answer_dce, 0);
tx_rx(answer_dce, call_dce, 1);
/* exit connection if ONHOOK state */
if (lm_get_state(call_dce) == LM_STATE_IDLE ||
lm_get_state(answer_dce) == LM_STATE_IDLE)
break;
/* simulate both modem & output the samples to disk */
sm_process(call_dce, call_buf, answer_buf1, NB_SAMPLES);
fwrite(call_buf, 1, NB_SAMPLES * 2, f1);
sm_process(answer_dce, answer_buf, call_buf1, NB_SAMPLES);
fwrite(answer_buf, 1, NB_SAMPLES * 2, f2);
/* simulate the phone line */
line_model(line_state,
call_buf1, call_buf,
answer_buf1, answer_buf, NB_SAMPLES);
}
fclose(f1);
fclose(f2);
for(;;) {
if (lm_display_poll_event())
break;
}
lm_display_close();
}
static int sim_open(struct lm_interface_state *s)
{
return 0;
}
static void sim_close(struct lm_interface_state *s)
{
}
static void sim_set_offhook(struct lm_interface_state *s, int v)
{
if (v) {
printf("%s: offhook\n",s->sm->name);
} else {
printf("%s: onhook\n",s->sm->name);
}
}
static void sim_set_ring(struct lm_interface_state *s, int v)
{
}
static void sim_main_loop(struct lm_interface_state *s)
{
}
struct sm_hw_info sm_hw_null =
{
sim_open,
sim_close,
sim_set_offhook,
sim_set_ring,
sim_main_loop,
};
/* simple phone line model */
#define LINE_FILTER_SIZE 129
#define A 0.99 /* constant for exponential averaging for power estimations */
#define FFT_SIZE 1024
/* state of a uni directional line */
typedef struct {
float buf[LINE_FILTER_SIZE]; /* last transmitted samples (ring buffer,
used by the line filter) */
int buf_ptr; /* pointer of the last transmitted sample in buf */
float tx_pow; /* estimated transmit power */
float rx_pow; /* estimated receive power */
float noise_pow; /* estimated noise power */
} UniDirLineState;
typedef struct LineModelState {
UniDirLineState line1, line2;
float fout1, fout2;
} LineModelState;
static float line_filter[LINE_FILTER_SIZE];
static float sigma; /* gaussian noise sigma */
static int nb_clamped; /* number of overflows */
static float modem_hybrid_echo; /* echo level created by the modem hybrid */
static float cs_hybrid_echo; /* echo level created by the central site hybrid */
#define RANDMAX 0x7fffffff
float random_unif(void)
{
return (float) random() / RANDMAX;
}
float random_gaussian(void)
{
float v1, v2, s , m;
do {
v1 = 2 * random_unif() - 1;
v2 = 2 * random_unif() - 1;
s = v1 * v1 + v2 * v2;
} while (s >= 1);
m = sqrt(-2 * log(s)/s);
return v1 * m;
}
/* tabulated medium range telephone line respond (from p 537, Digital
Communication, John G. Proakis */
/* amp 1.0 -> 2.15, freq = 3000 Hz -> 3.2, by 0.2 increments
delay = 4 ms -> 2.2
*/
float phone_amp[23] = {
0,
0.9,
1.4,
1.8,
2.0,
2.1,
2.3,
2.3,
2.2,
2.1,
2.0,
1.85,
1.75,
1.55,
1.3,
1.1,
0.8,
0.55,
0.25,
0.05,
0.05,
0.05,
0.00,
};
float phone_delay[23] = {
2.2, /* NA */
2.2, /* NA */
2.2,
0.9,
0.5,
0.25,
0.1,
0.05,
0.0,
0.0,
0.0,
0.05,
0.1,
0.2,
0.4,
0.5,
0.9,
1.2,
2.2,
2.2, /* NA */
2.2, /* NA */
2.2, /* NA */
2.2, /* NA */
};
static void build_line_impulse_response(void)
{
float f, f1, a, amp, phase, delay;
int index, i, j;
complex tab[FFT_SIZE];
FILE *outfile;
for(i=0;i<FFT_SIZE/2;i++) {
f = (float) i / FFT_SIZE;
f = f * SAMPLE_RATE;
f1 = f / 3000.0 * 3.2 / 0.2;
a = f1 - floor(f1);
index = (int)floor(f1);
/* linear interpolation */
amp = (1 - a) * phone_amp[index] + a * phone_amp[index+1];
amp = amp / 2.15;
delay = (1 - a) * phone_delay[index] + a * phone_delay[index+1];
delay = delay / 2.2 * 4;
phase = 2 * M_PI * f * delay * 0.001;
#if 0
printf("index=%d a=%0.3f f=%0.0f amp=%0.2f delay=%0.2f %0.1f\n",
index, a, f, amp, delay, phase);
#endif
tab[i].re = amp * cos(phase);
tab[i].im = amp * sin(phase);
}
tab[FFT_SIZE/2].im = 0;
for(i=1;i<FFT_SIZE/2;i++) {
tab[FFT_SIZE - i].re = tab[i].re;
tab[FFT_SIZE - i].im = - tab[i].im;
}
fft_calc(tab, FFT_SIZE, 1);
outfile = fopen("a", "w");
j = FFT_SIZE - (LINE_FILTER_SIZE - 1)/2;
for(i=0;i<LINE_FILTER_SIZE;i++) {
line_filter[i] = tab[j].re;
fprintf(outfile, "%f\n", tab[j].re);
if (++j == FFT_SIZE)
j = 0;
}
fclose(outfile);
}
LineModelState *line_model_init(void)
{
float N0,SNR, p, echo_level;
int i;
LineModelState *s;
s = malloc(sizeof(LineModelState));
memset(s, 0, sizeof(LineModelState));
SNR = 25; /* wanted SNR */
N0 = pow(10,-SNR/10.0);
sigma=sqrt(N0/2) * (float)SAMPLE_REF;
/* echos */
echo_level = -15; /* in dB */
cs_hybrid_echo = pow(10, echo_level/20.0);
modem_hybrid_echo = pow(10, echo_level/20.0);
#if 0
build_line_impulse_response();
#else
/* simple filter */
line_filter[LINE_FILTER_SIZE/2+1] = 0.3;
line_filter[LINE_FILTER_SIZE/2] = 1.0;
line_filter[LINE_FILTER_SIZE/2-1] = 0.3;
#endif
/* normalize the filter to a power of 1.0 */
p = 0;
for(i=0;i<LINE_FILTER_SIZE;i++) {
p += line_filter[i] * line_filter[i];
}
p = sqrt(p);
for(i=0;i<LINE_FILTER_SIZE;i++) line_filter[i] /= p;
#if 0
for(i=0;i<LINE_FILTER_SIZE;i++)
printf("%5d %0.3f\n", i, line_filter[i]);
#endif
return s;
}
float compute_db(float a)
{
return 10.0 * log(a) / log(10.0);
}
static int dump_count;
static float calc_line_filter(UniDirLineState *s,
float v, int calling)
{
float sum, noise;
int j, p;
/* compute tx power */
s->tx_pow = (v * v) * (1.0 - A) + A * s->tx_pow;
/* add the sample in the filter buffer */
p = s->buf_ptr;
s->buf[p] = v;
if (++p == LINE_FILTER_SIZE)
p = 0;
s->buf_ptr = p;
/* apply the filter */
sum = 0;
for(j=0;j<LINE_FILTER_SIZE;j++) {
sum += line_filter[j] * s->buf[p];
if (++p == LINE_FILTER_SIZE)
p = 0;
}
/* add noise */
noise = random_gaussian() * sigma;
sum += noise;
/* (testing only: noise power) */
s->noise_pow = (noise * noise) * (1.0 - A) + A * s->noise_pow;
/* compute rx_power */
s->rx_pow = (sum * sum) * (1.0 - A) + A * s->rx_pow;
/* dump estimations */
if (calling && ++dump_count == 50) {
float ref_db;
dump_count = 0;
ref_db = compute_db(SAMPLE_REF * SAMPLE_REF);
lm_dump_linesim_power(compute_db(s->tx_pow) - ref_db,
compute_db(s->rx_pow) - ref_db,
compute_db(s->noise_pow) - ref_db);
}
return sum;
}
static int clamp(float a)
{
if (a < -32768) {
a = -32768;
nb_clamped++;
} else if (a > 32767) {
a = 32767;
nb_clamped++;
}
return (int)rint(a);
}
/* modem (cal) modem (ans)
*
* input1 -> output1 (line1)
* output2 <- input2 (line2)
*
*/
void line_model(LineModelState *s,
s16 *output1, const s16 *input1,
s16 *output2, const s16 *input2,
int nb_samples)
{
int i;
float in1, in2, out1, out2;
float tmp1, tmp2;
for(i=0;i<nb_samples;i++) {
in1 = input1[i];
in2 = input2[i];
/* echo from cal modem central site hybrid */
tmp1 = in1 + s->fout2 * cs_hybrid_echo;
/* echo from ans modem central site hybrid */
tmp2 = in2 + s->fout1 * cs_hybrid_echo;
/* line filters & noise */
s->fout1 = calc_line_filter(&s->line1, tmp1, 1);
s->fout2 = calc_line_filter(&s->line2, tmp2, 0);
/* echo from ans modem hybrid */
out1 = s->fout1 + in2 * modem_hybrid_echo;
lm_dump_sample(CHANNEL_SAMPLE, out1 / 32768.0);
/* echo from cal modem hybrid */
out2 = s->fout2 + in1 * modem_hybrid_echo;
output1[i] = clamp(out1);
output2[i] = clamp(out2);
}
}

196
lmsoundcard.c Normal file
View File

@ -0,0 +1,196 @@
/* sample interface code to use a linux soundcard */
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/soundcard.h>
#include "lm.h"
/* OSS buffer size */
#define FRAGMENT_BITS 8
#define NB_FRAGMENTS (32768 >> FRAGMENT_BITS)
/* 128 samples : 16 ms, maybe too much ? */
#define NB_SAMPLES ((1 << FRAGMENT_BITS)/2)
/* default pty */
#define PTY_NAME "z0"
char *pty_name = "/dev/pty" PTY_NAME;
char *tty_name = "/dev/tty" PTY_NAME;
extern struct sm_hw_info sm_hw_soundcard;
void soundcard_modem(void)
{
struct sm_state sm1, *dce = &sm1;
struct sm_hw_info *hw = &sm_hw_soundcard;
s16 in_buf[NB_SAMPLES];
s16 out_buf[NB_SAMPLES];
u8 buf[1024];
struct lm_at_state at_parser;
fd_set rfds, wfds;
int hw_handle, tty_handle, max_handle, n;
int out_buf_flushed, i, len;
if (!lm_debug) {
printf("linmodem tty is '%s'\n", tty_name);
tty_handle = open(pty_name, O_RDWR);
if (tty_handle < 0) {
perror(pty_name);
return;
}
} else {
printf("linmodem tty is stdout\n");
tty_handle = 0;
}
fcntl(tty_handle, F_SETFL, O_NONBLOCK);
lm_init(dce, hw, "sc");
lm_at_parser_init(&at_parser, dce);
hw_handle = dce->hw_state->handle;
out_buf_flushed = 1;
/* XXX: non block mode ? */
/* test call */
printf("linmodem started.\n");
for(;;) {
/* sound card & tty handling */
FD_ZERO(&rfds);
FD_SET(hw_handle, &rfds);
FD_SET(tty_handle, &rfds);
FD_ZERO(&wfds);
if (!out_buf_flushed)
FD_SET(hw_handle, &wfds);
if (sm_size(&dce->rx_fifo) > 0)
FD_SET(tty_handle, &wfds);
max_handle = tty_handle;
if (hw_handle > tty_handle)
max_handle = hw_handle;
n = select(max_handle + 1, &rfds, &wfds, NULL, NULL);
if (n > 0) {
/* process at commands */
lm_at_parser(&at_parser);
/* read from tty */
if (FD_ISSET(tty_handle, &rfds)) {
len = read(tty_handle, buf, sizeof(buf));
for(i=0;i<len;i++) {
sm_put_bit(&dce->tx_fifo, buf[i]);
}
}
/* write to tty */
if (FD_ISSET(tty_handle, &wfds)) {
struct sm_fifo *f = &dce->rx_fifo;
int size;
size = f->eptr - f->rptr;
if (size > f->size)
size = f->size;
len = write(tty_handle, f->rptr, size);
if (len > 0) {
f->rptr += len;
f->size -= len;
if (f->rptr == f->eptr)
f->rptr = f->sptr;
}
}
/* we assume that the modem read & write per block of
NB_SAMPLES. It makes no sense the underlying hardware
does something else */
/* read from modem */
if (FD_ISSET(hw_handle, &rfds) && out_buf_flushed) {
len = read(hw_handle, in_buf, NB_SAMPLES * 2);
if (len == NB_SAMPLES * 2) {
/* process the modem samples */
sm_process(dce, out_buf, in_buf, NB_SAMPLES);
out_buf_flushed = 0;
}
}
/* write to modem */
if (FD_ISSET(hw_handle, &wfds)) {
len = write(hw_handle, out_buf, NB_SAMPLES * 2);
if (len == NB_SAMPLES * 2) {
out_buf_flushed = 1;
}
}
}
}
}
static int soundcard_open(struct lm_interface_state *s)
{
int tmp, err;
/* init the sound card to 8000 Hz, Mono, 16 bits */
s->handle = open("/dev/dsp", O_RDWR);
if (s->handle < 0) {
perror("/dev/dsp");
exit(1);
}
/* set the card to duplex */
tmp=0;
err=ioctl(s->handle, SNDCTL_DSP_SETDUPLEX, &tmp);
if (err < 0) {
perror("SNDCTL_DSP_SETDUPLEX");
}
/* buffer size */
tmp=(NB_FRAGMENTS << 16) | FRAGMENT_BITS;
err=ioctl(s->handle, SNDCTL_DSP_SETFRAGMENT, &tmp);
if (err < 0) {
perror("set fragment");
}
tmp=AFMT_S16_LE;
err=ioctl(s->handle,SNDCTL_DSP_SETFMT,&tmp);
if (err < 0) goto error;
/* should be last */
tmp = 8000;
err=ioctl(s->handle,SNDCTL_DSP_SPEED,&tmp);
if (err < 0) goto error;
return 0;
error:
close(s->handle);
return -1;
}
static void soundcard_close(struct lm_interface_state *s)
{
close(s->handle);
}
static void soundcard_set_offhook(struct lm_interface_state *s, int v)
{
if (v) {
printf("%s: offhook\n",s->sm->name);
} else {
printf("%s: onhook\n",s->sm->name);
}
}
static void soundcard_set_ring(struct lm_interface_state *s, int v)
{
}
static void soundcard_main_loop(struct lm_interface_state *s)
{
}
struct sm_hw_info sm_hw_soundcard =
{
soundcard_open,
soundcard_close,
soundcard_set_offhook,
soundcard_set_ring,
soundcard_main_loop,
};

38
lmstates.h Normal file
View File

@ -0,0 +1,38 @@
/* modem states */
/* sm_process */
TAG(SM_IDLE)
TAG(SM_CALL)
TAG(SM_GO_ONHOOK)
TAG(SM_PREDTMF_WAIT)
TAG(SM_DTMF_DIAL)
TAG(SM_DTMF_DIAL_WAIT)
TAG(SM_DTMF_DIAL_WAIT1)
TAG(SM_TEST_RING)
TAG(SM_TEST_RING2)
TAG(SM_RECEIVE)
TAG(SM_V8)
TAG(SM_V21)
TAG(SM_V23)
/* V8_process: call */
TAG(V8_WAIT_1SECOND)
TAG(V8_CI)
TAG(V8_CI_SEND)
TAG(V8_CI_OFF)
TAG(V8_GOT_ANSAM)
TAG(V8_CM_SEND)
TAG(V8_CJ_SEND)
TAG(V8_SIGC)
/* V8_process: answer */
TAG(V8_WAIT)
TAG(V8_CM_WAIT)
TAG(V8_JM_SEND)
TAG(V8_SIGA)
/* V34 phase 2 */
TAG(V34_P2_INFO0_SEND)
#undef TAG

84
serial.c Normal file
View File

@ -0,0 +1,84 @@
/*
* Serial encoder/decoder.
*
* Copyright (c) 2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
*/
#include "lm.h"
/* Init the serial encoder & decoder. 5 <= data_bits <= 8 and parity
can be 'E', 'O', or 'N' */
void serial_init(struct sm_state *s, int data_bits, int parity)
{
s->serial_data_bits = data_bits;
s->serial_use_parity = (parity == 'E' || parity == 'O');
s->serial_parity = (parity == 'O');
s->serial_wordsize = data_bits + 2 + s->serial_use_parity;
/* rx init */
s->serial_buf = 0;
s->serial_cnt = 0;
/* tx init */
s->serial_tx_buf = 0;
s->serial_tx_cnt = 0;
}
/* return a bit from the tx fifo with serial encoding */
int serial_get_bit(void *opaque)
{
struct sm_state *s = opaque;
int data, j, bit, p;
if (s->serial_tx_cnt == 0) {
data = sm_get_bit(&s->tx_fifo);
if (data == -1)
return 1;
s->serial_tx_cnt = s->serial_wordsize;
if (s->serial_use_parity) {
p = s->serial_parity;
for(j=0;j<s->serial_data_bits;j++) p ^= (data >> j) & 1;
s->serial_tx_buf = (data << 2) | (p << 1) | 1;
} else {
s->serial_tx_buf = (data << 1) | 1;
}
}
s->serial_tx_cnt--;
bit = (s->serial_tx_buf >> s->serial_tx_cnt) & 1;
return bit;
}
/* decode a serial stream and put it in rx_fifo. Not optimized */
void serial_put_bit(void *opaque, int bit)
{
struct sm_state *s = opaque;
int mask, p, j, data;
s->serial_buf = (s->serial_buf << 1) | bit;
if (s->serial_cnt >= (s->serial_wordsize-1)) {
mask = 1 | (1 << (s->serial_wordsize-1));
if ((s->serial_buf & mask) == 0x1) {
data = (s->serial_buf & ((1 << s->serial_wordsize) - 1)) >> 1;
if (s->serial_use_parity) {
p = s->serial_parity;
for(j=0;j<=s->serial_data_bits;j++) p ^= (data >> j) & 1;
if (!p)
sm_put_bit(&s->rx_fifo, data >> 1);
} else {
sm_put_bit(&s->rx_fifo, data);
}
s->serial_cnt = 0;
}
} else {
s->serial_cnt++;
}
}

65
v21.c Normal file
View File

@ -0,0 +1,65 @@
/*
* Implementation of the V21 modulation/demodulation
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include "lm.h"
#define SAMPLE_RATE 8000
void V21_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque)
{
if (calling) {
/* channel 1 */
s->f_lo = 1080 + 100;
s->f_hi = 1080 - 100;
} else {
/* channel 2 */
s->f_lo = 1750 + 100;
s->f_hi = 1750 - 100;
}
s->baud_rate = 300;
s->sample_rate = SAMPLE_RATE;
s->get_bit = get_bit;
s->opaque = opaque;
FSK_mod_init(s);
}
void V21_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque)
{
if (!calling) {
/* channel 1 */
s->f_lo = 1080 + 100;
s->f_hi = 1080 - 100;
} else {
/* channel 2 */
s->f_lo = 1750 + 100;
s->f_hi = 1750 - 100;
}
s->baud_rate = 300;
s->sample_rate = SAMPLE_RATE;
s->put_bit = put_bit;
s->opaque = opaque;
FSK_demod_init(s);
}
void V21_init(V21State *s, int calling,
get_bit_func get_bit, put_bit_func put_bit, void *opaque)
{
V21_mod_init(&s->tx, calling, get_bit, opaque);
V21_demod_init(&s->rx, calling, put_bit, opaque);
}
int V21_process(V21State *s, s16 *output, s16 *input, int nb_samples)
{
/* XXX: handle disconnect detection by looking at the power */
FSK_mod(&s->tx, output, nb_samples);
FSK_demod(&s->rx, input, nb_samples);
return 0;
}

13
v21.h Normal file
View File

@ -0,0 +1,13 @@
void V21_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque);
void V21_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque);
typedef struct {
FSK_mod_state tx;
FSK_demod_state rx;
} V21State;
void V21_init(V21State *s, int calling,
get_bit_func get_bit, put_bit_func put_bit, void *opaque);
int V21_process(V21State *sm, s16 *output, s16 *input, int nb_samples);

293
v22.c Normal file
View File

@ -0,0 +1,293 @@
/*
* V22 modulator & demodulator
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include "lm.h"
/*
* This code is also used by the V34 phase 2 at 600 bits/s. V22bis
* 2400 bps is implemented in the modulation but not in the
* demodulation.
*/
void V22_mod_init(V22ModState *s)
{
s->baud_phase = 0;
s->baud_num = 3;
s->baud_denom = 40;
s->tx_filter_wsize = V22_TX_FILTER_SIZE / s->baud_denom;
memset(s->tx_buf, 0, sizeof(s->tx_buf));
s->tx_outbuf_ptr = 0;
s->carrier_phase = 0;
s->carrier2_phase = 0;
s->Z = 0;
if (s->calling) {
/* call modem DPSK: 600 bps, carrier at 1200 Hz, 0 db */
s->carrier_incr = (PHASE_BASE * 1200.0) / V34_SAMPLE_RATE;
} else {
/* answer modem DPSK: 600 bps, carrier at 2400 Hz, -1 db,
guard tone at 1800 Hz, -7db */
s->carrier_incr = (PHASE_BASE * 2400.0) / V34_SAMPLE_RATE;
s->carrier2_incr = (PHASE_BASE * 1800.0) / V34_SAMPLE_RATE;
}
}
static void V22_mod_baseband(V22ModState *s, s16 *x_ptr, s16 *y_ptr)
{
int x, y, x1, y1, b1, b2;
/* handle each kind of modulation */
switch(s->mod_type) {
default:
case V34_MOD_600:
b1 = s->get_bit(s->opaque);
/* rotation by 0 or 180 degrees */
s->Z = s->Z ^ (b1 << 1);
x1 = 0x2000;
y1 = 0x2000;
break;
case V22_MOD_600:
b1 = s->get_bit(s->opaque);
/* rotation by 90 or 270 degrees */
s->Z = (s->Z + ((b1 << 1) | 1)) & 3;
x1 = 0x2000;
y1 = 0x2000;
break;
case V22_MOD_1200:
b1 = s->get_bit(s->opaque);
b2 = s->get_bit(s->opaque);
b2 ^= (1 - b1);
s->Z = (s->Z + ((b1 << 1) | b2)) & 3;
x1 = 0x2000;
y1 = 0x2000;
break;
case V22_MOD_2400:
/* quadrant selection */
b1 = s->get_bit(s->opaque);
b2 = s->get_bit(s->opaque);
b2 ^= (1 - b1);
s->Z = (s->Z + ((b1 << 1) | b2)) & 3;
/* 4 positions inside the quadrant */
b1 = s->get_bit(s->opaque);
b2 = s->get_bit(s->opaque);
/* XXX: normalize */
x1 = 0x1000;
if (b2)
x1 += 0x2000;
y1 = 0x1000;
if (b1)
y1 += 0x2000;
break;
}
/* rotate counter clockwise */
switch(s->Z) {
case 0:
x = x1;
y = y1;
break;
case 1:
x = -y1;
y = x1;
break;
case 2:
x = -x1;
y = -y1;
break;
default:
case 3:
x = y1;
y = -x1;
break;
}
*x_ptr = x;
*y_ptr = y;
}
void V22_mod(V22ModState *s, s16 *samples, unsigned int nb)
{
int i, j, k, val, si, sq, ph;
for(i=0;i<nb;i++) {
/* apply the spectrum shaping filter */
ph = s->baud_phase;
si = sq = 0;
for(j=0;j<s->tx_filter_wsize;j++) {
k = (s->tx_outbuf_ptr - j - 1) &
(V22_TX_BUF_SIZE - 1);
si += s->tx_buf[k][0] * v22_tx_filter[ph];
sq += s->tx_buf[k][1] * v22_tx_filter[ph];
ph += s->baud_denom;
}
si = si >> 14;
sq = sq >> 14;
/* get next baseband symbol ? */
s->baud_phase += s->baud_num;
if (s->baud_phase >= s->baud_denom) {
s->baud_phase -= s->baud_denom;
V22_mod_baseband(s,
&s->tx_buf[s->tx_outbuf_ptr][0],
&s->tx_buf[s->tx_outbuf_ptr][1]);
s->tx_outbuf_ptr = (s->tx_outbuf_ptr + 1) & (V22_TX_BUF_SIZE - 1);
}
val = (si * dsp_cos(s->carrier_phase) -
sq * dsp_cos((PHASE_BASE/4) - s->carrier_phase)) >> COS_BITS;
s->carrier_phase += s->carrier_incr;
if (!s->calling) {
/* a 1800 Hz tone is added for answer modem modulation at 6 dB below it */
val += (dsp_cos(s->carrier2_phase) >> 1);
s->carrier2_phase += s->carrier2_incr;
}
samples[i] = val;
}
}
void V22_demod_init(V22DemodState *s)
{
s->baud_phase = 0;
s->baud_num = 3;
s->baud_denom = 40;
s->carrier_phase = 0;
if (!s->calling) {
/* call modem DPSK: 600 bps, carrier at 1200 Hz, 0 db */
s->carrier_incr = (PHASE_BITS * 1200) / 8000;
} else {
/* answer modem DPSK: 600 bps, carrier at 2400 Hz, -1 db,
guard tone at 1800 Hz, -7db */
s->carrier_incr = (PHASE_BITS * 2400) / 8000;
}
}
void V22_demod(V22DemodState *s, s16 *samples, unsigned int nb)
{
}
/* test for FSK using V21 or V23 */
#define NB_SAMPLES 40
#define MAXDELAY 32
static int tx_bits[MAXDELAY], tx_ptr = 0, rx_ptr = 0;
static int tx_blank = 32;
/* transmit random bits with a sync header (31 ones, 1 zero) */
static int test_get_bit(void *opaque)
{
int bit;
if (tx_blank != 0) {
/* send 1 at the beginning for synchronization */
bit = (tx_blank > 1);
tx_blank--;
} else {
bit = random() % 2;
tx_bits[tx_ptr] = bit;
if (++tx_ptr == MAXDELAY)
tx_ptr = 0;
}
return bit;
}
static int nb_bits = 0, errors = 0, sync_count = 0, got_sync = 0;
static void test_put_bit(void *opaque, int bit)
{
int tbit;
if (!got_sync) {
if (bit) {
sync_count++;
} else {
if (sync_count > 16)
got_sync = 1;
sync_count = 0;
}
} else {
tbit = tx_bits[rx_ptr];
if (++rx_ptr == MAXDELAY)
rx_ptr = 0;
if (bit != tbit) {
errors++;
}
nb_bits++;
}
}
void V22_test(void)
{
V22ModState tx;
V22DemodState rx;
int err, calling;
struct LineModelState *line_state;
s16 buf[NB_SAMPLES];
s16 buf1[NB_SAMPLES];
s16 buf2[NB_SAMPLES];
s16 buf3[NB_SAMPLES];
FILE *f1;
err = lm_display_init();
if (err < 0) {
fprintf(stderr, "Could not init X display\n");
exit(1);
}
line_state = line_model_init();
f1 = fopen("cal.sw", "wb");
if (f1 == NULL) {
perror("cal.sw");
exit(1);
}
calling = 0;
tx.calling = calling;
tx.opaque = NULL;
tx.get_bit = test_get_bit;
tx.mod_type = V34_MOD_600;
V22_mod_init(&tx);
rx.calling = !calling;
rx.opaque = NULL;
rx.put_bit = test_put_bit;
rx.mod_type = tx.mod_type;
V22_demod_init(&rx);
nb_bits = 0;
errors = 0;
for(;;) {
if (lm_display_poll_event())
break;
V22_mod(&tx, buf, NB_SAMPLES);
memset(buf3, 0, sizeof(buf3));
line_model(line_state, buf1, buf, buf2, buf3, NB_SAMPLES);
fwrite(buf, 1, NB_SAMPLES * 2, f1);
V22_demod(&rx, buf1, NB_SAMPLES);
}
fclose(f1);
printf("errors=%d nb_bits=%d Pe=%f\n",
errors, nb_bits, (float) errors / (float)nb_bits);
}

46
v22.h Normal file
View File

@ -0,0 +1,46 @@
enum ModulationType {
V34_MOD_600,
V22_MOD_600,
V22_MOD_1200,
V22_MOD_2400,
};
/* 40 phases (sure too much, but we don't optimize right now) */
#define V22_TX_FILTER_SIZE (20 * 40)
#define V22_TX_BUF_SIZE 64
typedef struct {
/* parameters */
int calling;
enum ModulationType mod_type;
void *opaque;
get_bit_func get_bit;
/* state */
int baud_phase, baud_num, baud_denom;
int carrier_phase, carrier_incr;
int carrier2_phase, carrier2_incr;
int tx_filter_wsize;
s16 tx_buf[V22_TX_BUF_SIZE][2]; /* complex symbols to be sent */
int tx_outbuf_ptr; /* index of the next symbol in tx_buf */
int Z; /* last value transmitted */
} V22ModState;
typedef struct {
/* parameters */
int calling;
enum ModulationType mod_type;
void *opaque;
put_bit_func put_bit;
int baud_phase, baud_num, baud_denom;
int carrier_phase, carrier_incr;
} V22DemodState;
extern s16 v22_tx_filter[V22_TX_FILTER_SIZE];
void V22_mod_init(V22ModState *s);
void V22_mod(V22ModState *s, s16 *samples, unsigned int nb);
void V22_test(void);

72
v23.c Normal file
View File

@ -0,0 +1,72 @@
/*
* generic V23 modulator & demodulator
*
* Copyright (c) 1999 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include <stdlib.h>
#include <stdio.h>
#include "lm.h"
#include "fsk.h"
#define SAMPLE_RATE 8000
void V23_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque)
{
if (calling) {
/* 75 bauds */
s->f_lo = 390;
s->f_hi = 450;
s->baud_rate = 75;
} else {
/* 1200 bauds */
s->f_lo = 1300;
s->f_hi = 2100;
s->baud_rate = 1200;
}
s->sample_rate = SAMPLE_RATE;
s->get_bit = get_bit;
s->opaque = opaque;
FSK_mod_init(s);
}
void V23_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque)
{
if (!calling) {
/* 75 bauds */
s->f_lo = 390;
s->f_hi = 450;
s->baud_rate = 75;
} else {
/* 1200 bauds */
s->f_lo = 1300;
s->f_hi = 2100;
s->baud_rate = 1200;
}
s->sample_rate = SAMPLE_RATE;
s->put_bit = put_bit;
s->opaque = opaque;
FSK_demod_init(s);
}
void V23_init(V23State *s, int calling,
get_bit_func get_bit, put_bit_func put_bit, void *opaque)
{
V23_mod_init(&s->tx, calling, get_bit, opaque);
V23_demod_init(&s->rx, calling, put_bit, opaque);
}
int V23_process(V23State *s, s16 *output, s16 *input, int nb_samples)
{
FSK_mod(&s->tx, output, nb_samples);
FSK_demod(&s->rx, input, nb_samples);
return 0;
}

12
v23.h Normal file
View File

@ -0,0 +1,12 @@
void V23_mod_init(FSK_mod_state *s, int calling, get_bit_func get_bit, void *opaque);
void V23_demod_init(FSK_demod_state *s, int calling, put_bit_func put_bit, void *opaque);
typedef struct {
FSK_mod_state tx;
FSK_demod_state rx;
} V23State;
void V23_init(V23State *s, int calling,
get_bit_func get_bit, put_bit_func put_bit, void *opaque);
int V23_process(V23State *s, s16 *output, s16 *input, int nb_samples);

2311
v34.c Normal file

File diff suppressed because it is too large Load Diff

18
v34.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef V34_H
#define V34_H
#include "v34priv.h"
/* should be called once to init some V34 static tables */
void V34_static_init(void);
struct V34State;
void V34_init(struct V34State *s, int calling);
int V34_process(struct V34State *s, s16 *output, s16 *input, int nb_samples);
/* V34 half duplex test with line simulator */
void V34_test(void);
#endif

506
v34eq.c Normal file
View File

@ -0,0 +1,506 @@
/*
* Implementation of the V34 equalizer
*
* Copyright (c) 2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the V34 specification and by using basic signal processing
* knowledge.
*/
#include "lm.h"
#include "v34priv.h"
//#define DEBUG
#define SQRT3_2 (int)(0.8660254 * 0x4000)
#define CMUL(a,b,c) \
{\
(a).re=((b).re*(c).re-(b).im*(c).im) >> 14;\
(a).im=((b).im*(c).re+(b).re*(c).im) >> 14;\
}
#define SCALE(a,b,shift) \
{\
(a).re=(b).re >> (shift);\
(a).im=(b).im >> (shift);\
}
#define FFT23_SIZE (EQ_FRAC * V34_PP_SIZE)
#define RENORM 256.0
#define FRAC 16384.0
static icomplex fft144_table[FFT23_SIZE];
static s16 fft144_reverse[FFT23_SIZE];
icomplex tabPP[V34_PP_SIZE]; /* PP is used to generate the PP signal */
static icomplex tabPP_fft[V34_PP_SIZE];
/* Compute an fft on an array whose size n = 2^k.3^l. The algorithm is
not the most efficient, but it is simple. Each stage of the fft
normalize the result: it is divided by 2 (for fft2) or 4 (for fft3)
at each stage. For 144, the renormalization is 2^8 */
static void fft23(icomplex *output, icomplex *tab, unsigned int n)
{
unsigned int s, i, j, k;
icomplex *p, *q, *r, *c1_ptr, *c2_ptr;
s = n;
k = 1;
while (s != 1) {
if ((s % 3) == 0) {
/* we handle first the '3' factors */
s /= 3;
for(p = tab;p<tab + n;p+=2 * s) {
c1_ptr = fft144_table;
c2_ptr = fft144_table;
q = p + s;
r = p + 2*s;
for(j=0;j<s;j++) {
icomplex a,b,c;
int tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
int tmp8, tmp9, tmp10, tmp11, tmp12;
SCALE(a, *p, 2);
SCALE(b, *q, 2);
SCALE(c, *r, 2);
/* fft on 3 points */
tmp1 = a.re;
tmp10 = a.im;
tmp2 = b.re;
tmp3 = c.re;
tmp4 = tmp2 + tmp3;
tmp9 = (SQRT3_2 * (tmp3 - tmp2)) >> 14;
tmp6 = b.im;
tmp7 = c.im;
tmp8 = (SQRT3_2 * (tmp6 - tmp7)) >> 14;
tmp11 = tmp6 + tmp7;
p->re = (tmp1 + tmp4);
tmp5 = tmp1 - (tmp4 >> 1);
c.re = (tmp5 - tmp8);
b.re = (tmp5 + tmp8);
p->im = tmp10 + tmp11;
tmp12 = tmp10 - (tmp11 >> 1);
b.im = (tmp9 + tmp12);
c.im = (tmp12 - tmp9);
/* post multiplications */
CMUL(*q, b, *c1_ptr);
CMUL(*r, c, *c2_ptr);
p++;
q++;
r++;
c1_ptr += k;
c2_ptr += 2 * k;
if (c2_ptr >= (fft144_table + n))
c2_ptr -= n;
}
}
k *= 3;
} else {
/* '2' factors */
s /= 2;
for(p=tab;p<tab + n;p += s) {
c1_ptr = fft144_table;
q = p + s;
for(j=0;j<s;j++) {
icomplex a, b;
SCALE(a, *p, 1);
SCALE(b, *q, 1);
/* fft on 2 points */
p->re = (a.re + b.re);
p->im = (a.im + b.im);
b.re = (a.re - b.re);
b.im = (a.im - b.im);
/* post multiplication */
CMUL(*q, b, *c1_ptr);
p++;
q++;
c1_ptr += k;
}
}
k *= 2;
}
}
/* now we reverse the indices : cannot permute in place because
fft_reverse is not involutive */
for(i=0;i<n;i++) {
output[fft144_reverse[i]] = tab[i];
}
}
static s16 cos12[12] = { 16384, 14188, 8192, 0, -8192, -14188,
-16384, -14188, -8192, 0, 8192, 14188 };
static icomplex tabtmp[48];
/* Init some constants for the equalizer. May be moved to v34gen.c if
it is too complicated */
void V34eq_init(void)
{
int i, j, k, n;
float a, carrier;
complex tab1[V34_PP_SIZE], tab2[V34_PP_SIZE];
for(i=0;i<FFT23_SIZE;i++) {
a = - 2 * M_PI * i / (float) FFT23_SIZE;
fft144_table[i].re = (int)(cos(a) * 0x4000);
fft144_table[i].im = (int)(sin(a) * 0x4000);
}
/* reverse table */
for(i=0;i<FFT23_SIZE;i++) {
int base;
j = i;
k = 0;
n = FFT23_SIZE;
while (n != 1) {
if ((n % 2) == 0)
base = 2;
else
base = 3;
k = base * k + (j % base);
j /= base;
n /= base;
}
fft144_reverse[i] = k;
}
/* compute the V34 PP sequence */
for(k=0;k<12;k++) {
for(i=0;i<4;i++) {
j = k * i;
if ((k % 3) == 1)
j += 4;
j = j % 12;
tabPP[4*k+i].re = cos12[j];
tabPP[4*k+i].im = cos12[(15 - j) % 12];
}
}
/* fft of PP sequence */
carrier = 0.0;
for(i=0;i<V34_PP_SIZE;i++) {
icomplex a, b;
#if 0
b.re = (int)(cos(carrier) * 0x4000);
b.im = (int)(sin(carrier) * 0x4000);
CMUL(a, tabPP[i], b);
#else
a = tabPP[i];
#endif
tabtmp[i] = a;
tab1[i].re = a.re / 16384.0 / sqrt(48);
tab1[i].im = a.im / 16384.0 / sqrt(48);
carrier -= 2 * M_PI * 1920.0 / 3200.0;
// carrier -= 2 * M_PI * 1800.0 / 2400.0;
}
slow_fft(tab2, tab1, V34_PP_SIZE, 0);
for(i=0;i<V34_PP_SIZE;i++) {
tabPP_fft[i].re = (int)(tab2[i].re * 0x4000);
tabPP_fft[i].im = (int)(tab2[i].im * 0x4000);
#if 0
printf("%3d: %7.4f %7.4f\n",
i, tab2[i].re, tab2[i].im);
#endif
}
}
/* Fast training of the equalizer based on the PP sequence */
void V34_fast_equalize1(V34DSPState *s, s16 *input)
{
int i,k,j, vmax, v, lshift, renorm;
icomplex tab[FFT23_SIZE], tab1[FFT23_SIZE];
float carrier;
for(i=0;i<FFT23_SIZE;i++) {
tab[i].re = input[i];
tab[i].im = 0;
}
#if 0
/* test: modulate a PP sequence */
{
icomplex a, b;
float carrier, carrier_incr;
int p;
p = 0;
carrier_incr = (2 * M_PI * 0.25);
for(i=0;i<FFT23_SIZE/3;i++) {
a = tabPP[i];
b = tabPP[(i+1) % (FFT23_SIZE/3)];
tab[p].re = a.re;
tab[p].im = a.im;
p++;
tab[p].re = 0;
tab[p].im = 0;
p++;
tab[p].re = 0;
tab[p].im = 0;
p++;
}
}
#endif
#if 0
for(i=0;i<FFT23_SIZE;i++) {
tab[i].re = 0;
tab[i].im = 0;
if (i == 2)
tab[i].re = FRAC;
}
#endif
#ifdef DEBUG
printf("Fast equalizer Input:\n");
for(i=0;i<FFT23_SIZE;i++) {
printf("%3d: %7.4f %7.4f\n",
i, tab[i].re / FRAC, tab[i].im / FRAC);
}
#endif
fft23(tab1, tab, FFT23_SIZE);
/* find best renormalization shift (the fft prefers to have its
inputs as close as 2^14 as possible) */
for(i=0;i<FFT23_SIZE;i++)
tab[i] = tab1[i];
vmax = 0;
for(i=0;i<FFT23_SIZE/2;i++) {
v = abs(tab[i].re);
if (v > vmax)
vmax = v;
v = abs(tab[i].im);
if (v > vmax)
vmax = v;
}
lshift = 0;
while (vmax < 0x4000) {
vmax <<= 1;
lshift++;
}
#if defined(DEBUG) || 1
printf("vmax=%d lshift=%d\n", vmax, lshift);
#endif
for(i=0;i<FFT23_SIZE/2;i++) {
tab[i].re <<= lshift;
tab[i].im <<= lshift;
}
for(i=0;i<24;i++) {
icomplex a, b, c;
int norm, d, e;
c = tabPP_fft[i];
b = tab[i];
norm = b.re * b.re + b.im * b.im;
b = tab[i+ 48];
norm += b.re * b.re + b.im * b.im;
norm = norm >> 14;
if (norm == 0)
norm = 1;
c.re = (c.re << 14) / norm;
c.im = (c.im << 14) / norm;
b.re = tab[i].re;
b.im = - tab[i].im;
CMUL(a, c, b);
tab1[i] = a;
b.re = tab[48 + i].re;
b.im = - tab[48 + i].im;
CMUL(a, c, b);
tab1[48 + i] = a;
}
for(i=24;i<48;i++) {
icomplex a, b, c;
int norm;
c = tabPP_fft[i];
b = tab[i];
norm = b.re * b.re + b.im * b.im;
norm = norm >> 14;
if (norm == 0)
norm = 1;
c.re = (c.re << 14) / norm;
c.im = (c.im << 14) / norm;
b.re = tab[i].re;
b.im = - tab[i].im;
CMUL(a, c, b);
tab1[i] = a;
}
for(i=FFT23_SIZE/2;i<FFT23_SIZE;i++) {
tab1[i].re = 0;
tab1[i].im = 0;
}
for(i=0;i<FFT23_SIZE;i++)
tab[i] = tab1[i];
#ifdef DEBUG
printf("After FFT and division:\n");
for(i=0;i<FFT23_SIZE;i++) {
printf("%3d: %7.4f %7.4f\n",
i, tab[i].re / FRAC, tab[i].im / FRAC);
}
#endif
/* inverse FFT (we assume the size is a multiple of two) */
for(i=1;i<FFT23_SIZE/2;i++) {
icomplex a;
j = FFT23_SIZE - i;
a = tab[i];
tab[i] = tab[j];
tab[j] = a;
}
fft23(tab1, tab, FFT23_SIZE);
/* find the maximum real value & center the equalizer on that value */
vmax = 0;
j = 0;
for(i=0;i<FFT23_SIZE;i++) {
v = abs(tab1[i].re);
if (v > vmax) {
vmax = v;
j = i;
}
}
/* center & renormalize */
renorm = (int) (256.0 / FFT23_SIZE * RENORM * RENORM * 4.0 /
(1 << (14 - lshift)));
#if defined(DEBUG)
printf("Equalizer:\n");
printf("center=%d renorm=%d\n", j, renorm);
#endif
#if 0
k = FFT23_SIZE/2;
while (((j - k) % 3) != 0) {
if (++j == FFT23_SIZE)
j = 0;
}
#else
k = 0;
j = 0;
#endif
for(i=0;i<FFT23_SIZE;i++) {
// s->eq_filter[k][0] = (tab1[j].re * renorm) << 8;
// s->eq_filter[k][1] = (tab1[j].im * renorm) << 8;
if (++k == FFT23_SIZE)
k = 0;
if (++j == FFT23_SIZE)
j = 0;
}
#ifdef DEBUG
for(i=0;i<FFT23_SIZE;i++) {
printf("%3d: %7.4f %7.4f\n",
i,
(float)s->eq_filter[i][0] / (1 << 30),
(float)s->eq_filter[i][1] / (1 << 30));
}
#endif
}
#undef CMUL
#define CMUL(a,b,c) \
{\
(a).re=((b).re*(c).re-(b).im*(c).im);\
(a).im=((b).im*(c).re+(b).re*(c).im);\
}
void V34_fast_equalize(V34DSPState *s, s16 *input)
{
complex tab[144], tab1[144];
complex a, b, c;
float norm, d;
int i;
for(i=0;i<FFT23_SIZE;i++) {
tab1[i].re = input[i];
tab1[i].im = 0;
}
slow_fft(tab, tab1, 144, 0);
for(i=0;i<24;i++) {
c.re = tabPP_fft[i].re / FRAC;
c.im = tabPP_fft[i].im / FRAC;
b = tab[i];
norm = b.re * b.re + b.im * b.im;
b = tab[i+ 48];
norm += b.re * b.re + b.im * b.im;
c.re = (c.re) / norm;
c.im = (c.im) / norm;
b.re = tab[i].re;
b.im = - tab[i].im;
CMUL(a, c, b);
tab1[i] = a;
b.re = tab[48 + i].re;
b.im = - tab[48 + i].im;
CMUL(a, c, b);
tab1[48 + i] = a;
}
for(i=24;i<48;i++) {
c.re = tabPP_fft[i].re / FRAC;
c.im = tabPP_fft[i].im / FRAC;
b = tab[i];
norm = b.re * b.re + b.im * b.im;
c.re = (c.re) / norm;
c.im = (c.im) / norm;
b.re = tab[i].re;
b.im = - tab[i].im;
CMUL(a, c, b);
tab1[i] = a;
}
for(i=FFT23_SIZE/2;i<FFT23_SIZE;i++) {
tab1[i].re = 0;
tab1[i].im = 0;
}
for(i=0;i<FFT23_SIZE;i++) {
printf("%3d: %7.4f %7.4f\n",
i, tab[i].re / FRAC, tab[i].im / FRAC);
}
slow_fft(tab, tab1, 144, 1);
for(i=0;i<FFT23_SIZE;i++) {
s->eq_filter[i][0] = (int)(tab[i].re * FRAC * FRAC / 12) << 16;
s->eq_filter[i][1] = (int)(tab[i].im * FRAC * FRAC / 12) << 16;
}
}

321
v34gen.c Normal file
View File

@ -0,0 +1,321 @@
/*
* V34 constant data generator
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include "dsp.h"
#define V34_SAMPLE_RATE_NUM 10
#define V34_SAMPLE_RATE_DEN 3
#define V34_SAMPLE_RATE ((2400*V34_SAMPLE_RATE_NUM)/V34_SAMPLE_RATE_DEN)
#define V22_TX_FILTER_SIZE (20 * 40)
#define RC_FILTER_SIZE 40
void find_data_rot(int *data_ptr, int *rot_ptr, int x0, int y0)
{
int xx,yy,data,rot,x,y;
/* find the data & rotation */
for(data=0;data<4;data++) {
x = -3 + (data & 1) * 4;
y = -3 + (data >> 1) * 4;
for(rot=0;rot<4;rot++) {
if (x == x0 && y == y0) {
*data_ptr = data;
*rot_ptr = rot;
return;
}
/* rotate by 90 */
xx = y;
yy = -x;
x = xx;
y = yy;
}
}
}
/* 0 : rotation by 180, 1 = rotation by 90 */
int classify(int x[2][2])
{
int x0, y0, x1, y1,d0,d1,r0,r1;
x0 = 2 * x[0][0] - 3;
y0 = 2 * x[0][1] - 3;
x1 = 2 * x[1][0] - 3;
y1 = 2 * x[1][1] - 3;
find_data_rot(&d0,&r0,x0,y0);
find_data_rot(&d1,&r1,x1,y1);
if (((r0 - r1) & 1) == 0)
return 0;
else
return 1;
}
void gen_table(int nb_trans)
{
int Y[5],Yt[5], trans, ss[2][3], xx[2][2];
int res, i, y0;
printf("u8 trellis_trans_%d[256][4] = {\n",
nb_trans);
for(y0=0;y0<2;y0++) {
for(trans=0;trans<nb_trans;trans++) {
printf(" /* trans=%d y0=%d */\n", trans, y0);
Yt[1] = trans & 1;
Yt[2] = (trans >> 1) & 1;
Yt[4] = (trans >> 2) & 1;
Yt[3] = (trans >> 3) & 1;
for(xx[0][0] = 0; xx[0][0] < 4; xx[0][0]++)
for(xx[0][1] = 0; xx[0][1] < 4; xx[0][1]++)
for(xx[1][0] = 0; xx[1][0] < 4; xx[1][0]++)
for(xx[1][1] = 0; xx[1][1] < 4; xx[1][1]++) {
/* (§ 9.6.3.1) find Y vector. We traducted the table into binary expressions */
for(i=0;i<2;i++) {
int x,y,y0,x0,y1,x1;
/* XXX: is it right to suppose that we use figure 9 as a periodic mapping ? */
x = xx[i][0];
y = xx[i][1];
x0 = x & 1;
x1 = ((x & 2) >> 1);
y0 = y & 1;
y1 = ((y & 2) >> 1);
ss[i][2] = x1 ^ y1 ^ y0 ^ x0;
ss[i][1] = y0;
ss[i][0] = y0 ^ x0;
}
/* table 13 traducted into binary operations */
Y[4] = ss[0][2] ^ ss[1][2];
Y[3] = ss[0][1];
Y[2] = ss[0][0];
Y[1] = (ss[0][0] & ~ss[1][0] & 1) ^ ss[0][1] ^ ss[1][1];
res = 0;
switch(nb_trans) {
case 4:
res = (Yt[1] == Y[1] &&
Yt[2] == Y[2]);
break;
case 8:
res = (Yt[1] == Y[1] &&
Yt[2] == Y[2] &&
Yt[4] == Y[4]);
break;
case 16:
res = (Yt[1] == Y[1] &&
Yt[2] == Y[2] &&
Yt[3] == Y[3] &&
Yt[4] == Y[4]);
break;
}
if (res && classify(xx) == y0) {
printf(" { %d, %d, %d, %d },\n",
xx[0][0],
xx[0][1],
xx[1][0],
xx[1][1]);
}
}
}
}
printf("};\n");
}
static u8 S_tab[6][8] = {
/* a, c, d1, e1, d2, e2, J, P */
{ 1, 1, 2, 3, 3, 4, 7, 12, }, /* S=2400 */
{ 8, 7, 3, 5, 2, 3, 8, 12, }, /* S=2743 */
{ 7, 6, 3, 5, 2, 3, 7, 14, }, /* S=2800 */
{ 5, 4, 3, 5, 2, 3, 7, 15, }, /* S=3000 */
{ 4, 3, 4, 7, 3, 5, 7, 16, }, /* S=3200 */
{10, 7, 4, 7, 4, 7, 8, 15, }, /* S=3429 */
};
/* this table depends on the sample rate. We have S=a1/c1 * V34_SAMPLE_RATE */
static u8 baud_tab[6][2] = {
/* a1, c1 */
{ 3, 10 },
{ 12, 35 },
{ 7, 20 },
{ 3, 8 },
{ 2, 5 },
{ 3, 7 },
};
#define FFT_SIZE 2048
/* build a square raised cosine nyquist filter of n coefficients
centered on f0, with coefficients alpha and beta.
*/
void build_sqr_nyquist_filter(float *filter,
float f0, float alpha, float beta, int n)
{
float f, f1, f2, val, tau, norm;
int i,j;
complex tab[FFT_SIZE];
f1 = (1.0 - beta) * alpha;
f2 = (1.0 + beta) * alpha;
tau = 0.5 / alpha;
norm = tau / sqrt(FFT_SIZE);
if (f0 != 0)
norm *= 0.5;
for(i=0;i<=FFT_SIZE/2;i++) {
f = i / (float)FFT_SIZE;
/* center on f0 */
f = fabs(f - f0);
if (f <= f1)
val = 1;
else if (f <= f2) {
val = 0.5 * (1.0 + cos((M_PI * tau / beta) * (f - f1)));
} else {
val = 0;
}
val = sqrt(val);
tab[i].re = val * norm;
tab[i].im = 0;
}
for(i=1;i<FFT_SIZE;i++) tab[FFT_SIZE - i] = tab[i];
fft_calc(tab, FFT_SIZE, 0);
j = FFT_SIZE - ((n-1)/2);
for(i=0;i<n;i++) {
filter[i] = tab[j].re;
if (++j == FFT_SIZE)
j = 0;
}
}
void write_filter(char *name, float *filter, int n)
{
int i;
printf("s16 %s[%d]=\n{\n",
name, n);
for(i=0;i<n;i++) {
printf("%6d, ", (int)(filter[i] * 0x4000));
if ((i % 8) == 7)
printf("\n");
}
printf("\n};\n");
}
int main(int argc, char **argv)
{
int i, j, n;
float filter[FFT_SIZE];
char buf[512];
printf("/* THIS SOURCE CODE IS AUTOMATICALLY GENERATED - DO NOT MODIFY */\n");
printf("/*\n"
" * V34 tables\n"
" * \n"
" * Copyright (c) 1999,2000 Fabrice Bellard.\n"
" *\n"
" * This code is released under the GNU General Public License version\n"
" * 2. Please read the file COPYING to know the exact terms of the\n"
" * license.\n"
" */\n");
printf("#include \"lm.h\"\n"
"#include \"v34priv.h\"\n"
"\n");
/* tables for trellis coded modulation */
gen_table(4);
gen_table(8);
gen_table(16);
/* rx filters which convert the 8000 Hz flow to (symbol_rate * 3)
with a nyquist filter */
for(j=0;j<6;j++) {
float symbol_rate, carrier, alpha, beta, freq;
symbol_rate = 2400.0 *
(float)S_tab[j][0] / (float)S_tab[j][1];
for(i=0;i<2;i++) {
if (i == 1 &&
S_tab[j][2] == S_tab[j][4] &&
S_tab[j][3] == S_tab[j][5])
break;
carrier = symbol_rate *
(float)S_tab[j][2 + 2*i] / (float)S_tab[j][3 + 2*i];
alpha = symbol_rate / (2.0 * symbol_rate * 3.0 * baud_tab[j][1]);
beta = 0.1;
freq = carrier / (symbol_rate * 3.0 * baud_tab[j][1]);
n = RC_FILTER_SIZE * baud_tab[j][1] + 1;
printf("/* S=%d carrier=%d alpha=%f beta=%f f0=%f */\n",
(int)rint(symbol_rate),
(int)rint(carrier),
alpha, beta, freq);
build_sqr_nyquist_filter(filter, freq, alpha, beta, n);
sprintf(buf, "v34_rx_filter_%d_%d",
(int)rint(symbol_rate),
(int)rint(carrier));
write_filter(buf, filter, n);
}
}
/* tx filters */
for(j=0;j<6;j++) {
float alpha, beta;
alpha = 1.0 / (2.0 * baud_tab[j][1]);
beta = 0.1;
n = RC_FILTER_SIZE * baud_tab[j][1] + 1;
build_sqr_nyquist_filter(filter, 0.0, alpha, beta, n);
sprintf(buf, "v34_rc_%d_filter", baud_tab[j][1]);
write_filter(buf, filter, n);
}
/* V22 filters */
/* 600 sym/s for 8000 Hz, beta=0.75 */
build_sqr_nyquist_filter(filter, 0.0, 1.0 / (2.0 * 40.0), 0.75,
V22_TX_FILTER_SIZE);
write_filter("v22_tx_filter", filter, V22_TX_FILTER_SIZE);
return 0;
}

158
v34phase2.c Normal file
View File

@ -0,0 +1,158 @@
/*
* Implementation of the V34 phase 2
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the V34 specification and by using basic signal processing
* knowledge.
*/
#include "lm.h"
#include "v34priv.h"
#define DEBUG
#define INFO_SYNC 0x72
/* send the v34 info0 sequence */
void V34_send_info0(V34State *s, int ack)
{
u8 buf[48], *p, *q;
int crc;
p = buf;
put_bits(&p, 4, 0xf); /* fill bits */
put_bits(&p, 8, INFO_SYNC); /* sync word */
put_bits(&p, 1, 1); /* symbol rate 2743 supported ? */
put_bits(&p, 1, 1); /* symbol rate 2800 supported ? */
put_bits(&p, 1, 1); /* symbol rate 3429 supported ? */
put_bits(&p, 1, 1); /* symbol rate 3000, low carrier supported ? */
put_bits(&p, 1, 1); /* symbol rate 3000, high carrier supported ? */
put_bits(&p, 1, 1); /* symbol rate 3200, low carrier supported ? */
put_bits(&p, 1, 1); /* symbol rate 3200, high carrier supported ? */
put_bits(&p, 1, 1); /* symbol rate 3429 disallowed ? */
put_bits(&p, 1, 1); /* can power reduce ? */
put_bits(&p, 3, 5); /* difference between emit & receive sym rate */
put_bits(&p, 1, 0); /* from CME modem ? */
put_bits(&p, 1, 0); /* reserved by ITU */
put_bits(&p, 2, 0); /* tx clock source: 0=internal */
put_bits(&p, 1, ack); /* ack reception */
/* crc */
crc = calc_crc(buf + 12, p - (buf + 12));
put_bits(&p, 16, crc);
put_bits(&p, 4, 0xf); /* fill bits */
for(q=buf;q<p;q++) {
put_sym(s, buf[i], 0);
}
}
/* send the v34 info1c sequence */
void V34_send_info1c(V34State *s)
{
u8 buf[109], *p;
int crc;
p = buf;
put_bits(&p, 4, 0xf); /* fill bits */
put_bits(&p, 8, INFO_SYNC); /* sync word */
put_bits(&p, 3, 1); /* minimum power reduction (XXX) */
put_bits(&p, 3, 1); /* additional power reduction (XXX) */
put_bits(&p, 7, 0); /* length of MD sequence (35 ms incr) */
for(i=0;i<6;i++) {
/* for each symbol speed (increasing order) */
put_bits(&p, 1, 0); /* use high carrier ? (XXX) */
put_bits(&p, 4, 0); /* pre emphasis filter (XXX) */
put_bits(&p, 4, 12); /* projected data rate (XXX) */
}
put_bits(&p, 10, 0); /* frequency offset (XXX) */
/* crc */
crc = calc_crc(buf + 12, p - (buf + 12));
put_bits(&p, 16, crc);
put_bits(&p, 4, 0xf); /* fill bits */
}
/* send the v34 info1a sequence */
void V34_send_info1a(V34State *s)
{
u8 buf[70], *p;
int crc;
p = buf;
put_bits(&p, 4, 0xf); /* fill bits */
put_bits(&p, 8, INFO_SYNC); /* sync word */
put_bits(&p, 3, 1); /* minimum power reduction (XXX) */
put_bits(&p, 3, 1); /* additional power reduction (XXX) */
put_bits(&p, 7, 0); /* length of MD sequence (35 ms incr) */
put_bits(&p, 1, 0); /* high carrier used (XXX) */
put_bits(&p, 4, 0); /* pre emphasis filter (XXX) */
put_bits(&p, 4, 12); /* proj max data rate (XXX) */
put_bits(&p, 3, 4); /* sym rate ans->cal (XXX : 3200) */
put_bits(&p, 3, 4); /* sym rate cal->ans (XXX : 3200) */
put_bits(&p, 10, 0); /* frequency offset (XXX) */
/* crc */
crc = calc_crc(buf + 12, p - (buf + 12));
put_bits(&p, 16, crc);
put_bits(&p, 4, 0xf); /* fill bits */
}
void V34_send_L1(V34State *s, int rep)
{
static char ph[25] = {
/* 150 */ 1,-1, 1, 1,
/* 750 */ 1, 0, 1, 0,
/*1350 */ 1, 1,-1, 0,
/*1950 */ 1, 1,-1, 0,
/*2550 */ 1,-1, 1,-1,
/*3150 */ -1,-1,-1, 1,
/*3750 */ 1 };
for(n=0;n<rep;n++) {
for(i=0;i<25;i++) {
if (ph[i]) {
put_sym(s, i * 150 + 150, ph[i]);
}
}
}
}
int V34P2_process(V34State *s, s16 *output, s16 *input, int nb_samples)
{
/* modulation */
switch(s->state) {
case V34_P2_INFO0_SEND:
V34_send_info0(
}
/* demodulation */
switch(s->state) {
case V34_P2_INFO0_SEND:
}
}

261
v34priv.h Normal file
View File

@ -0,0 +1,261 @@
#ifndef V34PRIV_H
#define V34PRIV_H
#define MAX_MAPPING_FRAME_SIZE 79
#define M_MAX 18
/* symbol rate */
enum {
V34_S2400,
V34_S2743,
V34_S2800,
V34_S3000,
V34_S3200,
V34_S3429,
};
/* constellation parameters */
#define L_MAX 1664 /* max number of points in the constellation */
#define C_MIN -11
#define C_MAX 11
#define C_MAX_SIZE ((C_MAX-C_MIN+1)*(C_MAX-C_MIN+1))
#define C_RADIUS (2*(C_MAX-C_MIN)+1) /* max coordinate of the constellation */
#define SYNC_PATTERN 0x77FA /* (table 12) synchronisation pattern for J=8 */
#define V34_SAMPLE_RATE_NUM 10
#define V34_SAMPLE_RATE_DEN 3
#define V34_SAMPLE_RATE ((2400*V34_SAMPLE_RATE_NUM)/V34_SAMPLE_RATE_DEN)
/* size of the raised root cosine filter (for both rx & tx) */
#define RC_FILTER_SIZE 40
#define TX_BUF_SIZE (2048)
#define RX_BUF1_SIZE 256
#define RX_BUF2_SIZE 256
/* size of the complex equalizer filter */
#define EQ_FRAC 3
#define EQ_SIZE (52*EQ_FRAC)
#define AGC_WINDOW_SIZE 512 /* must be a power of two, in input samples */
#define TRELLIS_MAX_STATES 64
/* 5 times the constraint length */
#define TRELLIS_LENGTH (6*5)
/* 10 fractional bits for nyquist filters */
#define NQ_BITS 10
#define NQ_BASE (1 << NQ_BITS)
/* state of the signal processing part of the V34 transmitter */
typedef struct V34DSPState {
/* V34 parameters */
int calling; /* true if we are the caller */
int S; /* index for symbol rate */
int expanded_shape; /* true if expanded shape used */
int R; /* transmit rate (in bits/s, including aux channel) */
int conv_nb_states; /* number of states of the convolutional coder */
int use_non_linear;
int use_high_carrier;
s16 h[3][2]; /* precoding coefficients (14 bits fractional part) */
void *opaque;
get_bit_func get_bit;
put_bit_func put_bit;
/* do not modify after this */
int N; /* total number of bits in a data frame */
int W; /* number of aux bits in a data frame (0 = no aux channel) */
int J; /* number of data frame in a super frame */
int P; /* number of mapping frame in a data frame */
int b; /* max length of a mapping frame */
int r; /* counter to know the length of the mapping frame */
int K; /* mapping parameters */
int q;
int L; /* current number of points of the constellation */
int M; /* current number of rings */
int Z_1; /* previous Z value (see § 9.5) */
int mapping_frame; /* number of the mapping frame */
int rcnt, acnt; /* fractional counters to know the number of bits
in a mapping frame */
int half_data_frame_count; /* number of half data frame */
int sync_count; /* counter mod 2P for synchronisation */
s16 x[3][2]; /* 3 most recent samples for precoding (7 bit fractional part) */
int U0;
int conv_reg; /* memory of the convolutional coder */
int scrambler_reg; /* state of the self synchronizing scrambler */
float carrier_freq;
float symbol_rate;
s8 constellation[C_MAX_SIZE][2];
/* precomputed bases for the ring computation */
int g2_tab[8*(M_MAX - 1) + 1];
int g4_tab[8*(M_MAX - 1) + 1];
int g8_tab[8*(M_MAX - 1) + 1];
int z8_tab[8*(M_MAX - 1) + 1];
/* for decoding only */
u16 constellation_to_code[C_RADIUS+1][C_RADIUS+1];
/* for encoding only */
s16 *tx_filter;
s16 tx_buf[TX_BUF_SIZE][2];
int tx_buf_ptr, tx_outbuf_ptr, tx_buf_size;
int tx_filter_wsize;
int baud_num, baud_denom;
int baud_incr;
int baud_phase;
int carrier_phase;
int carrier_incr;
s16 tx_amp; /* amplitude for transmit : each symbol is multiplied
by it (1:8:7) */
int baud3_phase;
s16 *rx_filter;
int rx_filter_wsize;
s16 rx_buf1[RX_BUF1_SIZE];
int rx_buf1_ptr;
/* symbol synchronization */
s16 sync_low_mem[2];
s16 sync_low_coef[2];
s16 sync_high_mem[2];
s16 sync_high_coef[2];
s16 sync_A, sync_B, sync_C;
/* equalizer */
s32 eq_filter[EQ_SIZE][2];
s16 eq_buf[EQ_SIZE];
int eq_buf_ptr;
int eq_shift;
/* AGC */
float agc_mem;
float agc_coef;
int agc_power;
int agc_gain;
/* Viterbi decoder */
/* the previous decoded decision comming to this path. Each
decision Y[5] is coded on one byte */
u8 state_decision[TRELLIS_MAX_STATES][TRELLIS_LENGTH];
u8 state_path[TRELLIS_MAX_STATES][TRELLIS_LENGTH];
s16 state_memory[TRELLIS_LENGTH][4];
u8 u0_memory[TRELLIS_LENGTH];
int state_error[TRELLIS_MAX_STATES];
int state_error1[TRELLIS_MAX_STATES];
int trellis_ptr;
/* decoder synchronization */
int phase_4d; /* index of the current 2d symbol in the 4D symbol
(0 or 1) */
int phase_mse; /* MSE to find if we are synchronized on a 4D symbol */
int phase_mse_cnt;
s16 yy[2][2]; /* current 4D symbol */
s16 rx_mapping_frame[2*4][2];
int rx_mapping_frame_count;
/* rx state */
int sym_count;
/* current V34 protocol state */
int state;
int is_16states; /* 16 states required in the startup sequences */
/* interaction with receiver */
int J_received;
int JP_received;
} V34DSPState;
u8 trellis_trans_4[256][4];
u8 trellis_trans_8[256][4];
u8 trellis_trans_16[256][4];
/* V34 states */
enum {
V34_STARTUP3_S1,
V34_STARTUP3_SINV1,
V34_STARTUP3_S2,
V34_STARTUP3_SINV2,
V34_STARTUP3_PP,
V34_STARTUP3_TRN,
V34_STARTUP3_J,
V34_STARTUP3_JP,
V34_STARTUP3_WAIT_J,
V34_STARTUP4_S,
V34_STARTUP4_WAIT_JP,
V34_STARTUP4_SINV,
V34_STARTUP4_TRN,
V34_STARTUP4_MP,
V34_STARTUP4_MPP,
V34_STARTUP4_E,
V34_DATA,
/* receive only */
V34_STARTUP3_WAIT_S1,
};
void put_bits(u8 **pp, int n, int bits);
int calc_crc(u8 *buf, int size);
void v34_send_info0(V34DSPState *s, int ack);
#define DSPK_TX_FILTER_SIZE 321
extern s16 v34_dpsk_tx_filter[DSPK_TX_FILTER_SIZE];
extern s16 v34_rc_5_filter[];
extern s16 v34_rc_7_filter[];
extern s16 v34_rc_8_filter[];
extern s16 v34_rc_10_filter[];
extern s16 v34_rc_20_filter[];
extern s16 v34_rc_35_filter[];
extern s16 v34_rx_filter_2400_1600[];
extern s16 v34_rx_filter_2400_1800[];
extern s16 v34_rx_filter_2743_1646[];
extern s16 v34_rx_filter_2743_1829[];
extern s16 v34_rx_filter_2800_1680[];
extern s16 v34_rx_filter_2800_1867[];
extern s16 v34_rx_filter_3000_1800[];
extern s16 v34_rx_filter_3000_2000[];
extern s16 v34_rx_filter_3200_1829[];
extern s16 v34_rx_filter_3200_1920[];
extern s16 v34_rx_filter_3429_1959[];
/* v34eq.c */
typedef struct {
s16 re, im;
} icomplex;
#define V34_PP_SIZE 48
extern icomplex tabPP[V34_PP_SIZE];
void V34eq_init(void);
void V34_fast_equalize(V34DSPState *s, s16 *input);
typedef struct V34State {
/* V34 parameters test */
int calling; /* true if we are the caller */
int S; /* index for symbol rate */
int expanded_shape; /* true if expanded shape used */
int R; /* transmit rate (in bits/s, excluding aux channel) */
int conv_nb_states; /* number of states of the convolutional coder */
int use_non_linear;
int use_high_carrier;
int use_aux_channel;
s16 h[3][2]; /* precoding coefficients (14 bits fractional part) */
V34DSPState v34_tx;
V34DSPState v34_rx;
} V34State;
#endif

559
v8.c Normal file
View File

@ -0,0 +1,559 @@
/*
* V8 protocol handler
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the ITU specification and by using basic signal processing
* knowledge.
*/
#include "lm.h"
static void V8_mod_init(V8_mod_state *s)
{
s->sample_rate = V8_SAMPLE_RATE;
/* ANSam tone: 2100 Hz, amplitude modulated at 15 Hz, with phase
reversal every 450 ms */
s->phase = 0;
s->phase_incr = (int) (PHASE_BASE * 2100.0 / s->sample_rate);
s->mod_phase = 0;
s->mod_phase_incr = (int) (PHASE_BASE * 15.0 / s->sample_rate);
s->phase_reverse_samples = (int) (s->sample_rate * 0.450);
s->phase_reverse_left = 0;
/* XXX: incorrect power */
s->amp = (int) (pow(10, s->tone_level / 20.0) * 32768.0);
}
static void V8_mod(V8_mod_state *s, s16 *samples, unsigned int nb)
{
int amp,i;
for(i=0;i<nb;i++) {
/* handle phase reversal every 450 ms */
if (s->phase_reverse_left == 0) {
s->phase_reverse_left = s->phase_reverse_samples;
s->phase += PHASE_BASE / 2;
}
amp = (dsp_cos(s->mod_phase) * (int)(0.2 * COS_BASE)) >> COS_BITS;
amp += COS_BASE; /* between 0.8 and 1.2 */
samples[i] = (amp * dsp_cos(s->phase)) >> COS_BITS;
s->mod_phase += s->mod_phase_incr;
s->phase += s->phase_incr;
}
}
/* Recognize the V8 ANSam tone. Some other tones (in particular V21
tone) may be added later. We compute the DFT for every interesting
frequency and do a threshold with the power. Not the best method,
but easy to implement and quite reliable. */
static void V8_demod_init(V8_demod_state *s)
{
int i;
for(i=0;i<V8_N;i++) {
s->cos_tab[i] = (int) (cos( 2 * M_PI * i / V8_N) * COS_BASE);
s->sin_tab[i] = (int) (sin( 2 * M_PI * i / V8_N) * COS_BASE);
}
s->buf_ptr = 0;
s->v8_ANSam_detected = 0;
}
static void V8_demod(V8_demod_state *s, const s16 *samples, unsigned int nb)
{
int i, p0, p1;
for(i=0;i<nb;i++) {
s->buf[s->buf_ptr++] = samples[i];
if (s->buf_ptr >= V8_N) {
s->buf_ptr = 0;
dsp_sar_tab(s->buf, V8_N, 8);
p0 = dsp_norm2(s->buf, V8_N, 0);
p1 = compute_DFT(s->cos_tab, s->sin_tab, s->buf, DFT_COEF_2100, V8_N);
/* XXX: this test is incorrect (not homogenous) */
if ((p0 > 1000) && (p1 > (5*p0))) {
/* V8 tone recognized */
s->v8_ANSam_detected = 1;
}
}
}
}
/* V8 stream decoding */
static void ci_decode(V8State *s)
{
int data = s->rx_data[0];
if (data == 0x83) {
printf("CI: data call\n");
}
}
/* CM or JM decoding */
static void cm_decode(V8State *s)
{
u8 *p;
int c;
if (s->got_cm)
return;
if (s->cm_count > 0) {
/* we must receive two identical CM sequences */
if (s->cm_count == s->rx_data_ptr &&
!memcmp(s->cm_data, s->rx_data, s->rx_data_ptr)) {
/* got CM !! */
s->got_cm = 1;
/* decode it */
/* XXX: this decoding is sufficient for modulations, but
not exhaustive */
s->decoded_modulations = 0;
p = s->cm_data;
/* zero is used to indicate the end */
s->cm_data[s->cm_count] = 0;
c = *p++;
/* call function */
if ((c & 0xf8) != 0x80)
return;
if (c != V8_CALL_FUNC_DATA)
return;
/* modulation */
c = *p++;
if ((c & 0xf8) != V8_MODN0)
return;
if (c & V8_MODN0_V90)
s->decoded_modulations |= V8_MOD_V90;
if (c & V8_MODN0_V34)
s->decoded_modulations |= V8_MOD_V34;
c = *p++;
if ((c & 0x1c) == V8_EXT) {
/* ignored */
c = *p++;
if ((c & 0x1c) == V8_EXT) {
if (c & V8_MODN2_V23)
s->decoded_modulations |= V8_MOD_V23;
if (c & V8_MODN2_V21)
s->decoded_modulations |= V8_MOD_V21;
/* skip other extensions */
do {
c = *p++;
} while ((c & 0x1c) == V8_EXT);
}
}
return;
}
}
/* save the current CM sequence */
s->cm_count = s->rx_data_ptr;
memcpy(s->cm_data, s->rx_data, s->rx_data_ptr);
}
static void put_bit(void *opaque, int bit)
{
V8State *s = opaque;
int new_state, i;
/* wait ten ones & synchro */
s->bit_sync = ((s->bit_sync << 1) | bit) & ((1 << 20) - 1);
if (s->bit_sync == ((V8_TEN_ONES << 10) | V8_CI_SYNC)) {
new_state = V8_CI_SYNC;
goto data_init;
} else if (s->bit_sync == ((V8_TEN_ONES << 10) | V8_CM_SYNC)) {
new_state = V8_CM_SYNC;
data_init:
/* debug */
if (s->data_state == V8_CI_SYNC) {
printf("CI: ");
} else if (s->data_state == V8_CM_SYNC) {
if (s->calling)
printf("JM: ");
else
printf("CM: ");
}
for(i=0;i<s->rx_data_ptr;i++) printf(" %02x", s->rx_data[i]);
printf("\n");
/* decode previous sequence */
switch(s->data_state) {
case V8_CI_SYNC:
ci_decode(s);
break;
case V8_CM_SYNC:
cm_decode(s);
break;
}
s->data_state = new_state;
s->bit_buf = 0;
s->bit_cnt = 0;
s->rx_data_ptr = 0;
}
/* parse octets with 1 bit start, 1 bit stop */
if (s->data_state) {
s->bit_buf = ((s->bit_buf << 1) | bit) & ((1 << 10) - 1);
s->bit_cnt++;
/* start, stop ? */
if ((s->bit_buf & 0x201) == 0x001 && s->bit_cnt >= 10) {
int data;
/* store the available data */
data = (s->bit_buf >> 1) & 0xff;
printf("got data: %d %02x\n", s->data_state, data);
/* CJ detection */
if (data == 0) {
if (++s->data_zero_count == 3) {
s->got_cj = 1;
printf("got CJ\n");
}
} else {
s->data_zero_count = 0;
}
if (s->rx_data_ptr < (sizeof(s->rx_data)-1)) {
s->rx_data[s->rx_data_ptr++] = data;
}
s->bit_cnt = 0;
}
}
}
static void v8_decode_init(V8State *s)
{
V21_demod_init(&s->v21_rx, s->calling, put_bit, s);
s->data_state = 0;
s->bit_sync = 0;
s->cm_count = 0;
s->got_cm = 0;
s->got_cj = 0;
s->data_zero_count = 0;
s->rx_data_ptr = 0;
}
static int get_bit(void *opaque)
{
V8State *s = opaque;
int bit;
bit = sm_get_bit(&s->tx_fifo);
if (bit < 0)
bit = 1;
return bit;
}
static void v8_put_byte(V8State *s, int data)
{
/* insert start & stop bits */
sm_put_bits(&s->tx_fifo, ((data & 0xff) << 1) | 1, 10);
}
void V8_init(V8State *sm, int calling, int mod_mask)
{
sm->debug_laststate = -1;
sm->calling = calling;
if (sm->calling) {
sm->state = V8_WAIT_1SECOND;
sm_set_timer(&sm->v8_start_timer, 1000);
} else {
/* wait 200 ms */
sm_set_timer(&sm->v8_connect_timer, 200);
sm->state = V8_WAIT;
}
sm_init_fifo(&sm->rx_fifo, sm->rx_buf, sizeof(sm->rx_buf));
sm_init_fifo(&sm->tx_fifo, sm->tx_buf, sizeof(sm->tx_buf));
sm->modulation_mask = mod_mask;
}
/* send CM or JM */
static void cm_send(V8State *s, int mod_mask)
{
int val;
sm_put_bits(&s->tx_fifo, V8_TEN_ONES, 10);
sm_put_bits(&s->tx_fifo, V8_CM_SYNC, 10);
/* data call */
v8_put_byte(s, V8_CALL_FUNC_DATA);
/* supported modulations */
val = V8_MODN0;
if (mod_mask & V8_MOD_V90)
val |= V8_MODN0_V90;
if (mod_mask & V8_MOD_V34)
val |= V8_MODN0_V34;
v8_put_byte(s, val);
v8_put_byte(s, V8_EXT);
val = V8_EXT;
if (mod_mask & V8_MOD_V23)
val |= V8_MODN2_V23;
if (mod_mask & V8_MOD_V21)
val |= V8_MODN2_V21;
v8_put_byte(s, val);
/* for now, no LAPM */
//v8_put_byte(s, V8_DATA_LAPM);
/* We are not on celullar connection. What is that,
anyway? GSM? Don't send this - we don't what it is
for, anyway. */
//v8_put_byte(s, V8_DATA_NOCELULAR);
}
/* selection the modulation according to V8 priority from the bits in 'mask' */
static int select_modulation(int mask)
{
int val;
val = V8_MOD_HANGUP;
/* use modulations in this order */
if (mask & V8_MOD_V21)
val = V8_MOD_V21;
if (mask & V8_MOD_V23)
val = V8_MOD_V23;
if (mask & V8_MOD_V34)
val = V8_MOD_V34;
if (mask & V8_MOD_V90)
val = V8_MOD_V90;
return val;
}
/* V8 protocol handler */
int V8_process(V8State *s, s16 *output, s16 *input, int nb_samples)
{
int ret = 0;
/* modulation part */
switch (s->state) {
case V8_CI_SEND:
case V8_CM_SEND:
case V8_JM_SEND:
case V8_CJ_SEND:
/* modulate with V21 */
FSK_mod(&s->v21_tx, output, nb_samples);
break;
case V8_CM_WAIT:
/* send ANSam modulation */
V8_mod(&s->v8_tx, output, nb_samples);
break;
default:
/* output nothing */
memset(output, 0, nb_samples * sizeof(s16));
break;
}
/* demodulation part */
switch (s->state) {
case V8_CI:
case V8_CI_OFF:
case V8_CI_SEND:
/* detect ANSam */
V8_demod(&s->v8_rx, input, nb_samples);
break;
case V8_CM_WAIT:
case V8_CM_SEND:
case V8_JM_SEND:
/* V21 receive */
FSK_demod(&s->v21_rx, input, nb_samples);
break;
default:
break;
}
/* state machine */
if (lm_debug) {
if (s->state != s->debug_laststate) {
s->debug_laststate = s->state;
printf("%s: V8: state: %s\n",
s->calling ? "cal" : "ans", sm_states_str[s->state]);
}
}
switch(s->state) {
case V8_WAIT_1SECOND:
{
/* wait 1 second before sending the first CI packet */
if (sm_check_timer(&s->v8_start_timer)) {
s->state = V8_CI;
s->v8_ci_count = 0;
V8_demod_init(&s->v8_rx); /* init ANSam detection */
V21_mod_init(&s->v21_tx, 1, get_bit, s);
}
}
break;
/* send the CI packets */
case V8_CI:
{
int i;
/* send 4 CI packets (at least 3 must be sent) */
for(i=0;i<4;i++) {
sm_put_bits(&s->tx_fifo, V8_TEN_ONES, 10);
sm_put_bits(&s->tx_fifo, V8_CI_SYNC, 10);
v8_put_byte(s, V8_CALL_FUNC_DATA);
}
s->state = V8_CI_SEND;
}
break;
case V8_CI_SEND:
{
if (sm_size(&s->tx_fifo) == 0) {
s->state = V8_CI_OFF;
sm_set_timer(&s->v8_ci_timer, 500); /* 0.5 s off */
}
}
break;
case V8_CI_OFF:
{
/* check if an ANSam tone is detected */
if (s->v8_rx.v8_ANSam_detected) {
sm_set_timer(&s->v8_ci_timer, V8_TE);
s->state = V8_GOT_ANSAM;
} else if (sm_check_timer(&s->v8_ci_timer)) {
if (++s->v8_ci_count == V8_MAX_CI_SEQ) {
ret = V8_MOD_HANGUP;
}
else {
s->state = V8_CI;
}
}
}
break;
case V8_GOT_ANSAM:
{
if (sm_check_timer(&s->v8_ci_timer)) {
v8_decode_init(s);
s->state = V8_CM_SEND;
}
}
break;
case V8_CM_SEND:
{
if (s->got_cm) {
/* if JM detected, we send CJ & wait for 75 ms before exiting V8 */
s->selected_mod_mask = s->modulation_mask & s->decoded_modulations;
s->selected_modulation = select_modulation(s->selected_mod_mask);
/* flush tx queue */
sm_flush(&s->tx_fifo);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
/* a few more bytes to fill the time */
v8_put_byte(s, 0);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
v8_put_byte(s, 0);
s->state = V8_CJ_SEND;
} else if (sm_size(&s->tx_fifo) == 0) {
/* send CM */
cm_send(s, s->modulation_mask);
}
}
break;
case V8_CJ_SEND:
/* wait until CJ is sent */
if (sm_size(&s->tx_fifo) == 0) {
sm_set_timer(&s->v8_start_timer, 75);
s->state = V8_SIGC;
}
break;
case V8_SIGC:
if (sm_check_timer(&s->v8_start_timer)) {
/* it's OK, let's start the wanted modulation */
ret = s->selected_modulation;
}
break;
/* V8 answer */
case V8_WAIT:
{
if (sm_check_timer(&s->v8_connect_timer)) {
/* send the ANSam tone */
s->v8_tx.tone_level = -3; /* XXX: fix it */
V8_mod_init(&s->v8_tx);
/* prepare V21 to receive CI or CM */
v8_decode_init(s);
/* wait at most 5 seconds */
sm_set_timer(&s->v8_connect_timer, 5000);
s->state = V8_CM_WAIT;
}
}
break;
case V8_CM_WAIT:
{
if (sm_check_timer(&s->v8_connect_timer)) {
/* timeout */
ret = V8_MOD_HANGUP;
} else {
if (s->got_cm) {
/* stop sending ANSam & send JM */
V21_mod_init(&s->v21_tx, 0, get_bit, s);
/* timeout for JM */
sm_set_timer(&s->v8_connect_timer, 5000);
s->state = V8_JM_SEND;
s->selected_mod_mask = s->modulation_mask & s->decoded_modulations;
s->selected_modulation = select_modulation(s->selected_mod_mask);
}
}
}
break;
case V8_JM_SEND:
{
if (sm_check_timer(&s->v8_connect_timer)) {
/* timeout */
ret = V8_MOD_HANGUP;
} else if (s->got_cj) {
/* stop sending JM & wait 75 ms */
sm_set_timer(&s->v8_connect_timer, 75);
s->state = V8_SIGA;
} else if (sm_size(&s->tx_fifo) == 0) {
/* Send JM */
cm_send(s, s->selected_mod_mask);
}
}
break;
case V8_SIGA:
if (sm_check_timer(&s->v8_connect_timer)) {
ret = s->selected_modulation;
}
break;
}
return ret;
}

107
v8.h Normal file
View File

@ -0,0 +1,107 @@
#define V8_SAMPLE_RATE 8000
/* V8 tone ANSam tone synthesis */
typedef struct {
/* parameters */
int tone_level;
/* internal */
int sample_rate;
int phase, phase_incr;
int mod_phase, mod_phase_incr;
int phase_reverse_samples;
int phase_reverse_left;
int amp;
} V8_mod_state;
/* ANSam tone detection */
/* 25 ms window */
#define V8_N 200
#define DFT_COEF_2100 ((int)((2100.0 / V8_SAMPLE_RATE * V8_N) + 0.5))
typedef struct {
int buf_ptr;
s16 buf[V8_N];
s16 cos_tab[V8_N];
s16 sin_tab[V8_N];
int v8_ANSam_detected; /* true if ANSam detected */
} V8_demod_state;
/* Te period, as in the V8 spec 500 <= Te */
#define V8_TE 800
/* V8 main state */
typedef struct {
int calling; /* true if we are the calling modem */
int state; /* current state of the V8 protocol */
int debug_laststate;
struct sm_timer v8_start_timer;
struct sm_timer v8_ci_timer;
struct sm_timer v8_connect_timer;
int v8_ci_count;
FSK_mod_state v21_tx;
FSK_demod_state v21_rx;
struct sm_fifo rx_fifo;
struct sm_fifo tx_fifo;
u8 rx_buf[256];
u8 tx_buf[256];
V8_mod_state v8_tx;
V8_demod_state v8_rx;
/* available modulations */
int modulation_mask;
/* V8 data parsing */
unsigned int bit_buf;
unsigned int bit_sync;
int bit_cnt;
int data_state; /* indicates the type of synchro */
u8 rx_data[64];
int rx_data_ptr;
/* CM/JM parsing */
u8 cm_data[64];
int cm_count;
int got_cm;
int decoded_modulations; /* modulation mask decoded from CM/JM */
/* CJ parsing */
int got_cj;
int data_zero_count;
int selected_mod_mask; /* selected modulation mask */
int selected_modulation; /* see V8_MOD_xxx */
} V8State;
/* V8 protocol definitions */
#define V8_MAX_CI_SEQ 10 /* maximum number of CI packets sent */
#define V8_TEN_ONES 0x3ff
#define V8_CI_SYNC 0x001
#define V8_CM_SYNC 0x00f
#define V8_CALL_FUNC_DATA 0x83
#define V8_MODN0 0xA0
#define V8_EXT 0x08
#define V8_MODN0_V90 0x04 /* FIXME */
#define V8_MODN0_V34 0x02
#define V8_MODN2_V21 0x01
#define V8_MODN2_V23 0x20
#define V8_DATA_NOCELULAR 0xB0
#define V8_DATA_LAPM 0x54
#define V8_MOD_V90 (1 << 0) /* FIXME */
#define V8_MOD_V34 (1 << 1) /* V34 duplex */
#define V8_MOD_V23 (1 << 10) /* V23 duplex */
#define V8_MOD_V21 (1 << 12) /* V21 duplex */
#define V8_MOD_HANGUP 0x8000 /* indicate hangup */
void V8_init(V8State *sm, int calling, int mod_mask);
int V8_process(V8State *sm, s16 *output, s16 *input, int nb_samples);

798
v90.c Normal file
View File

@ -0,0 +1,798 @@
/*
* Implementation of the V90 modulation/demodulation
*
* Copyright (c) 1999,2000 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the V90 specification and by using basic signal processing
* knowledge.
*/
#include "lm.h"
#include "v90priv.h"
#include "v34priv.h"
#define DEBUG
/* Compute the average power of a given constellation :
nothing complicated here, except that each ucode must be counted
correctly. We took the algorithm of the spec. It could be very
optimized and simplified.
*/
int compute_power(V90EncodeState *s)
{
int m,v,i,j;
u64 p,n,r[6],k[6],a;
n = ((u64)1 << s->K) - 1;
for(i=0;i<6;i++) {
r[i] = n;
k[i] = n % s->M[i];
n = n / s->M[i];
}
p = 0;
a = 1;
for(i=0;i<6;i++) {
m = s->M[i];
for(j=0;j<m;j++) {
if (j < k[i]) {
n = a * (r[i+1] + 1);
} else if (j == k[i]) {
n = ((u64)1 << s->K) - a * (r[i] - r[i+1]);
} else {
n = a * r[i+1];
}
v = s->ucode_to_linear[s->m_to_ucode[i][j]];
p += v * v * n;
}
a = a * m;
}
return p / ((u64) 6 << s->K);
}
static const int sign_op[4] = { 0, 0x55, 0xff, 0xaa };
/* Select the best method for the current shaping frame
We find the the shortest path in a treillis of depth ld
*/
static void select_best_signs(V90EncodeState *s, int sp_frame_size, int pp)
{
int ucode_ptr, depth, i;
int state, lstate, lstate_min;
int t_min, x_min, y_min, v_min, w_min, sg;
int x1, y1, v1, w1, x, y, v, w;
u8 mem_l[2][TREILLIS_MAX_DEPTH+2],
mem_t[2][TREILLIS_MAX_DEPTH+2];
s16 mem_x[2][TREILLIS_MAX_DEPTH+2],
mem_y[2][TREILLIS_MAX_DEPTH+2],
mem_v[2][TREILLIS_MAX_DEPTH+2],
mem_w[2][TREILLIS_MAX_DEPTH+2];
/* save new pp signs */
s->pp[s->ucode_ptr] = pp;
/* Find best path starting at state Q */
ucode_ptr = (s->ucode_ptr - (sp_frame_size * s->ld)) & (UCODE_BUF_SIZE-1);
/* fill the treillis memory */
mem_t[s->Q][0] = s->t;
mem_x[s->Q][0] = s->x;
mem_y[s->Q][0] = s->y;
mem_v[s->Q][0] = s->v;
mem_w[s->Q][0] = 0;
for(depth = 0; depth <= s->ld; depth++) {
for(state = 0; state < 2; state++) {
w_min = 0x7fffffff;
lstate_min = t_min = x_min = y_min = v_min = 0;
/* we select the current state at the beginning of the treillis */
if (depth == 0)
lstate = s->Q;
else
lstate = 0;
do {
/* compute the signs */
sg = mem_t[lstate][depth] ^ s->pp[ucode_ptr];
/* apply the bit invertion method */
sg ^= sign_op[(state << 1) | lstate];
x1 = mem_x[lstate][depth];
y1 = mem_y[lstate][depth];
v1 = mem_v[lstate][depth];
w1 = mem_w[lstate][depth];
/* apply the spectral shaping filter to the frame */
for(i=0;i<sp_frame_size;i++) {
x = s->ucode_to_linear[s->ucode[(ucode_ptr + i) & (UCODE_BUF_SIZE-1)]];
if (((sg >> i) & 1) == 0)
x = -x;
y = x - ((s->b1 * x1 + s->a1 * y1) >> 6);
v = y - ((s->b2 * y1 + s->a2 * v1) >> 6);
w = ((v * v) >> 4) + w1;
x1 = x;
y1 = y;
v1 = v;
w1 = w;
}
/* best transition ? */
if (w1 < w_min) {
t_min = sg;
x_min = x1;
y_min = y1;
v_min = v1;
w_min = w1;
lstate_min = lstate;
}
lstate++;
} while (lstate < 2 && depth != 0);
/* selects the best transition */
mem_t[state][depth+1] = t_min;
mem_x[state][depth+1] = x_min;
mem_y[state][depth+1] = y_min;
mem_v[state][depth+1] = v_min;
mem_w[state][depth+1] = w_min;
mem_l[state][depth+1] = lstate_min;
}
ucode_ptr = (ucode_ptr + sp_frame_size) & (UCODE_BUF_SIZE-1);
}
/* now select the best next state by recursing thru the treillis */
if (mem_w[1][s->ld + 1] < mem_w[0][s->ld + 1])
state = 1;
else
state = 0;
for(depth=s->ld;depth>0;depth--)
state = mem_l[state][depth];
s->Q = state;
/* update current values */
s->t = mem_t[state][1];
s->x = mem_x[state][1];
s->y = mem_y[state][1];
s->v = mem_v[state][1];
s->ucode_ptr = (s->ucode_ptr + sp_frame_size) & (UCODE_BUF_SIZE - 1);
}
/*
* converts (S+K) data bits to 6 a/u law values (note: we output
* linear values which may be converted back to u/a law). A delay of
* (ld * frame_size) is introduced due to the shaping treillis.
*/
static void v90_encode_mapping_frame(V90EncodeState *s, s16 *samples, u8 *data)
{
int k, l, i, j, frame_size, nb_frames, p, signs;
u64 v;
int pv[3];
/* modulo mapping to ucodes */
v = 0;
for(i=0;i<s->K;i++) v |= (data[i+s->S] << i);
for(i=0;i<6;i++) {
k = v % s->M[i];
v /= s->M[i];
/* compute the corresponding ucode */
s->ucode[(s->ucode_ptr + i) & (UCODE_BUF_SIZE - 1)] = s->m_to_ucode[i][k];
}
/* computation of the sign of the ucodes */
switch(s->S) {
case 6:
default:
/* no redundancy & spectral shaping */
l = s->last_sign;
signs = 0;
for(i=0;i<6;i++) {
l = data[i] ^ l;
signs |= (l << i);
}
s->last_sign = l;
s->ucode_ptr = (s->ucode_ptr + 6) & (UCODE_BUF_SIZE - 1);
frame_size = 6;
goto skip_spectral_shaping;
case 5:
/* 1 bit of redundancy */
{
int pp1, pp3, pp5;
pp1 = data[0] ^ s->last_sign;
pp3 = data[2] ^ pp1;
pp5 = data[4] ^ pp3;
s->last_sign = pp5;
pv[0] = (pp1 << 1) | (data[1] << 2) | (pp3 << 3) |
(data[3] << 4) | (pp5 << 5);
frame_size = 6;
}
break;
case 4:
/* 2 bits of redundancy */
{
int pp1, pp4;
pp1 = data[0] ^ s->last_sign;
pp4 = data[2] ^ pp1;
s->last_sign = pp4;
pv[0] = (pp1 << 1) | (data[1] << 2);
pv[1] = (pp4 << 1) | (data[3] << 2);
frame_size = 3;
}
break;
case 3:
/* 3 bits of redundancy */
{
int pp1, pp3, pp5;
pp1 = data[0] ^ s->last_sign;
pp3 = data[1] ^ pp1;
pp5 = data[2] ^ pp3;
s->last_sign = pp5;
pv[0] = (pp1 << 1);
pv[1] = (pp3 << 1);
pv[2] = (pp5 << 1);
frame_size = 2;
}
break;
}
/* select the best signs for each frame (with a delay of ld) */
nb_frames = 6-s->S;
signs = 0;
for(i=0,j=0; i<nb_frames; i++, j+=frame_size) {
select_best_signs(s, frame_size, pv[i]);
l = s->t & ((1 << frame_size) - 1);
signs |= (l << j);
}
skip_spectral_shaping:
/* now the signs are computed, we can compute the pcm values from
the ucodes. It may be faster to use the convertion already done
for spectral shaping */
p = (s->ucode_ptr - 6 - (frame_size * s->ld)) & (UCODE_BUF_SIZE - 1);
for(i=0;i<6;i++) {
int x;
x = s->ucode_to_linear[s->ucode[p]];
if (((signs >> i) & 1) == 0)
x = -x;
samples[i] = x;
p = (p + 1) & (UCODE_BUF_SIZE - 1);
}
}
/*
* converts 6 a/u law samples to (S+K) data bits
*/
static void v90_decode_mapping_frame(V90DecodeState *s, u8 *data, s16 *samples)
{
s16 *tab;
int d1, d2, i, j, l, val, v, m_min, m_max, m;
u8 signs[6];
u64 bitbuf;
/* find signs & ucode */
bitbuf = 0;
for(j=5;j>=0;j--) {
val = samples[j];
if (val < 0) {
val = - val;
signs[j] = 0;
} else {
signs[j] = 1;
}
/* We look for the value closest to val with a binary search:
see Knuth section 6.2 exercice 1. */
tab = &s->m_to_linear[j][0];
m_min = 0;
m_max = s->M[j] - 1;
while (m_min <= m_max) {
m = (m_min + m_max) >> 1;
v = tab[m];
if (v == val)
goto found;
else if (val > v) {
m_max = m - 1;
} else {
m_min = m + 1;
}
}
d1 = tab[m_max] - val;
d2 = val - tab[m_min];
if (d1 < d2)
m = m_max;
else
m = m_min;
found:
/* now we update the modulo value */
bitbuf = bitbuf * s->M[j] + m;
}
/* output the K bits */
for(i=0;i<s->K;i++)
data[i + s->S] = (bitbuf >> i) & 1;
switch(s->S) {
default:
case 6:
/* no redundant bit */
l = s->last_sign;
for(i=0;i<6;i++) {
data[i] = l ^ signs[i];
l = signs[i];
}
s->last_sign = l;
break;
case 5:
/* 1 redundant bit */
{
int pp1, pp3, pp5, t0, pv0, Q1;
t0 = 0;
for(i=0;i<6;i++)
t0 |= (signs[i] << i);
pv0 = s->t ^ t0;
Q1 = (pv0 & 1) ^ s->Q;
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 0x3f;
s->t = t0;
s->Q = Q1;
/* extract the data bits */
data[1] = (pv0 >> 2) & 1;
data[3] = (pv0 >> 4) & 1;
pp1 = ((pv0 >> 1) & 1);
pp3 = ((pv0 >> 3) & 1);
pp5 = ((pv0 >> 5) & 1);
data[0] = pp1 ^ s->last_sign;
data[2] = pp1 ^ pp3;
data[4] = pp3 ^ pp5;
s->last_sign = pp5;
}
break;
case 4:
/* 2 redundant bits */
{
int pp1, pp4, t0, t1, pv0, pv1, Q1, Q2;
t0 = signs[0] | (signs[1] << 1) | (signs[2] << 2);
t1 = signs[3] | (signs[4] << 1) | (signs[5] << 2);
pv0 = s->t ^ t0;
Q1 = (pv0 & 1) ^ s->Q;
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 7;
pv1 = t0 ^ t1;
Q2 = (pv1 & 1) ^ Q1;
pv1 ^= sign_op[Q1 | (Q2 << 1)] & 7;
s->t = t1;
s->Q = Q2;
data[1] = (pv0 >> 2) & 1;
data[3] = (pv1 >> 2) & 1;
pp1 = (pv0 >> 1) & 1;
pp4 = (pv1 >> 1) & 1;
data[0] = pp1 ^ s->last_sign;
data[2] = pp4 ^ pp1;
s->last_sign = pp4;
}
break;
case 3:
/* 3 redundant bits */
{
int pp1, pp3, pp5, t0, t1, t2, pv0, pv1, pv2, Q1, Q2, Q3;
t0 = signs[0] | (signs[1] << 1);
t1 = signs[2] | (signs[3] << 1);
t2 = signs[4] | (signs[5] << 1);
pv0 = s->t ^ t0;
Q1 = (pv0 & 1) ^ s->Q;
pv0 ^= sign_op[s->Q | (Q1 << 1)] & 3;
pv1 = t0 ^ t1;
Q2 = (pv1 & 1) ^ Q1;
pv1 ^= sign_op[Q1 | (Q2 << 1)] & 3;
pv2 = t1 ^ t2;
Q3 = (pv2 & 1) ^ Q2;
pv2 ^= sign_op[Q2 | (Q3 << 1)] & 3;
s->t = t2;
s->Q = Q3;
pp1 = (pv0 >> 1) & 1;
pp3 = (pv1 >> 1) & 1;
pp5 = (pv2 >> 1) & 1;
data[0] = pp1 ^ s->last_sign;
data[1] = pp3 ^ pp1;
data[2] = pp5 ^ pp3;
s->last_sign = pp5;
}
break;
}
}
static void compute_constellation(V90EncodeState *s,
u8 m_index[6], u8 ucode_used[6][128])
{
int i,j,k,m;
/* the ucode are taken from the bigger value down to the smaller value */
for(j=0;j<6;j++) {
k = m_index[j];
m = 0;
for(i=127;i>=0;i--) {
if (ucode_used[k][i]) {
s->m_to_ucode[j][m] = i;
m++;
}
}
s->M[j] = m;
}
#ifdef DEBUG
for(j=0;j<6;j++) {
printf("M[%d]: ", j);
for(i=0;i<s->M[j];i++) {
printf("%3d ", s->m_to_ucode[j][i]);
}
printf("\n");
}
#endif
}
void v90_encode_init(V90EncodeState *s)
{
}
void v90_decode_init(V90DecodeState *s)
{
int i,j,m;
/* some test values (You can modify them to test the
modulator/demodulator) */
for(i=0;i<6;i++) {
for(j=0;j<16;j++)
s->ucode_used[i][10 + j * 6] = 1;
}
s->K = 4 * 6;
s->S = 5;
s->alaw = 1;
s->ld = 2;
s->a1 = (int) (0.1 * 64);
s->a2 = (int) (0.2 * 64);
s->b1 = (int) (-0.1 * 64);
s->b2 = (int) (0.1 * 64);
/* we suppose that all other values are set to zero */
/* compute the decode tables */
if (s->alaw)
s->ucode_to_linear = v90_alaw_ucode_to_linear;
else
s->ucode_to_linear = v90_ulaw_ucode_to_linear;
for(j=0;j<6;j++) {
m = 0;
for(i=127;i>=0;i--) {
if (s->ucode_used[j][i]) {
s->m_to_linear[j][m] = s->ucode_to_linear[i];
m++;
}
}
s->M[j] = m;
}
}
static u8 test_buf[1000];
/* send a CP frame (analog modem) */
static void v90_send_CP(V90DecodeState *s, int is_CP, int ack)
{
u8 *buf, *p;
int i, crc, drn;
buf = test_buf;
p = buf;
put_bits(&p, 17, 0x1ffff); /* frame sync */
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 1, 0); /* reserved */
put_bits(&p, 1, is_CP); /* 0=CPt 1=CP frame */
drn = s->K + s->S;
if (is_CP)
drn -= 20;
else
drn -= 8;
put_bits(&p, 5, drn); /* drn: speed */
put_bits(&p, 5, 0); /* reserved */
put_bits(&p, 1, 0); /* 1 if silence asked */
put_bits(&p, 2, 6 - s->S); /* Sr */
put_bits(&p, 1, ack); /* ack */
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 1, s->alaw); /* u/a law selection */
for(i=0;i<13;i++) {
put_bits(&p, 1, 1); /* speed (i+2) * 2400 supported (V34 part) */
}
put_bits(&p, 2, s->ld);
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 16, 0); /* RMS value for TRN1d */
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 8, s->a1 & 0xff); /* spectral shaping parameters */
put_bits(&p, 8, s->a2 & 0xff); /* spectral shaping parameters */
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 8, s->b1 & 0xff); /* spectral shaping parameters */
put_bits(&p, 8, s->b2 & 0xff); /* spectral shaping parameters */
put_bits(&p, 1, 0); /* start bit */
for(i=0;i<6;i++) {
if (i == 4)
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 4, 0); /* modulation index */
}
put_bits(&p, 1, 0); /* different different tx - D/A constellations ? */
put_bits(&p, 7, 0); /* reserved */
/* transmit the constellation */
for(i=0;i<128;i++) {
if ((i & 15) == 0)
put_bits(&p, 1, 0); /* start bit */
put_bits(&p, 1, s->ucode_used[0][i]);
}
/* CRC */
put_bits(&p, 1, 0); /* start bit */
crc = calc_crc(buf + 17, p - (buf+17));
put_bits(&p, 16, crc);
put_bits(&p, 3, 0); /* fill */
printf("CP size= %d\n", p - buf);
}
static int get_bit(u8 **pp)
{
u8 *p;
int v;
p = *pp;
v = *p++;
*pp = p;
return v;
}
static int get_bits(u8 *p, int n)
{
int i, v;
v = 0;
for(i=n-1;i>=0;i--) {
v |= *p++ << i;
}
return v;
}
/* parse the CP packet & compute the V90 parameters */
static void v90_parse_CP(V90EncodeState *s, u8 *buf)
{
u8 m_index[6];
u8 ucode_used[7][128];
int drn, i, j, k, nb_constellations;
/* now the whole packet is can be read */
s->S = 6 - get_bits(buf + 31, 2);
s->alaw = get_bits(buf + 35, 1);
s->ld = get_bits(buf + 49, 2);
s->a1 = (s8) get_bits(buf + 69, 8);
s->a2 = (s8) get_bits(buf + 77, 8);
s->b1 = (s8) get_bits(buf + 86, 8);
s->b2 = (s8) get_bits(buf + 94, 8);
for(i=0;i<4;i++)
m_index[i] = get_bits(buf + 103 + i * 4, 4);
for(i=0;i<2;i++)
m_index[4 + i] = get_bits(buf + 120 + i * 4, 4);
nb_constellations = 0;
for(i=0;i<6;i++) {
if (m_index[i] > nb_constellations) nb_constellations = m_index[i];
}
nb_constellations++;
if (buf[128])
nb_constellations++;
for(i=0;i<nb_constellations;i++) {
for(j=0;j<128;j++) {
k = i * 128 + j;
ucode_used[i][j] = buf[137 + (17 * (k >> 4)) + (k & 15)];
}
}
/* compute K */
drn = get_bits(buf + 20, 5);
if (buf[19])
drn += 20;
else
drn += 8;
s->K = drn - s->S;
if (s->alaw)
s->ucode_to_linear = v90_alaw_ucode_to_linear;
else
s->ucode_to_linear = v90_ulaw_ucode_to_linear;
printf("V90_received_CP:\n");
compute_constellation(s, m_index, ucode_used);
printf("S=%d K=%d R=%d alaw=%d ld=%d a1=%d a2=%d b1=%d b2=%d\n",
s->S, s->K, ((s->S + s->K) * 8000) / 6,
s->alaw, s->ld, s->a1, s->a2, s->b1, s->b2);
}
/* received & parse the CP packet */
static void v90_receive_CP(V90EncodeState *s)
{
u8 buf[1024], *p, *q;
int b, i, frame_index, one_count, frame_count, nb_constellations;
int crc1;
u8 m_index[6];
p = test_buf;
wait_sync:
one_count = 0;
while (get_bit(&p)) {
one_count++;
}
if (one_count != 17)
goto wait_sync;
#ifdef DEBUG
printf("got CP sync\n");
#endif
frame_index = 0;
frame_count = 8;
crc1 = 1;
q = buf + 17;
while (frame_index < frame_count) {
printf("%2d: ", frame_index);
*q++ = 0;
for(i=0;i<16;i++) {
b = get_bit(&p);
*q++ = b;
printf("%d", b);
}
printf("\n");
if (frame_index == 6) {
/* compute the number of constellation to read */
for(i=0;i<4;i++) {
m_index[i] = get_bits(buf + 103 + i * 4, 4);
}
for(i=0;i<2;i++) {
m_index[4 + i] = get_bits(buf + 120 + i * 4, 4);
}
nb_constellations = 0;
for(i=0;i<6;i++) {
if (m_index[i] > nb_constellations) nb_constellations = m_index[i];
}
nb_constellations++;
if (buf[128])
nb_constellations++;
frame_count += 8 * nb_constellations;
}
if (frame_index == (frame_count - 1)) {
/* check the crc (it must be zero because we include the crc itself) */
crc1 = calc_crc(buf + 17, q - (buf+17));
}
if (get_bit(&p) != 0) {
printf("start bit expected\n");
goto wait_sync;
}
frame_index++;
}
if (crc1 != 0)
goto wait_sync;
v90_parse_CP(s, buf);
}
/* simple test of V90 algebraic computations */
/* Note: if ld != 0 and 3 <= S <= 4, the delay introduced with data[][]
is not correct */
void V90_test(void)
{
int i,j,n,l;
V90EncodeState v90_enc;
V90DecodeState v90_dec;
u8 data[5][48], data1[48];
s16 samples[6];
/* init modem state */
memset(&v90_enc, 0, sizeof(v90_enc));
v90_encode_init(&v90_enc);
memset(&v90_dec, 0, sizeof(v90_dec));
v90_decode_init(&v90_dec);
/* send the CP sequence which contains the modulation parameters */
v90_send_CP(&v90_dec, 1, 0);
/* "receive" it ! */
v90_receive_CP(&v90_enc);
/* number of data bits per mapping frame */
n = v90_enc.S + v90_enc.K;
/* transmit & receive 1000 mapping frames */
memset(data, 0, sizeof(data));
l = 0;
for(i=0;i<1000;i++) {
for(j=0;j<n;j++) data[l][j] = random() & 1;
v90_encode_mapping_frame(&v90_enc, samples, data[l]);
// for(j=0;j<6;j++) samples[j] += (random() % 32) - 16;
printf("%4d: ", i);
for(j=0;j<6;j++) printf("%6d,", samples[j]);
printf("\n");
v90_decode_mapping_frame(&v90_dec, data1, samples);
l = (l + 1) % (v90_dec.ld+1);
for(j=0;j<n;j++) {
if (data[l][j] != data1[j]) {
printf("error mapping frame=%d bit=%d\n", i, j);
}
}
}
}

11
v90.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef V90_H
#define V90_H
typedef struct {
int dummy;
} V90_params;
void V90_test(void);
#endif

110
v90gen.c Normal file
View File

@ -0,0 +1,110 @@
/*
* V90 table generator
*
* Copyright (c) 1999 Fabrice Bellard.
*
* This code is released under the GNU General Public License version
* 2. Please read the file COPYING to know the exact terms of the
* license.
*
* This implementation is totally clean room. It was written by
* reading the V90 specification and by using basic signal processing
* knowledge.
*/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
/* from g711.c by SUN microsystems (unrestricted use) */
#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
#define QUANT_MASK (0xf) /* Quantization field mask. */
#define NSEGS (8) /* Number of A-law segments. */
#define SEG_SHIFT (4) /* Left shift for segment number. */
#define SEG_MASK (0x70) /* Segment field mask. */
#define BIAS (0x84) /* Bias for linear code. */
/*
* alaw2linear() - Convert an A-law value to 16-bit linear PCM
*
*/
int
alaw2linear(a_val)
unsigned char a_val;
{
int t;
int seg;
a_val ^= 0x55;
t = (a_val & QUANT_MASK) << 4;
seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
switch (seg) {
case 0:
t += 8;
break;
case 1:
t += 0x108;
break;
default:
t += 0x108;
t <<= seg - 1;
}
return ((a_val & SIGN_BIT) ? t : -t);
}
int
ulaw2linear(u_val)
unsigned char u_val;
{
int t;
/* Complement to obtain normal u-law value. */
u_val = ~u_val;
/*
* Extract and bias the quantization bits. Then
* shift up by the segment number and subtract out the bias.
*/
t = ((u_val & QUANT_MASK) << 3) + BIAS;
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}
int main(int argc, char **argv)
{
int i;
printf("/* THIS SOURCE CODE IS AUTOMATICALLY GENERATED - DO NOT MODIFY */\n");
printf("/*\n"
" * V90 tables\n"
" * \n"
" * Copyright (c) 1999 Fabrice Bellard.\n"
" *\n"
" * This code is released under the GNU General Public License version\n"
" * 2. Please read the file COPYING to know the exact terms of the\n"
" * license.\n"
" */\n");
printf("#include \"lm.h\"\n"
"#include \"v90priv.h\"\n"
"\n");
printf("const s16 v90_ulaw_ucode_to_linear[128]= {\n");
for(i=0;i<128;i++) {
printf("%5d,", ulaw2linear(i ^ 0xff));
if ((i & 7) == 7) printf("\n");
}
printf("};\n");
printf("const s16 v90_alaw_ucode_to_linear[128]= {\n");
for(i=0;i<128;i++) {
printf("%5d,", alaw2linear(i ^ 0xd5));
if ((i & 7) == 7) printf("\n");
}
printf("};\n");
return 0;
}

65
v90priv.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef V90PRIV_H
#define V90PRIV_H
#define V90_SAMPLE_RATE 8000
#define TREILLIS_MAX_DEPTH 4
/* ring buffer size of the previous ucodes : must be a power of two */
#define UCODE_BUF_SIZE (TREILLIS_MAX_DEPTH * 8)
/* state of the signal processing part of the V90 modem */
typedef struct V90EncodeState {
struct sm_state *sm;
int alaw; /* true=alaw, false=ulaw */
/* frame parameters (a frame = 6 PAM values) */
int S; /* sign bits (3 <= S <= 6) */
int K; /* number of bits for the ring coder */
int a1, a2, b1, b2; /* spectral conformer parameters */
int M[6]; /* ring size */
u8 m_to_ucode[6][128]; /* encoding table */
int ld; /* depth for spectral shaping (0 <= ld <= 3) */
/* sign mapper */
int ucode_ptr; /* index of the first pam value of the
current mapping frame */
u8 ucode[UCODE_BUF_SIZE]; /* current output of the modulator */
u8 pp[UCODE_BUF_SIZE]; /* corresponding signs (grouped by frame) */
int last_sign; /* sign of the last computed frame */
/* spectral shaping treillis */
u8 t; /* signs of the frame */
s16 x; /* last x value: not stricly needed, but simply the treillis computation */
s16 y; /* memory for spectral shaping filter */
s16 v; /* idem */
s16 w; /* idem */
int Q; /* current shaping treillis state
(with a delay of ld) */
const s16 *ucode_to_linear; /* table to retrieve the linear values from ucodes */
} V90EncodeState;
const s16 v90_ulaw_ucode_to_linear[128];
const s16 v90_alaw_ucode_to_linear[128];
typedef struct V90DecodeState {
struct sm_state *sm;
int alaw; /* true=alaw, false=ulaw */
/* frame parameters (a frame = 6 PAM values) */
int S; /* sign bits (3 <= S <= 6) */
int K; /* number of bits for the ring coder */
int M[6]; /* ring size */
u8 ucode_used[6][128];
s16 m_to_linear[6][128]; /* give the estimated linear value of each received modulo */
int ld;
s8 a1, a2, b1, b2;
int last_sign;
int t;
int Q;
const s16 *ucode_to_linear; /* table to retrieve the linear values from ucodes */
} V90DecodeState;
#endif