mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-02-26 20:33:53 +01:00
first commit
This commit is contained in:
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
build/
|
||||||
5
.vscode/.server-controller-port.log
vendored
Normal file
5
.vscode/.server-controller-port.log
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"port": 13452,
|
||||||
|
"time": 1728395259887,
|
||||||
|
"version": "0.0.3"
|
||||||
|
}
|
||||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.errorSquiggles": "disabled",
|
||||||
|
"files.associations": {
|
||||||
|
"ascii_cmd.h": "c",
|
||||||
|
"time.h": "c",
|
||||||
|
"rds.h": "c"
|
||||||
|
}
|
||||||
|
}
|
||||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. 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
|
||||||
|
them 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 prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. 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.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey 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;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If 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 convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU 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 that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
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.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
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
|
||||||
|
state 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) <year> <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 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program 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, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU 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 Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
125
README.md
Normal file
125
README.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# MiniRDS
|
||||||
|
|
||||||
|
This program is designed for generating a realtime RDS signal. It is capable of RDS2 using up to 3 additional subcarriers.
|
||||||
|
|
||||||
|
This is based on the RDS encoder from [Mpxgen](https://github.com/Anthony96922/mpxgen), which is currently not maintained.
|
||||||
|
|
||||||
|
Note that some missing groups were added from [librds](https://flerken.zapto.org:1115/kuba/librds), [my](https://flerken.zapto.org:1115/kuba) other project
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Low resource requirements
|
||||||
|
- Support for basic RDS data fields: PS, RT, PTY and AF
|
||||||
|
- RDS items can be updated through control pipe
|
||||||
|
- RT+ support
|
||||||
|
- RDS2 support (including station logo transmission)
|
||||||
|
|
||||||
|
#### To do
|
||||||
|
- Threading
|
||||||
|
|
||||||
|
#### Planned features
|
||||||
|
- UECP
|
||||||
|
- Configuration file
|
||||||
|
|
||||||
|
## What are the changes from normal MiniRDS?
|
||||||
|
- CMake
|
||||||
|
- better arguments
|
||||||
|
- Option to disable ODA
|
||||||
|
- Better Group Sequence (original had 0A 2A but here it is 0A 0A 0A 0A 2A 2A 2A 2A (2A) so PS appear instantly and RT is fast)
|
||||||
|
- Added LIC and PIN
|
||||||
|
- Most commands are compatible with the ASCII format that pira.cz uses
|
||||||
|
- Custom groups (use `G=BBBBCCCCDDDD` or `CG AAAA BBBB CCCC DDDD`)
|
||||||
|
- Mono! mono? Mono! Not stereo! why? because the 2 channels were exactly the same in the first place so there was no point in having a 2nd one
|
||||||
|
|
||||||
|
## Build
|
||||||
|
For Debian-like distros: `sudo apt-get install libao-dev libsamplerate0-dev` to install deps.
|
||||||
|
|
||||||
|
Note that MiniRDS requires CMake to compile (install with `sudo apt install cmake`)
|
||||||
|
|
||||||
|
Once those are installed, run
|
||||||
|
```sh
|
||||||
|
git clone https://flerken.zapto.org:1115/kuba/minirds
|
||||||
|
cd minirds/src
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
Simply run:
|
||||||
|
```
|
||||||
|
minirds
|
||||||
|
```
|
||||||
|
to confirm proper operation.
|
||||||
|
|
||||||
|
Please see `-h` for more options.
|
||||||
|
|
||||||
|
### Stereo Tool integration
|
||||||
|
The following setup allows MiniRDS to be used alongside Stereo Tool audio processor.
|
||||||
|
```
|
||||||
|
.-------------.
|
||||||
|
| Stereo Tool |--(FM MPX w/o RDS)-----.
|
||||||
|
'-------------' |
|
||||||
|
v
|
||||||
|
.-----------------------.
|
||||||
|
| ALSA dmixer |
|
||||||
|
| slave: MPX sound card |--------(to sound card)-------->
|
||||||
|
| 192 kHz |
|
||||||
|
'-----------------------'
|
||||||
|
^
|
||||||
|
.---------. |
|
||||||
|
| MiniRDS |--(RDS output)-------------'
|
||||||
|
'---------'
|
||||||
|
```
|
||||||
|
|
||||||
|
First, add the following contents to ~/.asoundrc:
|
||||||
|
```
|
||||||
|
# ST MPX output
|
||||||
|
pcm.mpxmix {
|
||||||
|
type dmix
|
||||||
|
ipc_key 1001
|
||||||
|
slave {
|
||||||
|
pcm "digital-out" # change to your actual sound card
|
||||||
|
rate 192000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, add the collowing contents to ~/.ao:
|
||||||
|
```
|
||||||
|
dev=mpxmix
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set the Stereo Tool MPX output to use the ALSA "mpxmix" output. Finally run minirds. *Adjust volumes accordingly.*
|
||||||
|
|
||||||
|
Note that this setup is not optimal. Hans plans to add RDS2 passthough to the ST external RDS input. [Stereo Tool forum post](https://forums.stereotool.com/viewtopic.php?f=14&t=33793&start=150)
|
||||||
|
|
||||||
|
If you need to you also can run MiniRDS on a ST transmitter modulated as mono audio, which does work however on some TXs the RDS signal may not be great due to harmonics of ST Pilot Tone
|
||||||
|
|
||||||
|
### Changing PS, RT, TA and PTY at run-time
|
||||||
|
You can control PS, RT, TA (Traffic Announcement flag), PTY (Program Type) and many other items at run-time using a named pipe (FIFO). For this run MiniRDS with the `--ctl` argument.
|
||||||
|
|
||||||
|
Scripts can be written to obtain and send "now playing" text data to MiniRDS for dynamic RDS.
|
||||||
|
|
||||||
|
See the [command list](doc/command_list.md) for a complete list of valid commands.
|
||||||
|
|
||||||
|
### RDS2
|
||||||
|
MiniRDS has a working implementation of the RFT protocol in RDS2. Please edit the Makefile accordingly and rebuild for RDS2 capabilities. You may use your own image by using the provided "make-station-logo.sh" script. Valid formats are PNG or JPG and should be about 3kB or less. Larger images take considerably longer to receive.
|
||||||
|
|
||||||
|
## References
|
||||||
|
- [EN 50067, Specification of the radio data system (RDS) for VHF/FM sound broadcasting in the frequency range 87.5 to 108.0 MHz](http://www.interactive-radio-system.com/docs/EN50067_RDS_Standard.pdf)
|
||||||
|
- [IEC 62106-2, Radio data system (RDS) – Part 2: Message format: coding and definitions of RDS features](http://downloads.dxing.si/download.php?file=ISO%20Stamdards/RDS/latest%20(includes%20RDS2)/iec-62106-2-2021.pdf)
|
||||||
|
- [IEC 62106-3, Radio data system (RDS) – Part 3: Usage and registration of Open Data Applications (ODAs)](http://downloads.dxing.si/download.php?file=ISO%20Stamdards/RDS/latest%20(includes%20RDS2)/iec-62106-3-2018.pdf)
|
||||||
|
- [IEC 62106-4, Radio data system (RDS) – Part 4: Registered code tables](http://downloads.dxing.si/download.php?file=ISO%20Stamdards/RDS/latest%20(includes%20RDS2)/iec-62106-4-2018.pdf)
|
||||||
|
- [IEC 62106-6, Radio data system (RDS) – Part 6: Compilation of technical specifications for Open Data Applications in the
|
||||||
|
public domain](http://downloads.dxing.si/download.php?file=ISO%20Stamdards/RDS/latest%20(includes%20RDS2)/iec-62106-6-2018.pdf)
|
||||||
|
- [P232 RDS Encoder
|
||||||
|
Technical Manual](https://pira.cz/rds/p232man.pdf)
|
||||||
|
- [PIRA32 RDS Encoder
|
||||||
|
Technical Manual](https://pira.cz/rds/manual.pdf)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
Note that this is a fork of [Anthony96922](https://github.com/Anthony96922)'s [MiniRDS](https://github.com/Anthony96922/MiniRDS)
|
||||||
|
|
||||||
|
The RDS waveform generator was adapted from [PiFmRds](https://github.com/ChristopheJacquet/PiFmRds)
|
||||||
25
doc/command_list.md
Normal file
25
doc/command_list.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
## MiniRDS command list
|
||||||
|
|
||||||
|
Most commands are in the ASCII format, for RT you can use `TEXT` or `RT1`, however some comamnds werent yet transitioned to the new command syntax, and here they are:
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
#### `MPX`
|
||||||
|
Set volumes in percent modulation for individual MPX subcarrier signals.
|
||||||
|
|
||||||
|
`MPX 0,9,9,9,9`
|
||||||
|
|
||||||
|
Carriers: (first to last)
|
||||||
|
Pilot tone
|
||||||
|
RDS 1
|
||||||
|
RDS 2 (67 khz)
|
||||||
|
RDS 2 (71 khz)
|
||||||
|
RDS 2 (76 khz)
|
||||||
|
|
||||||
|
#### `VOL`
|
||||||
|
Set the output volume in percent.
|
||||||
|
|
||||||
|
`VOL 100`
|
||||||
|
|
||||||
|
## Other
|
||||||
|
See src/supported_cmds.txt
|
||||||
BIN
doc/mpxgen.jpg
Normal file
BIN
doc/mpxgen.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
82
src/CMakeLists.txt
Normal file
82
src/CMakeLists.txt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# This is from kuba's version of MiniRDS
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
# Project name and version
|
||||||
|
project(minirds VERSION 1.0)
|
||||||
|
|
||||||
|
# Define options
|
||||||
|
option(ODA_RTP "Enable ODA (RT+)" ON)
|
||||||
|
|
||||||
|
option(RDS2 "Enable RDS2 capabilities" OFF)
|
||||||
|
option(RDS2_QUADRATURE_CARRIER "Shift RDS2 stream carriers by 90, 180, and 270 degrees" ON)
|
||||||
|
option(RDS2_SYMBOL_SHIFTING "Enable RDS2 symbol shifting" ON)
|
||||||
|
option(RDS2_DEBUG "Enable RDS2 debugging" OFF)
|
||||||
|
|
||||||
|
option(STATIC_LIBSAMPLERATE "Use a static libsamplerate library (.a)" OFF)
|
||||||
|
|
||||||
|
|
||||||
|
set(LIBSAMPLERATE_DIR "./libsamplerate" CACHE STRING "Directory for static libsamplerate")
|
||||||
|
|
||||||
|
# Set compiler and flags
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wextra -pedantic -O2 -std=c18 -DVERSION=\"${PROJECT_VERSION}\"")
|
||||||
|
|
||||||
|
# Define sources
|
||||||
|
set(SOURCES
|
||||||
|
minirds.c
|
||||||
|
waveforms.c
|
||||||
|
rds.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle RDS2 options
|
||||||
|
if(RDS2)
|
||||||
|
add_definitions(-DRDS2)
|
||||||
|
set(SOURCES ${SOURCES} rds2.c)
|
||||||
|
if(RDS2_QUADRATURE_CARRIER)
|
||||||
|
add_definitions(-DRDS2_QUADRATURE_CARRIER)
|
||||||
|
endif()
|
||||||
|
if(RDS2_SYMBOL_SHIFTING)
|
||||||
|
add_definitions(-DRDS2_SYMBOL_SHIFTING)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(RDS2_DEBUG)
|
||||||
|
add_definitions(-DRDS2_DEBUG)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ODA_RTP)
|
||||||
|
add_definitions(-DODA)
|
||||||
|
add_definitions(-DODA_RTP)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
${SOURCES}
|
||||||
|
fm_mpx.c
|
||||||
|
control_pipe.c
|
||||||
|
osc.c
|
||||||
|
resampler.c
|
||||||
|
modulator.c
|
||||||
|
lib.c
|
||||||
|
ascii_cmd.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the executable
|
||||||
|
add_executable(minirds ${SOURCES})
|
||||||
|
|
||||||
|
# Handle libsamplerate linkage
|
||||||
|
if(STATIC_LIBSAMPLERATE)
|
||||||
|
target_include_directories(minirds PRIVATE ${LIBSAMPLERATE_DIR}/include)
|
||||||
|
target_link_libraries(minirds PRIVATE ${LIBSAMPLERATE_DIR}/lib/libsamplerate.a)
|
||||||
|
else()
|
||||||
|
find_library(SAMPLERATE_LIBRARY samplerate)
|
||||||
|
target_link_libraries(minirds PRIVATE ${SAMPLERATE_LIBRARY})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link additional libraries
|
||||||
|
target_link_libraries(minirds PRIVATE m pthread pulse pulse-simple)
|
||||||
|
|
||||||
|
# Install target
|
||||||
|
install(TARGETS minirds DESTINATION /usr/local/bin)
|
||||||
|
# Add profiling flags globally
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
|
||||||
395
src/ascii_cmd.c
Normal file
395
src/ascii_cmd.c
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
/*
|
||||||
|
* MiniRDS - FM RDS Encoder
|
||||||
|
* contains code from:
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "rds.h"
|
||||||
|
#include "fm_mpx.h"
|
||||||
|
#include "lib.h"
|
||||||
|
#include "ascii_cmd.h"
|
||||||
|
|
||||||
|
#define CMD_MATCHES(a) (ustrcmp(cmd, (unsigned char *)a) == 0)
|
||||||
|
|
||||||
|
void process_ascii_cmd(unsigned char *str) {
|
||||||
|
unsigned char *cmd, *arg;
|
||||||
|
uint16_t cmd_len = 0;
|
||||||
|
|
||||||
|
cmd_len = _strnlen((const char*)str, CTL_BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (cmd_len > 3 && str[2] == ' ') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[2] = 0;
|
||||||
|
arg = str + 3;
|
||||||
|
|
||||||
|
if (CMD_MATCHES("CG")) {
|
||||||
|
/* this stays */
|
||||||
|
uint16_t blocks[4];
|
||||||
|
int count = sscanf((const char*)arg, "%hX %hX %hX %hX",
|
||||||
|
&blocks[0], &blocks[1],
|
||||||
|
&blocks[2], &blocks[3]);
|
||||||
|
if (count == 4) {
|
||||||
|
set_rds_cg(blocks);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_len > 4 && str[3] == ' ') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[3] = 0;
|
||||||
|
arg = str + 4;
|
||||||
|
|
||||||
|
if (CMD_MATCHES("MPX")) {
|
||||||
|
#ifdef RDS2
|
||||||
|
float gains[5];
|
||||||
|
if (sscanf((char *)arg, "%f,%f,%f,%f,%f",
|
||||||
|
&gains[0], &gains[1], &gains[2], &gains[3],
|
||||||
|
&gains[4]) == 5) {
|
||||||
|
set_carrier_volume(0, gains[0]);
|
||||||
|
set_carrier_volume(1, gains[1]);
|
||||||
|
set_carrier_volume(2, gains[2]);
|
||||||
|
set_carrier_volume(3, gains[3]);
|
||||||
|
set_carrier_volume(4, gains[4]);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
float gains[2];
|
||||||
|
if (sscanf((char *)arg, "%f,%f",
|
||||||
|
&gains[0], &gains[1]) == 2)
|
||||||
|
{
|
||||||
|
set_carrier_volume(0, gains[0]);
|
||||||
|
set_carrier_volume(1, gains[1]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("VOL")) {
|
||||||
|
arg[4] = 0;
|
||||||
|
set_output_volume(strtof((char *)arg, NULL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmd_len == 5) {
|
||||||
|
cmd = str;
|
||||||
|
if(CMD_MATCHES("AFCH=")) {
|
||||||
|
clear_rds_af();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd_len > 5 && str[4] == '=') {
|
||||||
|
/* compatibilty with existing (standarts)*/
|
||||||
|
cmd = str;
|
||||||
|
cmd[4] = 0;
|
||||||
|
arg = str + 5;
|
||||||
|
if (CMD_MATCHES("TEXT")) {
|
||||||
|
arg[RT_LENGTH * 2] = 0;
|
||||||
|
set_rds_rt1(xlat(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("PTYN")) {
|
||||||
|
arg[PTYN_LENGTH] = 0;
|
||||||
|
set_rds_ptyn(xlat(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("AFCH")) {
|
||||||
|
if(arg[0] == 'A' || arg[0] == 'B') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clear_rds_af();
|
||||||
|
uint8_t arg_count;
|
||||||
|
rds_af_t new_af;
|
||||||
|
uint8_t af[MAX_AFS], *af_iter;
|
||||||
|
arg_count = sscanf((char *)arg,
|
||||||
|
"%hhx,%hhx,%hhx,%hhx,%hhx," /* AF list */
|
||||||
|
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||||
|
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||||
|
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||||
|
"%hhx,%hhx,%hhx,%hhx,%hhx",
|
||||||
|
&af[0], &af[1], &af[2], &af[3], &af[4],
|
||||||
|
&af[5], &af[6], &af[7], &af[8], &af[9],
|
||||||
|
&af[10], &af[11], &af[12], &af[13], &af[14],
|
||||||
|
&af[15], &af[16], &af[17], &af[18], &af[19],
|
||||||
|
&af[20], &af[21], &af[22], &af[23], &af[24]);
|
||||||
|
|
||||||
|
af_iter = af;
|
||||||
|
memset(&new_af, 0, sizeof(struct rds_af_t));
|
||||||
|
while (arg_count-- != 0) {
|
||||||
|
uint8_t current_value = *af_iter;
|
||||||
|
float frequency = (875.0 + current_value) / 10.0;
|
||||||
|
add_rds_af(&new_af, frequency);
|
||||||
|
af_iter++;
|
||||||
|
}
|
||||||
|
set_rds_af(new_af);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmd_len == 4) {
|
||||||
|
cmd = str;
|
||||||
|
if(CMD_MATCHES("TPS=")) {
|
||||||
|
set_rds_tpson(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(CMD_MATCHES("LPS=")) {
|
||||||
|
set_rds_lpson(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd_len > 4 && str[3] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[3] = 0;
|
||||||
|
arg = str + 4;
|
||||||
|
if (CMD_MATCHES("TPS")) {
|
||||||
|
arg[PS_LENGTH * 2] = 0;
|
||||||
|
set_rds_tps(xlat(arg));
|
||||||
|
set_rds_tpson(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("RT1")) {
|
||||||
|
arg[RT_LENGTH * 2] = 0;
|
||||||
|
set_rds_rt1(xlat(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("PTY")) {
|
||||||
|
arg[2] = 0;
|
||||||
|
set_rds_pty(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("ECC")) {
|
||||||
|
arg[2] = 0;
|
||||||
|
set_rds_ecc(strtoul((char *)arg, NULL, 16));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("LIC")) {
|
||||||
|
arg[2] = 0;
|
||||||
|
set_rds_lic(strtoul((char *)arg, NULL, 16));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
if (CMD_MATCHES("RTP")) {
|
||||||
|
char tag_names[2][32];
|
||||||
|
uint8_t tags[6];
|
||||||
|
if (sscanf((char *)arg, "%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",
|
||||||
|
&tags[0], &tags[1], &tags[2], &tags[3],
|
||||||
|
&tags[4], &tags[5]) == 6) {
|
||||||
|
set_rds_rtplus_tags(tags);
|
||||||
|
} else if (sscanf((char *)arg, "%31[^,],%hhu,%hhu,%31[^,],%hhu,%hhu",
|
||||||
|
tag_names[0], &tags[1], &tags[2],
|
||||||
|
tag_names[1], &tags[4], &tags[5]) == 6) {
|
||||||
|
tags[0] = get_rtp_tag_id(tag_names[0]);
|
||||||
|
tags[3] = get_rtp_tag_id(tag_names[1]);
|
||||||
|
set_rds_rtplus_tags(tags);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (CMD_MATCHES("LPS")) {
|
||||||
|
arg[LPS_LENGTH] = 0;
|
||||||
|
set_rds_lpson(1);
|
||||||
|
set_rds_lps(arg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CMD_MATCHES("PIN")) {
|
||||||
|
uint8_t pin[3];
|
||||||
|
if (sscanf((char *)arg, "%hhu,%hhu,%hhu",
|
||||||
|
&pin[0], &pin[1], &pin[2]) == 3) {
|
||||||
|
set_rds_pin(pin[0], pin[1], pin[2]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(cmd_len == 3) {
|
||||||
|
cmd = str;
|
||||||
|
if(CMD_MATCHES("AF=")) {
|
||||||
|
clear_rds_af();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd_len > 3 && str[2] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[2] = 0;
|
||||||
|
arg = str + 3;
|
||||||
|
|
||||||
|
if (CMD_MATCHES("PS")) {
|
||||||
|
if(arg[0] == '\0') arg[0] = ' '; /* fix for strings that start with a space idk why but tps works fine with space started strings */
|
||||||
|
arg[PS_LENGTH * 2] = 0;
|
||||||
|
set_rds_ps(xlat(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("CT")) {
|
||||||
|
set_rds_ct(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("DI")) {
|
||||||
|
arg[2] = 0;
|
||||||
|
set_rds_di(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("TP")) {
|
||||||
|
set_rds_tp(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("TA")) {
|
||||||
|
set_rds_ta(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("MS")) {
|
||||||
|
set_rds_ms(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CMD_MATCHES("PI")) {
|
||||||
|
arg[4] = 0;
|
||||||
|
set_rds_pi(strtoul((char *)arg, NULL, 16));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CMD_MATCHES("AF")) {
|
||||||
|
if(arg[0] == 'A' || arg[0] == 'B') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clear_rds_af();
|
||||||
|
uint8_t arg_count;
|
||||||
|
rds_af_t new_af;
|
||||||
|
float af[MAX_AFS], *af_iter;
|
||||||
|
arg_count = sscanf((char *)arg,
|
||||||
|
"%f,%f,%f,%f,%f," /* AF list */
|
||||||
|
"%f,%f,%f,%f,%f,"
|
||||||
|
"%f,%f,%f,%f,%f,"
|
||||||
|
"%f,%f,%f,%f,%f,"
|
||||||
|
"%f,%f,%f,%f,%f",
|
||||||
|
&af[0], &af[1], &af[2], &af[3], &af[4],
|
||||||
|
&af[5], &af[6], &af[7], &af[8], &af[9],
|
||||||
|
&af[10], &af[11], &af[12], &af[13], &af[14],
|
||||||
|
&af[15], &af[16], &af[17], &af[18], &af[19],
|
||||||
|
&af[20], &af[21], &af[22], &af[23], &af[24]);
|
||||||
|
af_iter = af;
|
||||||
|
memset(&new_af, 0, sizeof(struct rds_af_t));
|
||||||
|
while (arg_count-- != 0) {
|
||||||
|
add_rds_af(&new_af, *af_iter++);
|
||||||
|
}
|
||||||
|
set_rds_af(new_af);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_len > 2 && str[1] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[1] = 0;
|
||||||
|
arg = str + 2;
|
||||||
|
if (CMD_MATCHES("G")) {
|
||||||
|
uint16_t blocks[4];
|
||||||
|
if(cmd_len == 18){
|
||||||
|
/* RDS2 Group*/
|
||||||
|
/* do a ifdef rds2 here when implementing*/
|
||||||
|
} else if(cmd_len == 14) {
|
||||||
|
/* RDS1 Group*/
|
||||||
|
blocks[0] = get_rds_pi();
|
||||||
|
int count = sscanf((char *)arg, "%4hx%4hx%4hx", &blocks[1], &blocks[2], &blocks[3]);
|
||||||
|
if(count == 3) {
|
||||||
|
set_rds_cg(blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd_len > 6 && str[5] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[5] = 0;
|
||||||
|
arg = str + 6;
|
||||||
|
|
||||||
|
if (CMD_MATCHES("PINEN")) {
|
||||||
|
arg[1] = 0;
|
||||||
|
set_rds_pin_enabled(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CMD_MATCHES("RT1EN")) {
|
||||||
|
set_rds_rt1_enabled(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_len > 6 && str[5] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[5] = 0;
|
||||||
|
arg = str + 6;
|
||||||
|
if (CMD_MATCHES("LEVEL")) {
|
||||||
|
uint8_t val = strtoul((char *)arg, NULL, 10);
|
||||||
|
val /= 255;
|
||||||
|
val *= 15; /* max value*/
|
||||||
|
#ifdef RDS2
|
||||||
|
set_carrier_volume(1, val);
|
||||||
|
set_carrier_volume(2, val);
|
||||||
|
#else
|
||||||
|
set_carrier_volume(1, val);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd_len > 7 && str[6] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[6] = 0;
|
||||||
|
arg = str + 7;
|
||||||
|
|
||||||
|
if (CMD_MATCHES("RDSGEN")) {
|
||||||
|
arg[1] = 0;
|
||||||
|
set_rdsgen(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CMD_MATCHES("PTYNEN")) {
|
||||||
|
arg[1] = 0;
|
||||||
|
set_rds_ptyn_enabled(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
if (CMD_MATCHES("RTPRUN")) {
|
||||||
|
arg[1] = 0;
|
||||||
|
set_rds_rtplus_flags(strtoul((char *)arg, NULL, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (cmd_len > 6 && str[5] == '=') {
|
||||||
|
cmd = str;
|
||||||
|
cmd[5] = 0;
|
||||||
|
arg = str + 6;
|
||||||
|
if (CMD_MATCHES("LEVEL")) {
|
||||||
|
uint8_t val = strtoul((char *)arg, NULL, 10);
|
||||||
|
val /= 255;
|
||||||
|
val *= 15; /* max value*/
|
||||||
|
#ifdef RDS2
|
||||||
|
set_carrier_volume(1, val);
|
||||||
|
set_carrier_volume(2, val);
|
||||||
|
#else
|
||||||
|
set_carrier_volume(1, val);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CMD_MATCHES("ECCEN")) {
|
||||||
|
set_rds_ecclic_toggle(arg[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/ascii_cmd.h
Normal file
23
src/ascii_cmd.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CMD_BUFFER_SIZE 255
|
||||||
|
#define CTL_BUFFER_SIZE (CMD_BUFFER_SIZE * 2)
|
||||||
|
#define READ_TIMEOUT_MS 100
|
||||||
|
|
||||||
|
extern void process_ascii_cmd(unsigned char *cmd);
|
||||||
33
src/common.h
Normal file
33
src/common.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019-2021 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* common includes for mpxgen */
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* workaround for missing pi definition */
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define M_2PI (M_PI * 2.0)
|
||||||
84
src/control_pipe.c
Normal file
84
src/control_pipe.c
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "ascii_cmd.h"
|
||||||
|
#include "control_pipe.h"
|
||||||
|
|
||||||
|
static int fd;
|
||||||
|
static struct pollfd poller;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Opens a file (pipe) to be used to control the RDS coder.
|
||||||
|
*/
|
||||||
|
int open_control_pipe(char *filename) {
|
||||||
|
fd = open(filename, O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd == -1) return -1;
|
||||||
|
|
||||||
|
/* setup the poller */
|
||||||
|
poller.fd = fd;
|
||||||
|
poller.events = POLLIN;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Polls the control file (pipe), and if a command is received,
|
||||||
|
* calls process_ascii_cmd.
|
||||||
|
*/
|
||||||
|
void poll_control_pipe() {
|
||||||
|
static unsigned char pipe_buf[CTL_BUFFER_SIZE];
|
||||||
|
static unsigned char cmd_buf[CMD_BUFFER_SIZE];
|
||||||
|
struct timeval timeout;
|
||||||
|
int ret;
|
||||||
|
fd_set set;
|
||||||
|
char *token;
|
||||||
|
|
||||||
|
FD_ZERO(&set);
|
||||||
|
FD_SET(fd, &set);
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = READ_TIMEOUT_MS * 1000;
|
||||||
|
|
||||||
|
/* check for new commands */
|
||||||
|
if (poll(&poller, 1, READ_TIMEOUT_MS) <= 0) return;
|
||||||
|
|
||||||
|
/* return early if there are no new commands */
|
||||||
|
if (poller.revents == 0) return;
|
||||||
|
|
||||||
|
memset(pipe_buf, 0, CTL_BUFFER_SIZE);
|
||||||
|
|
||||||
|
ret = select(fd + 1, &set, NULL, NULL, &timeout);
|
||||||
|
if (ret == -1 || ret == 0) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
read(fd, pipe_buf, CTL_BUFFER_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle commands per line this is really good because if were sending text commands very quick after eachother then we can get a rt of for example 'Now its 12:00RT Now its 12:01' */
|
||||||
|
token = strtok((char *)pipe_buf, "\n");
|
||||||
|
while (token != NULL) {
|
||||||
|
memset(cmd_buf, 0, CMD_BUFFER_SIZE);
|
||||||
|
memcpy(cmd_buf, token, CMD_BUFFER_SIZE - 1);
|
||||||
|
token = strtok(NULL, "\n");
|
||||||
|
|
||||||
|
process_ascii_cmd(cmd_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void close_control_pipe() {
|
||||||
|
if (fd > 0) close(fd);
|
||||||
|
}
|
||||||
25
src/control_pipe.h
Normal file
25
src/control_pipe.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
extern int open_control_pipe(char *filename);
|
||||||
|
extern void close_control_pipe();
|
||||||
|
extern void poll_control_pipe();
|
||||||
178
src/fm_mpx.c
Normal file
178
src/fm_mpx.c
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "rds.h"
|
||||||
|
#ifdef RDS2
|
||||||
|
#include "rds2.h"
|
||||||
|
#endif
|
||||||
|
#include "fm_mpx.h"
|
||||||
|
#include "osc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local oscillator objects
|
||||||
|
* this is where the MPX waveforms are stored
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static struct osc_t osc_57k;
|
||||||
|
#ifdef RDS2
|
||||||
|
static struct osc_t osc_67k;
|
||||||
|
static struct osc_t osc_71k;
|
||||||
|
static struct osc_t osc_76k;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static float mpx_vol;
|
||||||
|
|
||||||
|
static uint8_t rdsgen;
|
||||||
|
|
||||||
|
void set_output_volume(float vol) {
|
||||||
|
if (vol > 100.0f) vol = 100.0f;
|
||||||
|
mpx_vol = vol / 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rdsgen(uint8_t gen) {
|
||||||
|
#ifdef RDS2
|
||||||
|
if (gen > 2) gen = 2;
|
||||||
|
#else
|
||||||
|
if (gen > 1) gen = 1;
|
||||||
|
#endif
|
||||||
|
rdsgen = gen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* subcarrier volumes */
|
||||||
|
static float volumes[MPX_SUBCARRIER_END] = {
|
||||||
|
0.0f, /* pilot tone: 0% */
|
||||||
|
1.0f, /* RDS: 7.5% modulation */
|
||||||
|
#ifdef RDS2
|
||||||
|
/* RDS2 */
|
||||||
|
0.05f, //66.5 khz 5%
|
||||||
|
0.05f, //71.25 khz 5%
|
||||||
|
0.0f //76 khz 0%
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_carrier_volume(uint8_t carrier, float new_volume) {
|
||||||
|
/* check for valid index */
|
||||||
|
if (carrier >= MPX_SUBCARRIER_END) return;
|
||||||
|
|
||||||
|
/* don't allow levels over 15% */
|
||||||
|
if (new_volume >= 15.0f) new_volume = 15.0f;
|
||||||
|
|
||||||
|
volumes[carrier] = new_volume / 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fm_mpx_init(uint32_t sample_rate) {
|
||||||
|
/* initialize the subcarrier oscillators */
|
||||||
|
osc_init(&osc_57k, sample_rate, 57000.0f);
|
||||||
|
rdsgen = 1;
|
||||||
|
#ifdef RDS2
|
||||||
|
osc_init(&osc_67k, sample_rate, 66500.0f);
|
||||||
|
osc_init(&osc_71k, sample_rate, 71250.0f);
|
||||||
|
osc_init(&osc_76k, sample_rate, 76000.0f);
|
||||||
|
rdsgen = 2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void fm_rds_get_frames(float *outbuf, size_t num_frames) {
|
||||||
|
size_t j = 0;
|
||||||
|
float out;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_frames; i++) {
|
||||||
|
out = 0.0f;
|
||||||
|
|
||||||
|
#ifdef RDS2
|
||||||
|
out += osc_get_cos(&osc_57k)
|
||||||
|
* get_rds_sample(0, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS_STREAM_0];
|
||||||
|
#else
|
||||||
|
out += get_rds_sample(0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS_STREAM_0];
|
||||||
|
#endif
|
||||||
|
#ifdef RDS2
|
||||||
|
#ifdef RDS2_QUADRATURE_CARRIER
|
||||||
|
/* RDS2 is quadrature phase */
|
||||||
|
|
||||||
|
/* 90 degree shift */
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||||
|
out += osc_get_sin(&osc_67k)
|
||||||
|
* get_rds_sample(1, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 180 degree shift */
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_2] != 0) {
|
||||||
|
out += -osc_get_cos(&osc_71k)
|
||||||
|
* get_rds_sample(2, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 270 degree shift */
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_3] != 0) {
|
||||||
|
out += -osc_get_sin(&osc_76k)
|
||||||
|
* get_rds_sample(3, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_3];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||||
|
out += osc_get_cos(&osc_67k)
|
||||||
|
* get_rds_sample(1, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||||
|
out += osc_get_cos(&osc_71k)
|
||||||
|
* get_rds_sample(2, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_3] != 0) {
|
||||||
|
out += osc_get_cos(&osc_76k)
|
||||||
|
* get_rds_sample(3, 0)
|
||||||
|
* volumes[MPX_SUBCARRIER_RDS2_STREAM_3];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* update oscillator */
|
||||||
|
osc_update_pos(&osc_57k);
|
||||||
|
#ifdef RDS2
|
||||||
|
osc_update_pos(&osc_67k);
|
||||||
|
osc_update_pos(&osc_71k);
|
||||||
|
osc_update_pos(&osc_76k);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* declipper */
|
||||||
|
out = fminf(+1.0f, out);
|
||||||
|
out = fmaxf(-1.0f, out);
|
||||||
|
|
||||||
|
/* adjust volume and put into channel */
|
||||||
|
if(rdsgen != 0) outbuf[j] = out * mpx_vol;
|
||||||
|
j++;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fm_mpx_exit() {
|
||||||
|
osc_exit(&osc_57k);
|
||||||
|
#ifdef RDS2
|
||||||
|
osc_exit(&osc_67k);
|
||||||
|
osc_exit(&osc_71k);
|
||||||
|
osc_exit(&osc_76k);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
45
src/fm_mpx.h
Normal file
45
src/fm_mpx.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* MPX */
|
||||||
|
#define NUM_MPX_FRAMES_IN 512
|
||||||
|
#define NUM_MPX_FRAMES_OUT (NUM_MPX_FRAMES_IN * 2)
|
||||||
|
/*
|
||||||
|
* The sample rate at which the MPX generation runs at
|
||||||
|
*/
|
||||||
|
#define MPX_SAMPLE_RATE RDS_SAMPLE_RATE
|
||||||
|
|
||||||
|
#define OUTPUT_SAMPLE_RATE 192000
|
||||||
|
|
||||||
|
enum mpx_subcarriers {
|
||||||
|
MPX_SUBCARRIER_ST_PILOT,
|
||||||
|
MPX_SUBCARRIER_RDS_STREAM_0,
|
||||||
|
#ifdef RDS2
|
||||||
|
MPX_SUBCARRIER_RDS2_STREAM_1,
|
||||||
|
MPX_SUBCARRIER_RDS2_STREAM_2,
|
||||||
|
MPX_SUBCARRIER_RDS2_STREAM_3,
|
||||||
|
#endif
|
||||||
|
MPX_SUBCARRIER_END
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void fm_mpx_init(uint32_t sample_rate);
|
||||||
|
extern void fm_rds_get_frames(float *outbuf, size_t num_frames);
|
||||||
|
extern void fm_mpx_exit();
|
||||||
|
extern void set_output_volume(float vol);
|
||||||
|
extern void set_rdsgen(uint8_t gen);
|
||||||
|
extern void set_carrier_volume(uint8_t carrier, float new_volume);
|
||||||
519
src/lib.c
Normal file
519
src/lib.c
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2021 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "rds.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stuff common for both RDS and RDS2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* needed workaround implicit declaration (edit: do we need this? ) */
|
||||||
|
extern int nanosleep(const struct timespec *req, struct timespec *rem);
|
||||||
|
|
||||||
|
/* millisecond sleep */
|
||||||
|
void msleep(unsigned long ms) {
|
||||||
|
struct timespec ts;
|
||||||
|
ts.tv_sec = ms / 1000ul; /* whole seconds */
|
||||||
|
ts.tv_nsec = (ms % 1000ul) * 1000; /* remainder, in nanoseconds */
|
||||||
|
nanosleep(&ts, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* just like strlen */
|
||||||
|
size_t _strnlen(const char *s, size_t maxlen) {
|
||||||
|
size_t len = 0;
|
||||||
|
while (s[len] != 0 && len < maxlen)
|
||||||
|
len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unsigned equivalent of strcmp */
|
||||||
|
int ustrcmp(const unsigned char *s1, const unsigned char *s2) {
|
||||||
|
unsigned char c1, c2;
|
||||||
|
|
||||||
|
do {
|
||||||
|
c1 = *s1++;
|
||||||
|
c2 = *s2++;
|
||||||
|
if (c1 == '\0')
|
||||||
|
return c1 - c2;
|
||||||
|
} while (c1 == c2);
|
||||||
|
|
||||||
|
return c1 - c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
static char *rtp_content_types[64] = {
|
||||||
|
/* dummy */
|
||||||
|
"DUMMY_CLASS",
|
||||||
|
/* item */
|
||||||
|
"ITEM.TITLE",
|
||||||
|
"ITEM.ALBUM",
|
||||||
|
"ITEM.TRACKNUMBER",
|
||||||
|
"ITEM.ARTIST",
|
||||||
|
"ITEM.COMPOSITION",
|
||||||
|
"ITEM.MOVEMENT",
|
||||||
|
"ITEM.CONDUCTOR",
|
||||||
|
"ITEM.COMPOSER",
|
||||||
|
"ITEM.BAND",
|
||||||
|
"ITEM.COMMENT",
|
||||||
|
"ITEM.GENRE",
|
||||||
|
/* info */
|
||||||
|
"INFO.NEWS",
|
||||||
|
"INFO.NEWS.LOCAL",
|
||||||
|
"INFO.STOCKMARKET",
|
||||||
|
"INFO.SPORT",
|
||||||
|
"INFO.LOTTERY",
|
||||||
|
"INFO.HOROSCOPE",
|
||||||
|
"INFO.DAILY_DIVERSION",
|
||||||
|
"INFO.HEALTH",
|
||||||
|
"INFO.EVENT",
|
||||||
|
"INFO.SCENE",
|
||||||
|
"INFO.CINEMA",
|
||||||
|
"INFO.TV",
|
||||||
|
"INFO.DATE_TIME",
|
||||||
|
"INFO.WEATHER",
|
||||||
|
"INFO.TRAFFIC",
|
||||||
|
"INFO.ALARM",
|
||||||
|
"INFO.ADVERTISEMENT",
|
||||||
|
"INFO.URL",
|
||||||
|
"INFO.OTHER",
|
||||||
|
/* program */
|
||||||
|
"STATIONNAME.SHORT",
|
||||||
|
"STATIONNAME.LONG",
|
||||||
|
"PROGRAMME.NOW",
|
||||||
|
"PROGRAMME.NEXT",
|
||||||
|
"PROGRAMME.PART",
|
||||||
|
"PROGRAMME.HOST",
|
||||||
|
"PROGRAMME.EDITORIAL_STAFF",
|
||||||
|
"PROGRAMME.FREQUENCY",
|
||||||
|
"PROGRAMME.HOMEPAGE",
|
||||||
|
"PROGRAMME.SUBCHANNEL",
|
||||||
|
/* interactivity */
|
||||||
|
"PHONE.HOTLINE",
|
||||||
|
"PHONE.STUDIO",
|
||||||
|
"PHONE.OTHER",
|
||||||
|
"SMS.STUDIO",
|
||||||
|
"SMS.OTHER",
|
||||||
|
"EMAIL.HOTLINE",
|
||||||
|
"EMAIL.STUDIO",
|
||||||
|
"EMAIL.OTHER",
|
||||||
|
"MMS.OTHER",
|
||||||
|
"CHAT",
|
||||||
|
"CHAT.CENTRE",
|
||||||
|
"VOTE.QUESTION",
|
||||||
|
"VOTE.CENTRE",
|
||||||
|
/* rfu */
|
||||||
|
"RFU_1",
|
||||||
|
"RFU_2",
|
||||||
|
/* private classes */
|
||||||
|
"PRIVATE_1",
|
||||||
|
"PRIVATE_2",
|
||||||
|
"PRIVATE_3",
|
||||||
|
/* descriptor */
|
||||||
|
"PLACE",
|
||||||
|
"APPOINTMENT",
|
||||||
|
"IDENTIFIER",
|
||||||
|
"PURCHASE",
|
||||||
|
"GET_DATA"
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t get_rtp_tag_id(char *rtp_tag_name) {
|
||||||
|
uint8_t tag_id = 0;
|
||||||
|
for (uint8_t i = 0; i < 64; i++) {
|
||||||
|
if (strcmp(rtp_tag_name, rtp_content_types[i]) == 0) {
|
||||||
|
tag_id = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *get_rtp_tag_name(uint8_t rtp_tag) {
|
||||||
|
if (rtp_tag > 63) rtp_tag = 0;
|
||||||
|
return rtp_content_types[rtp_tag];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
static uint16_t offset_words[] = {
|
||||||
|
0x0FC, /* A */
|
||||||
|
0x198, /* B */
|
||||||
|
0x168, /* C */
|
||||||
|
0x1B4, /* D */
|
||||||
|
0x350 /* C' */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* CRC-16 ITU-T/CCITT checkword calculation */
|
||||||
|
/* wanna ask where is this used */
|
||||||
|
uint16_t crc16(uint8_t *data, size_t len) {
|
||||||
|
uint16_t crc = 0xffff;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
crc = (crc >> 8) | (crc << 8);
|
||||||
|
crc ^= data[i];
|
||||||
|
crc ^= (crc & 0xff) >> 4;
|
||||||
|
crc ^= (crc << 8) << 4;
|
||||||
|
crc ^= ((crc & 0xff) << 4) << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc ^ 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate the checkword for each block and emit the bits */
|
||||||
|
#ifdef RDS2
|
||||||
|
void add_checkwords(uint16_t *blocks, uint8_t *bits, bool rds2)
|
||||||
|
#else
|
||||||
|
void add_checkwords(uint16_t *blocks, uint8_t *bits)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
size_t i, j;
|
||||||
|
uint8_t bit, msb;
|
||||||
|
uint16_t block, block_crc, check, offset_word;
|
||||||
|
bool group_type_b = false;
|
||||||
|
#ifdef RDS2
|
||||||
|
bool tunneling_type_b = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* if b11 is 1, then type B */
|
||||||
|
#ifdef RDS2
|
||||||
|
if (!rds2 && IS_TYPE_B(blocks))
|
||||||
|
#else
|
||||||
|
if (IS_TYPE_B(blocks))
|
||||||
|
#endif
|
||||||
|
group_type_b = true;
|
||||||
|
|
||||||
|
#ifdef RDS2
|
||||||
|
/* for when version B groups are coded in RDS2 */
|
||||||
|
if (rds2 && IS_TUNNELING(blocks) && IS_TYPE_B(blocks))
|
||||||
|
tunneling_type_b = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (i = 0; i < GROUP_LENGTH; i++) {
|
||||||
|
#ifdef RDS2
|
||||||
|
/*
|
||||||
|
* If tunneling type B groups use offset
|
||||||
|
* word C' for block 3
|
||||||
|
*/
|
||||||
|
if (rds2 && i == 2 && tunneling_type_b) {
|
||||||
|
offset_word = offset_words[4];
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
/* Group version B needs C' for block 3 */
|
||||||
|
if (i == 2 && group_type_b) {
|
||||||
|
offset_word = offset_words[4];
|
||||||
|
} else {
|
||||||
|
offset_word = offset_words[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
block = blocks[i];
|
||||||
|
|
||||||
|
/* Classical CRC computation */
|
||||||
|
block_crc = 0;
|
||||||
|
for (j = 0; j < BLOCK_SIZE; j++) {
|
||||||
|
bit = (block & (INT16_15 >> j)) != 0;
|
||||||
|
msb = (block_crc >> (POLY_DEG - 1)) & 1;
|
||||||
|
block_crc <<= 1;
|
||||||
|
if (msb ^ bit) block_crc ^= POLY;
|
||||||
|
*bits++ = bit;
|
||||||
|
}
|
||||||
|
check = block_crc ^ offset_word;
|
||||||
|
for (j = 0; j < POLY_DEG; j++) {
|
||||||
|
*bits++ = (check & ((1 << (POLY_DEG - 1)) >> j)) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AF stuff
|
||||||
|
*/
|
||||||
|
uint8_t add_rds_af(struct rds_af_t *af_list, float freq) {
|
||||||
|
uint16_t af;
|
||||||
|
uint8_t entries_reqd = 1; /* default for FM */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check how many slots are required for the frequency
|
||||||
|
*
|
||||||
|
* LF/MF (AM) needs 2 entries: one for the
|
||||||
|
* AM follows code and the freq code itself
|
||||||
|
*/
|
||||||
|
if (freq < 87.6f || freq > 107.9f) { /* is LF/MF */
|
||||||
|
entries_reqd = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if the AF list is full */
|
||||||
|
if (af_list->num_afs + entries_reqd > MAX_AFS) {
|
||||||
|
/* Too many AF entries */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if new frequency is valid */
|
||||||
|
if (freq >= 87.6f && freq <= 107.9f) { /* FM */
|
||||||
|
af = (uint16_t)(freq * 10.0f) - 875;
|
||||||
|
af_list->afs[af_list->num_entries] = af;
|
||||||
|
af_list->num_entries += 1;
|
||||||
|
} else if (freq >= 153.0f && freq <= 279.0f) { /* LF */
|
||||||
|
af = (uint16_t)(freq - 153.0f) / 9 + 1;
|
||||||
|
af_list->afs[af_list->num_entries + 0] = AF_CODE_LFMF_FOLLOWS;
|
||||||
|
af_list->afs[af_list->num_entries + 1] = af;
|
||||||
|
af_list->num_entries += 2;
|
||||||
|
} else if (freq >= 531.0f && freq <= 1602.0f) { /* AM 9 kHz spacing */
|
||||||
|
af = (uint16_t)(freq - 531.0f) / 9 + 16;
|
||||||
|
af_list->afs[af_list->num_entries + 0] = AF_CODE_LFMF_FOLLOWS;
|
||||||
|
af_list->afs[af_list->num_entries + 1] = af;
|
||||||
|
af_list->num_entries += 2;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
af_list->num_afs++;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *show_af_list(struct rds_af_t af_list) {
|
||||||
|
float freq;
|
||||||
|
bool is_lfmf = false;
|
||||||
|
static char outstr[255];
|
||||||
|
uint8_t outstrlen = 0;
|
||||||
|
|
||||||
|
outstrlen += sprintf(outstr, "AF: %u,", af_list.num_afs);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < af_list.num_entries; i++) {
|
||||||
|
if (af_list.afs[i] == AF_CODE_LFMF_FOLLOWS) {
|
||||||
|
/* The next AF will be for LF/MF */
|
||||||
|
is_lfmf = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_lfmf) {
|
||||||
|
if (af_list.afs[i] >= 1 && af_list.afs[i] <= 15) { /* LF */
|
||||||
|
freq = 153.0f + ((float)(af_list.afs[i] - 1) * 9.0f);
|
||||||
|
outstrlen += sprintf(outstr + outstrlen, " (LF)%.0f", freq);
|
||||||
|
} else { /*if (af_list.afs[i] >= 16 && af_list.afs[i] <= 135) {*/ /* MF */
|
||||||
|
freq = 531.0f + ((float)(af_list.afs[i] - 16) * 9.0f);
|
||||||
|
outstrlen += sprintf(outstr + outstrlen, " (MF)%.0f", freq);
|
||||||
|
}
|
||||||
|
is_lfmf = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FM */
|
||||||
|
freq = (float)((uint16_t)af_list.afs[i] + 875) / 10.0f;
|
||||||
|
outstrlen += sprintf(outstr + outstrlen, " %.1f", freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UTF-8 to RDS char set converter
|
||||||
|
*
|
||||||
|
* Translates certain chars into their RDS equivalents
|
||||||
|
* NOTE!! Only applies to PS, RT and PTYN. LPS uses UTF-8 (SCB = 1)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define XLATSTRLEN 255
|
||||||
|
unsigned char *xlat(unsigned char *str) {
|
||||||
|
static unsigned char new_str[XLATSTRLEN];
|
||||||
|
uint8_t i = 0;
|
||||||
|
|
||||||
|
while (*str != 0 && i < XLATSTRLEN) {
|
||||||
|
switch (*str) {
|
||||||
|
case 0xc2:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0xa1: new_str[i] = 0x8e; break; /* INVERTED EXCLAMATION MARK */
|
||||||
|
case 0xa3: new_str[i] = 0xaa; break; /* POUND SIGN */
|
||||||
|
case 0xa7: new_str[i] = 0xbf; break; /* SECTION SIGN */
|
||||||
|
case 0xa9: new_str[i] = 0xa2; break; /* COPYRIGHT SIGN */
|
||||||
|
case 0xaa: new_str[i] = 0xa0; break; /* FEMININE ORDINAL INDICATOR */
|
||||||
|
case 0xb0: new_str[i] = 0xbb; break; /* DEGREE SIGN */
|
||||||
|
case 0xb1: new_str[i] = 0xb4; break; /* PLUS-MINUS SIGN */
|
||||||
|
case 0xb2: new_str[i] = 0xb2; break; /* SUPERSCRIPT TWO */
|
||||||
|
case 0xb3: new_str[i] = 0xb3; break; /* SUPERSCRIPT THREE */
|
||||||
|
case 0xb5: new_str[i] = 0xb8; break; /* MIKRO SIGN */
|
||||||
|
case 0xb9: new_str[i] = 0xb1; break; /* SUPERSCRIPT ONE */
|
||||||
|
case 0xba: new_str[i] = 0xb0; break; /* MASCULINE ORDINAL INDICATOR */
|
||||||
|
case 0xbc: new_str[i] = 0xbc; break; /* VULGAR FRACTION ONE QUARTER */
|
||||||
|
case 0xbd: new_str[i] = 0xbd; break; /* VULGAR FRACTION ONE HALF */
|
||||||
|
case 0xbe: new_str[i] = 0xbe; break; /* VULGAR FRACTION THREE QUARTERS */
|
||||||
|
case 0xbf: new_str[i] = 0xb9; break; /* INVERTED QUESTION MARK */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc3:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0x80: new_str[i] = 0xc1; break; /* LATIN CAPITAL LETTER A WITH GRAVE */
|
||||||
|
case 0x81: new_str[i] = 0xc0; break; /* LATIN CAPITAL LETTER A WITH ACUTE */
|
||||||
|
case 0x82: new_str[i] = 0xd0; break; /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
|
||||||
|
case 0x83: new_str[i] = 0xe0; break; /* LATIN CAPITAL LETTER A WITH TILDE */
|
||||||
|
case 0x84: new_str[i] = 0xd1; break; /* LATIN CAPITAL LETTER A WITH DIAERESIS */
|
||||||
|
case 0x85: new_str[i] = 0xe1; break; /* LATIN CAPITAL LETTER A WITH RING ABOVE */
|
||||||
|
case 0x86: new_str[i] = 0xe2; break; /* LATIN CAPITAL LETTER AE */
|
||||||
|
case 0x87: new_str[i] = 0x8b; break; /* LATIN CAPITAL LETTER C WITH CEDILLA */
|
||||||
|
case 0x88: new_str[i] = 0xc3; break; /* LATIN CAPITAL LETTER E WITH GRAVE */
|
||||||
|
case 0x89: new_str[i] = 0xc2; break; /* LATIN CAPITAL LETTER E WITH ACUTE */
|
||||||
|
case 0x8a: new_str[i] = 0xd2; break; /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
|
||||||
|
case 0x8b: new_str[i] = 0xd3; break; /* LATIN CAPITAL LETTER E WITH DIAERESIS */
|
||||||
|
case 0x8c: new_str[i] = 0xc5; break; /* LATIN CAPITAL LETTER I WITH GRAVE */
|
||||||
|
case 0x8d: new_str[i] = 0xc4; break; /* LATIN CAPITAL LETTER I WITH ACUTE */
|
||||||
|
case 0x8e: new_str[i] = 0xd4; break; /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
|
||||||
|
case 0x8f: new_str[i] = 0xd5; break; /* LATIN CAPITAL LETTER I WITH DIAERESIS */
|
||||||
|
case 0x90: new_str[i] = 0xce; break; /* LATIN CAPITAL LETTER ETH */
|
||||||
|
case 0x91: new_str[i] = 0x8a; break; /* LATIN CAPITAL LETTER N WITH TILDE */
|
||||||
|
case 0x92: new_str[i] = 0xc7; break; /* LATIN CAPITAL LETTER O WITH GRAVE */
|
||||||
|
case 0x93: new_str[i] = 0xc6; break; /* LATIN CAPITAL LETTER O WITH ACUTE */
|
||||||
|
case 0x94: new_str[i] = 0xd6; break; /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
|
||||||
|
case 0x95: new_str[i] = 0xe6; break; /* LATIN CAPITAL LETTER O WITH TILDE */
|
||||||
|
case 0x96: new_str[i] = 0xd7; break; /* LATIN CAPITAL LETTER O WITH DIAERESIS */
|
||||||
|
case 0x98: new_str[i] = 0xe7; break; /* LATIN CAPITAL LETTER O WITH STROKE */
|
||||||
|
case 0x99: new_str[i] = 0xc9; break; /* LATIN CAPITAL LETTER U WITH GRAVE */
|
||||||
|
case 0x9a: new_str[i] = 0xc8; break; /* LATIN CAPITAL LETTER U WITH ACUTE */
|
||||||
|
case 0x9b: new_str[i] = 0xd8; break; /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
|
||||||
|
case 0x9c: new_str[i] = 0xd9; break; /* LATIN CAPITAL LETTER U WITH DIAERESIS */
|
||||||
|
case 0x9d: new_str[i] = 0xe5; break; /* LATIN CAPITAL LETTER Y WITH ACUTE */
|
||||||
|
case 0x9e: new_str[i] = 0xe8; break; /* LATIN CAPITAL LETTER THORN */
|
||||||
|
case 0xa0: new_str[i] = 0x81; break; /* LATIN SMALL LETTER A WITH GRAVE */
|
||||||
|
case 0xa1: new_str[i] = 0x80; break; /* LATIN SMALL LETTER A WITH ACUTE */
|
||||||
|
case 0xa2: new_str[i] = 0x90; break; /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
|
||||||
|
case 0xa3: new_str[i] = 0xf0; break; /* LATIN SMALL LETTER A WITH TILDE */
|
||||||
|
case 0xa4: new_str[i] = 0x91; break; /* LATIN SMALL LETTER A WITH DIAERESIS */
|
||||||
|
case 0xa5: new_str[i] = 0xf1; break; /* LATIN SMALL LETTER A WITH RING ABOVE */
|
||||||
|
case 0xa6: new_str[i] = 0xf2; break; /* LATIN SMALL LETTER AE */
|
||||||
|
case 0xa7: new_str[i] = 0x9b; break; /* LATIN SMALL LETTER C WITH CEDILLA */
|
||||||
|
case 0xa8: new_str[i] = 0x83; break; /* LATIN SMALL LETTER E WITH GRAVE */
|
||||||
|
case 0xa9: new_str[i] = 0x82; break; /* LATIN SMALL LETTER E WITH ACUTE */
|
||||||
|
case 0xaa: new_str[i] = 0x92; break; /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
|
||||||
|
case 0xab: new_str[i] = 0x93; break; /* LATIN SMALL LETTER E WITH DIAERESIS */
|
||||||
|
case 0xac: new_str[i] = 0x85; break; /* LATIN SMALL LETTER I WITH GRAVE */
|
||||||
|
case 0xad: new_str[i] = 0x84; break; /* LATIN SMALL LETTER I WITH ACUTE */
|
||||||
|
case 0xae: new_str[i] = 0x94; break; /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
|
||||||
|
case 0xaf: new_str[i] = 0x95; break; /* LATIN SMALL LETTER I WITH DIAERESIS */
|
||||||
|
case 0xb0: new_str[i] = 0xef; break; /* LATIN SMALL LETTER ETH */
|
||||||
|
case 0xb1: new_str[i] = 0x9a; break; /* LATIN SMALL LETTER N WITH TILDE */
|
||||||
|
case 0xb2: new_str[i] = 0x87; break; /* LATIN SMALL LETTER O WITH GRAVE */
|
||||||
|
case 0xb3: new_str[i] = 0x86; break; /* LATIN SMALL LETTER O WITH ACUTE */
|
||||||
|
case 0xb4: new_str[i] = 0x96; break; /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
|
||||||
|
case 0xb5: new_str[i] = 0xf6; break; /* LATIN SMALL LETTER O WITH TILDE */
|
||||||
|
case 0xb6: new_str[i] = 0x97; break; /* LATIN SMALL LETTER O WITH DIAERESIS */
|
||||||
|
case 0xb7: new_str[i] = 0xba; break; /* DIVISION SIGN */
|
||||||
|
case 0xb8: new_str[i] = 0xf7; break; /* LATIN SMALL LETTER O WITH STROKE */
|
||||||
|
case 0xb9: new_str[i] = 0x89; break; /* LATIN SMALL LETTER U WITH GRAVE */
|
||||||
|
case 0xba: new_str[i] = 0x88; break; /* LATIN SMALL LETTER U WITH ACUTE */
|
||||||
|
case 0xbb: new_str[i] = 0x98; break; /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
|
||||||
|
case 0xbc: new_str[i] = 0x99; break; /* LATIN SMALL LETTER U WITH DIAERESIS */
|
||||||
|
case 0xbd: new_str[i] = 0xf5; break; /* LATIN SMALL LETTER Y WITH ACUTE */
|
||||||
|
case 0xbe: new_str[i] = 0xf8; break; /* LATIN SMALL LETTER THORN */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc4:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0x87: new_str[i] = 0xfb; break; /* LATIN SMALL LETTER C WITH ACUTE */
|
||||||
|
case 0x8c: new_str[i] = 0xcb; break; /* LATIN CAPITAL LETTER C WITH CARON */
|
||||||
|
case 0x8d: new_str[i] = 0xdb; break; /* LATIN SMALL LETTER C WITH CARON */
|
||||||
|
case 0x91: new_str[i] = 0xde; break; /* LATIN SMALL LETTER D WITH STROKE */
|
||||||
|
case 0x9b: new_str[i] = 0xa5; break; /* LATIN SMALL LETTER E WITH CARON */
|
||||||
|
case 0xb0: new_str[i] = 0xb5; break; /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
|
||||||
|
case 0xb1: new_str[i] = 0x9f; break; /* LATIN SMALL LETTER DOTLESS I */
|
||||||
|
case 0xb2: new_str[i] = 0x8f; break; /* LATIN CAPITAL LIGATURE IJ */
|
||||||
|
case 0xb3: new_str[i] = 0x9f; break; /* LATIN SMALL LIGATURE IJ */
|
||||||
|
case 0xbf: new_str[i] = 0xcf; break; /* LATIN CAPITAL LETTER L WITH MIDDLE DOT */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc5:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0x80: new_str[i] = 0xdf; break; /* LATIN SMALL LETTER L WITH MIDDLE DOT */
|
||||||
|
case 0x84: new_str[i] = 0xb6; break; /* LATIN SMALL LETTER N WITH ACUTE */
|
||||||
|
case 0x88: new_str[i] = 0xa6; break; /* LATIN SMALL LETTER N WITH CARON */
|
||||||
|
case 0x8a: new_str[i] = 0xe9; break; /* LATIN CAPITAL LETTER ENG */
|
||||||
|
case 0x8b: new_str[i] = 0xf9; break; /* LATIN SMALL LETTER ENG */
|
||||||
|
case 0x91: new_str[i] = 0xa7; break; /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */
|
||||||
|
case 0x92: new_str[i] = 0xe3; break; /* LATIN CAPITAL LIGATURE OE */
|
||||||
|
case 0x93: new_str[i] = 0xf3; break; /* LATIN SMALL LIGATURE OE */
|
||||||
|
case 0x94: new_str[i] = 0xea; break; /* LATIN CAPITAL LETTER R WITH ACUTE */
|
||||||
|
case 0x95: new_str[i] = 0xfa; break; /* LATIN SMALL LETTER R WITH ACUTE */
|
||||||
|
case 0x98: new_str[i] = 0xca; break; /* LATIN CAPITAL LETTER R WITH CARON */
|
||||||
|
case 0x99: new_str[i] = 0xda; break; /* LATIN SMALL LETTER R WITH CARON */
|
||||||
|
case 0x9a: new_str[i] = 0xec; break; /* LATIN CAPITAL LETTER S WITH ACUTE */
|
||||||
|
case 0x9b: new_str[i] = 0xfc; break; /* LATIN SMALL LETTER S WITH ACUTE */
|
||||||
|
case 0x9e: new_str[i] = 0x8c; break; /* LATIN CAPITAL LETTER S WITH CEDILLA */
|
||||||
|
case 0x9f: new_str[i] = 0x9c; break; /* LATIN SMALL LETTER S WITH CEDILLA */
|
||||||
|
case 0xa0: new_str[i] = 0xcc; break; /* LATIN CAPITAL LETTER S WITH CARON */
|
||||||
|
case 0xa1: new_str[i] = 0xdc; break; /* LATIN SMALL LETTER S WITH CARON */
|
||||||
|
case 0xa6: new_str[i] = 0xee; break; /* LATIN CAPITAL LETTER T WITH STROKE */
|
||||||
|
case 0xa7: new_str[i] = 0xfe; break; /* LATIN SMALL LETTER T WITH STROKE */
|
||||||
|
case 0xb1: new_str[i] = 0xb7; break; /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */
|
||||||
|
case 0xb5: new_str[i] = 0xf4; break; /* LATIN SMALL LETTER W WITH CIRCUMFLEX */
|
||||||
|
case 0xb7: new_str[i] = 0xe4; break; /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */
|
||||||
|
case 0xb9: new_str[i] = 0xed; break; /* LATIN CAPITAL LETTER Z WITH ACUTE */
|
||||||
|
case 0xba: new_str[i] = 0xfd; break; /* LATIN SMALL LETTER Z WITH ACUTE */
|
||||||
|
case 0xbd: new_str[i] = 0xcd; break; /* LATIN CAPITAL LETTER Z WITH CARON */
|
||||||
|
case 0xbe: new_str[i] = 0xdd; break; /* LATIN SMALL LETTER Z WITH CARON */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc7:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0xa6: new_str[i] = 0xa4; break; /* LATIN CAPITAL LETTER G WITH CARON */
|
||||||
|
case 0xa7: new_str[i] = 0x9d; break; /* LATIN SMALL LETTER G WITH CARON */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xce:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0xb1: new_str[i] = 0xa1; break; /* GREEK SMALL LETTER ALPHA */
|
||||||
|
case 0xb2: new_str[i] = 0x8d; break; /* GREEK SMALL LETTER BETA */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xcf:
|
||||||
|
str++;
|
||||||
|
switch (*str) {
|
||||||
|
case 0x80: new_str[i] = 0xa8; break; /* GREEK SMALL LETTER PI */
|
||||||
|
default: new_str[i] = ' '; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* 0-127 or unknown */
|
||||||
|
switch (*str) {
|
||||||
|
case '$': new_str[i] = 0xab; break; /* DOLLAR SIGN */
|
||||||
|
default: new_str[i] = *str; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_str[i] = 0;
|
||||||
|
return new_str;
|
||||||
|
}
|
||||||
34
src/lib.h
Normal file
34
src/lib.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2021 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern void msleep(unsigned long ms);
|
||||||
|
|
||||||
|
extern size_t _strnlen(const char *s, size_t maxlen);
|
||||||
|
extern int ustrcmp(const unsigned char *s1, const unsigned char *s2);
|
||||||
|
|
||||||
|
extern uint8_t get_rtp_tag_id(char *rtp_tag_name);
|
||||||
|
extern char *get_rtp_tag_name(uint8_t rtp_tag);
|
||||||
|
#ifdef RDS2
|
||||||
|
extern void add_checkwords(uint16_t *blocks, uint8_t *bits, bool rds2);
|
||||||
|
#else
|
||||||
|
extern void add_checkwords(uint16_t *blocks, uint8_t *bits);
|
||||||
|
#endif
|
||||||
|
extern uint8_t add_rds_af(struct rds_af_t *af_list, float freq);
|
||||||
|
extern char *show_af_list(struct rds_af_t af_list);
|
||||||
|
extern uint16_t crc16(uint8_t *data, size_t len);
|
||||||
|
extern unsigned char *xlat(unsigned char *str);
|
||||||
15
src/make-station-logo.sh
Executable file
15
src/make-station-logo.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create station logo data to build into the mpxgen executable for RDS2
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 <logo file>"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
echo 'static const unsigned char station_logo[] = {' > rds2_image_data.c
|
||||||
|
xxd -i < "$1" >> rds2_image_data.c
|
||||||
|
echo '};' >> rds2_image_data.c
|
||||||
|
echo 'static const unsigned int station_logo_len = sizeof(station_logo);' >> rds2_image_data.c
|
||||||
|
fi
|
||||||
354
src/minirds.c
Normal file
354
src/minirds.c
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <signal.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <pulse/simple.h>
|
||||||
|
|
||||||
|
#include "rds.h"
|
||||||
|
#include "fm_mpx.h"
|
||||||
|
#include "control_pipe.h"
|
||||||
|
#include "resampler.h"
|
||||||
|
#include "lib.h"
|
||||||
|
#include "ascii_cmd.h"
|
||||||
|
|
||||||
|
static uint8_t stop_rds;
|
||||||
|
|
||||||
|
static void stop() {
|
||||||
|
printf("Received an stopping signal\n");
|
||||||
|
stop_rds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void float2char2channel(
|
||||||
|
float *inbuf, char *outbuf, size_t frames) {
|
||||||
|
uint16_t j = 0, k = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < frames; i++) {
|
||||||
|
int16_t sample = lroundf(inbuf[j++] * 16383.5f);
|
||||||
|
|
||||||
|
// Direct memory write is more efficient for byte conversion
|
||||||
|
outbuf[k++] = sample & 0xFF;
|
||||||
|
outbuf[k++] = (sample >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* threads */
|
||||||
|
static void *control_pipe_worker() {
|
||||||
|
while (!stop_rds) {
|
||||||
|
poll_control_pipe();
|
||||||
|
msleep(READ_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
close_control_pipe();
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_help(char *name) {
|
||||||
|
printf(
|
||||||
|
"This is MiniRDS, a lightweight RDS encoder.\n"
|
||||||
|
"Version %f\n"
|
||||||
|
"\n"
|
||||||
|
"Usage: %s [options]\n"
|
||||||
|
"\n"
|
||||||
|
" -i,--pi Program Identification code\n"
|
||||||
|
" [default: 3073]\n"
|
||||||
|
" -s,--ps Program Service name\n"
|
||||||
|
" [default: \"radio95\"]\n"
|
||||||
|
" -r,--rt1 Radio Text 1\n"
|
||||||
|
" [default: (nothing)]\n"
|
||||||
|
" -p,--pty Program Type\n"
|
||||||
|
" [default: 0]\n"
|
||||||
|
" -T,--tp Traffic Program\n"
|
||||||
|
" [default: 0]\n"
|
||||||
|
" -A,--af Alternative Frequency (FM/LF/MF)\n"
|
||||||
|
" (more than one AF may be passed)\n"
|
||||||
|
" -P,--ptyn Program Type Name\n"
|
||||||
|
" -l,--lps Long PS\n"
|
||||||
|
" -e,--ecc ECC code\n"
|
||||||
|
" -d,--di DI code\n"
|
||||||
|
" -C,--ctl FIFO control pipe\n"
|
||||||
|
#ifdef RDS2
|
||||||
|
" -I,--img RDS2 Logo path\n"
|
||||||
|
#endif
|
||||||
|
" -h,--help Show this help text and exit\n"
|
||||||
|
" -v,--version Show version and exit\n"
|
||||||
|
"\n",
|
||||||
|
VERSION,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_version() {
|
||||||
|
printf("MiniRDS version (radio95 Edit) %f\n", VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
int opt;
|
||||||
|
char control_pipe[51];
|
||||||
|
struct rds_params_t rds_params = {
|
||||||
|
.ps = "radio95",
|
||||||
|
.rt1 = "",
|
||||||
|
.pi = 0x305F,
|
||||||
|
.ecc = 0xE2,
|
||||||
|
.lps = "radio95 - Radio Nowotomyskie"
|
||||||
|
};
|
||||||
|
float volume = 100.0f;
|
||||||
|
|
||||||
|
/* buffers */
|
||||||
|
float *mpx_buffer;
|
||||||
|
float *out_buffer;
|
||||||
|
char *dev_out;
|
||||||
|
|
||||||
|
int8_t r;
|
||||||
|
size_t frames;
|
||||||
|
|
||||||
|
/* SRC */
|
||||||
|
SRC_STATE *src_state;
|
||||||
|
SRC_DATA src_data;
|
||||||
|
|
||||||
|
/* PASIMPLE */
|
||||||
|
pa_simple *device;
|
||||||
|
pa_sample_spec format;
|
||||||
|
|
||||||
|
/* pthread */
|
||||||
|
pthread_attr_t attr;
|
||||||
|
pthread_t control_pipe_thread;
|
||||||
|
pthread_mutex_t control_pipe_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
pthread_cond_t control_pipe_cond;
|
||||||
|
|
||||||
|
const char *short_opt = "R:i:s:r:p:T:A:P:l:e:L:d:C:"
|
||||||
|
#ifdef RDS2
|
||||||
|
"I:"
|
||||||
|
#endif
|
||||||
|
"hv";
|
||||||
|
|
||||||
|
struct option long_opt[] =
|
||||||
|
{
|
||||||
|
{"rds", required_argument, NULL, 'R'},
|
||||||
|
{"pi", required_argument, NULL, 'i'},
|
||||||
|
{"ps", required_argument, NULL, 's'},
|
||||||
|
{"rt1", required_argument, NULL, 'r'},
|
||||||
|
{"pty", required_argument, NULL, 'p'},
|
||||||
|
{"tp", required_argument, NULL, 'T'},
|
||||||
|
{"af", required_argument, NULL, 'A'},
|
||||||
|
{"ptyn", required_argument, NULL, 'P'},
|
||||||
|
{"lps", required_argument, NULL, 'l'},
|
||||||
|
{"ecc", required_argument, NULL, 'e'},
|
||||||
|
{"lic", required_argument, NULL, 'L'},
|
||||||
|
{"di", required_argument, NULL, 'd'},
|
||||||
|
{"ctl", required_argument, NULL, 'C'},
|
||||||
|
#ifdef RDS2
|
||||||
|
{"img", required_argument, NULL, 'I'},
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{"version", no_argument, NULL, 'v'},
|
||||||
|
{ 0, 0, 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
memset(control_pipe, 0, 51);
|
||||||
|
|
||||||
|
keep_parsing_opts:
|
||||||
|
|
||||||
|
opt = getopt_long(argc, argv, short_opt, long_opt, NULL);
|
||||||
|
if (opt == -1) goto done_parsing_opts;
|
||||||
|
|
||||||
|
switch (opt) {
|
||||||
|
case 'i': /* pi */
|
||||||
|
rds_params.pi = strtoul(optarg, NULL, 16);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': /* ps */
|
||||||
|
memcpy(rds_params.ps, xlat((unsigned char *)optarg), PS_LENGTH);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r': /* rt1 */
|
||||||
|
memcpy(rds_params.rt1, xlat((unsigned char *)optarg), RT_LENGTH);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p': /* pty */
|
||||||
|
rds_params.pty = strtoul(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T': /* tp */
|
||||||
|
rds_params.tp = strtoul(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'A': /* af */
|
||||||
|
if (add_rds_af(&rds_params.af, strtof(optarg, NULL)) == 1) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P': /* ptyn */
|
||||||
|
memcpy(rds_params.ptyn, xlat((unsigned char *)optarg), PTYN_LENGTH);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l': /* lps */
|
||||||
|
memcpy(rds_params.lps, (unsigned char *)optarg, LPS_LENGTH);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'e': /* ecc */
|
||||||
|
rds_params.ecc = strtoul(optarg, NULL, 16);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'L': /* lic */
|
||||||
|
rds_params.lic = strtoul(optarg, NULL, 16);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C': /* ctl */
|
||||||
|
memcpy(control_pipe, optarg, 50);
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifdef RDS2
|
||||||
|
case 'I': /* img */
|
||||||
|
memcpy(rds_params.rds2_image_path, optarg, 50);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case 'v': /* version */
|
||||||
|
show_version();
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case 'h': /* help */
|
||||||
|
case '?':
|
||||||
|
default:
|
||||||
|
show_help(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto keep_parsing_opts;
|
||||||
|
|
||||||
|
done_parsing_opts:
|
||||||
|
|
||||||
|
/* Initialize pthread stuff */
|
||||||
|
pthread_mutex_init(&control_pipe_mutex, NULL);
|
||||||
|
pthread_cond_init(&control_pipe_cond, NULL);
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
|
||||||
|
/* Setup buffers */
|
||||||
|
mpx_buffer = malloc(NUM_MPX_FRAMES_IN * 2 * sizeof(float));
|
||||||
|
out_buffer = malloc(NUM_MPX_FRAMES_OUT * 2 * sizeof(float));
|
||||||
|
dev_out = malloc(NUM_MPX_FRAMES_OUT * 2 * sizeof(int16_t) * sizeof(char));
|
||||||
|
|
||||||
|
/* Gracefully stop the encoder on SIGINT or SIGTERM */
|
||||||
|
signal(SIGINT, stop);
|
||||||
|
signal(SIGTERM, stop);
|
||||||
|
|
||||||
|
/* Initialize the baseband generator */
|
||||||
|
fm_mpx_init(MPX_SAMPLE_RATE);
|
||||||
|
set_output_volume(volume);
|
||||||
|
|
||||||
|
/* Initialize the RDS modulator */
|
||||||
|
init_rds_encoder(rds_params);
|
||||||
|
|
||||||
|
/* PASIMPLE format */
|
||||||
|
format.format = PA_SAMPLE_S16LE;
|
||||||
|
format.channels = 1;
|
||||||
|
format.rate = OUTPUT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
device = pa_simple_new(
|
||||||
|
NULL, // Default PulseAudio server
|
||||||
|
"MiniRDS", // Application name
|
||||||
|
PA_STREAM_PLAYBACK, // Direction (playback)
|
||||||
|
"RDS", // Default device
|
||||||
|
"RDS Generator", // Stream description
|
||||||
|
&format, // Sample format
|
||||||
|
NULL, // Default channel map
|
||||||
|
NULL, // Default buffering attributes
|
||||||
|
NULL // Error variable
|
||||||
|
);
|
||||||
|
if (device == NULL) {
|
||||||
|
fprintf(stderr, "Error: cannot open sound device.\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SRC out (MPX -> output) */
|
||||||
|
memset(&src_data, 0, sizeof(SRC_DATA));
|
||||||
|
src_data.input_frames = NUM_MPX_FRAMES_IN;
|
||||||
|
src_data.output_frames = NUM_MPX_FRAMES_OUT;
|
||||||
|
src_data.src_ratio =
|
||||||
|
(double)OUTPUT_SAMPLE_RATE / (double)MPX_SAMPLE_RATE;
|
||||||
|
src_data.data_in = mpx_buffer;
|
||||||
|
src_data.data_out = out_buffer;
|
||||||
|
|
||||||
|
r = resampler_init(&src_state, 1);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Could not create output resampler.\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize the control pipe reader */
|
||||||
|
if (control_pipe[0]) {
|
||||||
|
if (open_control_pipe(control_pipe) == 0) {
|
||||||
|
fprintf(stderr, "Reading control commands on %s.\n", control_pipe);
|
||||||
|
/* Create control pipe polling worker */
|
||||||
|
r = pthread_create(&control_pipe_thread, &attr, control_pipe_worker, NULL);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Could not create control pipe thread.\n");
|
||||||
|
control_pipe[0] = 0;
|
||||||
|
goto exit;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Created control pipe thread.\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Failed to open control pipe: %s.\n", control_pipe);
|
||||||
|
control_pipe[0] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
fm_rds_get_frames(mpx_buffer, NUM_MPX_FRAMES_IN);
|
||||||
|
|
||||||
|
if (resample(src_state, src_data, &frames) < 0) break;
|
||||||
|
|
||||||
|
float2char2channel(out_buffer, dev_out, frames);
|
||||||
|
|
||||||
|
/* num_bytes = audio frames( * channels) * bytes per sample */
|
||||||
|
if (pa_simple_write(device, dev_out, frames * sizeof(int16_t), NULL) != 0) {
|
||||||
|
fprintf(stderr, "Error: could not play audio.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop_rds) {
|
||||||
|
fprintf(stderr, "Stopping the loop...\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resampler_exit(src_state);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (control_pipe[0]) {
|
||||||
|
/* shut down threads */
|
||||||
|
fprintf(stderr, "Waiting for pipe thread to shut down.\n");
|
||||||
|
pthread_cond_signal(&control_pipe_cond);
|
||||||
|
pthread_join(control_pipe_thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
pa_simple_free(device);
|
||||||
|
fm_mpx_exit();
|
||||||
|
exit_rds_encoder();
|
||||||
|
|
||||||
|
free(mpx_buffer);
|
||||||
|
free(out_buffer);
|
||||||
|
free(dev_out);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
202
src/modulator.c
Normal file
202
src/modulator.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2021 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "rds.h"
|
||||||
|
#ifdef RDS2
|
||||||
|
#include "rds2.h"
|
||||||
|
#endif
|
||||||
|
#include "fm_mpx.h"
|
||||||
|
#include "waveforms.h"
|
||||||
|
#include "modulator.h"
|
||||||
|
|
||||||
|
static struct rds_t **rds_ctx;
|
||||||
|
static float **waveform;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the RDS objects
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void init_rds_objects() {
|
||||||
|
rds_ctx = malloc(NUM_STREAMS * sizeof(struct rds_t));
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < NUM_STREAMS; i++) {
|
||||||
|
rds_ctx[i] = malloc(sizeof(struct rds_t));
|
||||||
|
rds_ctx[i]->bit_buffer = malloc(BITS_PER_GROUP);
|
||||||
|
rds_ctx[i]->sample_buffer =
|
||||||
|
malloc(SAMPLE_BUFFER_SIZE * sizeof(float));
|
||||||
|
|
||||||
|
#ifdef RDS2_SYMBOL_SHIFTING
|
||||||
|
/*
|
||||||
|
* symbol shifting to reduce total power of aggregate carriers
|
||||||
|
*
|
||||||
|
* see:
|
||||||
|
* https://ietresearch.onlinelibrary.wiley.com/doi/pdf/10.1049/el.2019.0292
|
||||||
|
* for more information
|
||||||
|
*/
|
||||||
|
switch (i) {
|
||||||
|
case 1:
|
||||||
|
/* time offset of 1/2 */
|
||||||
|
rds_ctx[i]->symbol_shift = SAMPLES_PER_BIT / 2;
|
||||||
|
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/* time offset of 1/4 */
|
||||||
|
rds_ctx[i]->symbol_shift = SAMPLES_PER_BIT / 4;
|
||||||
|
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
/* time offset of 3/4 */
|
||||||
|
rds_ctx[i]->symbol_shift = (SAMPLES_PER_BIT / 4) * 3;
|
||||||
|
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||||
|
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||||
|
break;
|
||||||
|
default: /* stream 0 */
|
||||||
|
/* no time offset */
|
||||||
|
rds_ctx[i]->symbol_shift = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
rds_ctx[i]->symbol_shift_buf_idx = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
waveform = malloc(2 * sizeof(float));
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
|
waveform[i] = malloc(FILTER_SIZE * sizeof(float));
|
||||||
|
for (uint16_t j = 0; j < FILTER_SIZE; j++) {
|
||||||
|
waveform[i][j] = i ?
|
||||||
|
+waveform_biphase[j] : -waveform_biphase[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit_rds_objects() {
|
||||||
|
for (uint8_t i = 0; i < NUM_STREAMS; i++) {
|
||||||
|
int has_symbol_shift = rds_ctx[i]->symbol_shift;
|
||||||
|
if (has_symbol_shift) {
|
||||||
|
free(rds_ctx[i]->symbol_shift_buf);
|
||||||
|
}
|
||||||
|
free(rds_ctx[i]->sample_buffer);
|
||||||
|
free(rds_ctx[i]->bit_buffer);
|
||||||
|
free(rds_ctx[i]);
|
||||||
|
}
|
||||||
|
free(rds_ctx);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
|
free(waveform[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(waveform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get an RDS sample. This generates the envelope of the waveform using
|
||||||
|
* pre-generated elementary waveform samples.
|
||||||
|
*/
|
||||||
|
#ifdef RDS2
|
||||||
|
float get_rds_sample(uint8_t stream_num, uint8_t rds2tunneling) {
|
||||||
|
#else
|
||||||
|
float get_rds_sample(uint8_t stream_num) {
|
||||||
|
#endif
|
||||||
|
struct rds_t *rds;
|
||||||
|
uint16_t idx;
|
||||||
|
float *cur_waveform;
|
||||||
|
float sample;
|
||||||
|
|
||||||
|
/* select context */
|
||||||
|
rds = rds_ctx[stream_num];
|
||||||
|
|
||||||
|
if (rds->sample_count == SAMPLES_PER_BIT) {
|
||||||
|
if (rds->bit_pos == BITS_PER_GROUP) {
|
||||||
|
#ifdef RDS2
|
||||||
|
if(!rds2tunneling) {
|
||||||
|
if (stream_num > 0) {
|
||||||
|
get_rds2_bits(stream_num, rds->bit_buffer);
|
||||||
|
} else {
|
||||||
|
get_rds_bits(rds->bit_buffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
get_rds_bits(rds->bit_buffer);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
get_rds_bits(rds->bit_buffer);
|
||||||
|
#endif
|
||||||
|
rds->bit_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do differential encoding */
|
||||||
|
rds->cur_bit = rds->bit_buffer[rds->bit_pos++];
|
||||||
|
rds->prev_output = rds->cur_output;
|
||||||
|
rds->cur_output = rds->prev_output ^ rds->cur_bit;
|
||||||
|
|
||||||
|
idx = rds->in_sample_index;
|
||||||
|
cur_waveform = waveform[rds->cur_output];
|
||||||
|
|
||||||
|
uint16_t buffer_remaining = SAMPLE_BUFFER_SIZE - idx;
|
||||||
|
if (buffer_remaining >= FILTER_SIZE) {
|
||||||
|
// Process continuous chunk
|
||||||
|
for (uint16_t i = 0; i < FILTER_SIZE; i++) {
|
||||||
|
rds->sample_buffer[idx + i] += cur_waveform[i];
|
||||||
|
}
|
||||||
|
idx += FILTER_SIZE;
|
||||||
|
} else {
|
||||||
|
// Process in two parts to handle wrapping
|
||||||
|
for (uint16_t i = 0; i < buffer_remaining; i++) {
|
||||||
|
rds->sample_buffer[idx + i] += cur_waveform[i];
|
||||||
|
}
|
||||||
|
for (uint16_t i = 0; i < FILTER_SIZE - buffer_remaining; i++) {
|
||||||
|
rds->sample_buffer[i] += cur_waveform[buffer_remaining + i];
|
||||||
|
}
|
||||||
|
idx = FILTER_SIZE - buffer_remaining;
|
||||||
|
}
|
||||||
|
rds->in_sample_index += SAMPLES_PER_BIT;
|
||||||
|
if (rds->in_sample_index == SAMPLE_BUFFER_SIZE)
|
||||||
|
rds->in_sample_index = 0;
|
||||||
|
|
||||||
|
rds->sample_count = 0;
|
||||||
|
}
|
||||||
|
rds->sample_count++;
|
||||||
|
|
||||||
|
if (rds->symbol_shift) {
|
||||||
|
rds->symbol_shift_buf[rds->symbol_shift_buf_idx++] =
|
||||||
|
rds->sample_buffer[rds->out_sample_index];
|
||||||
|
|
||||||
|
if (rds->symbol_shift_buf_idx == rds->symbol_shift)
|
||||||
|
rds->symbol_shift_buf_idx = 0;
|
||||||
|
|
||||||
|
sample = rds->symbol_shift_buf[rds->symbol_shift_buf_idx];
|
||||||
|
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
sample = rds->sample_buffer[rds->out_sample_index];
|
||||||
|
done:
|
||||||
|
rds->sample_buffer[rds->out_sample_index++] = 0;
|
||||||
|
if (rds->out_sample_index == SAMPLE_BUFFER_SIZE)
|
||||||
|
rds->out_sample_index = 0;
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
43
src/modulator.h
Normal file
43
src/modulator.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2021 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef RDS2
|
||||||
|
#define NUM_STREAMS 4
|
||||||
|
#else
|
||||||
|
#define NUM_STREAMS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* RDS signal context */
|
||||||
|
typedef struct rds_t {
|
||||||
|
uint8_t *bit_buffer; /* BITS_PER_GROUP */
|
||||||
|
uint8_t bit_pos;
|
||||||
|
float *sample_buffer; /* SAMPLE_BUFFER_SIZE */
|
||||||
|
uint8_t prev_output;
|
||||||
|
uint8_t cur_output;
|
||||||
|
uint8_t cur_bit;
|
||||||
|
uint8_t sample_count;
|
||||||
|
uint16_t in_sample_index;
|
||||||
|
uint16_t out_sample_index;
|
||||||
|
uint8_t symbol_shift;
|
||||||
|
float *symbol_shift_buf;
|
||||||
|
uint8_t symbol_shift_buf_idx;
|
||||||
|
} rds_t;
|
||||||
|
|
||||||
|
extern void init_rds_objects();
|
||||||
|
extern void exit_rds_objects();
|
||||||
|
|
||||||
108
src/osc.c
Normal file
108
src/osc.c
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "osc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Code for MPX oscillator
|
||||||
|
*
|
||||||
|
* This uses lookup tables to speed up the waveform generation
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DDS function generator
|
||||||
|
*
|
||||||
|
* Create wave constants for a given frequency
|
||||||
|
*/
|
||||||
|
static void create_wave(uint32_t rate, float freq,
|
||||||
|
float *sin_wave, float *cos_wave, uint16_t *max_phase) {
|
||||||
|
double sin_sample, cos_sample;
|
||||||
|
/* used to determine if we have completed a cycle */
|
||||||
|
uint16_t periods = round((double)rate/freq);
|
||||||
|
const double w = M_2PI * freq / rate;
|
||||||
|
|
||||||
|
/* First value of a sine wave is always 0 */
|
||||||
|
*sin_wave++ = 0.0f;
|
||||||
|
*cos_wave++ = 1.0f;
|
||||||
|
|
||||||
|
for(uint16_t i = 1; i < periods; i++) {
|
||||||
|
sin_sample = sin(w * i);
|
||||||
|
cos_sample = cos(w * i);
|
||||||
|
*sin_wave++ = (float)sin_sample;
|
||||||
|
*cos_wave++ = (float)cos_sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
*max_phase = periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Oscillator object initialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void osc_init(struct osc_t *osc, uint32_t sample_rate, float freq) {
|
||||||
|
|
||||||
|
/* sample rate for the objects */
|
||||||
|
osc->sample_rate = sample_rate;
|
||||||
|
|
||||||
|
/* waveform tables */
|
||||||
|
osc->sin_wave = malloc(osc->sample_rate * sizeof(float));
|
||||||
|
osc->cos_wave = malloc(osc->sample_rate * sizeof(float));
|
||||||
|
|
||||||
|
/* set current position to 0 */
|
||||||
|
osc->cur = 0;
|
||||||
|
|
||||||
|
/* create waveform data and load into lookup tables */
|
||||||
|
create_wave(osc->sample_rate, freq,
|
||||||
|
osc->sin_wave, osc->cos_wave,
|
||||||
|
&osc->max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a single waveform sample
|
||||||
|
*
|
||||||
|
* Cosine is needed for SSB generation
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
float osc_get_cos(struct osc_t *osc) {
|
||||||
|
return osc->cos_wave[osc->cur];
|
||||||
|
}
|
||||||
|
|
||||||
|
float osc_get_sin(struct osc_t *osc) {
|
||||||
|
return osc->sin_wave[osc->cur];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shift the oscillator to the next position
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void osc_update_pos(struct osc_t *osc) {
|
||||||
|
if (++osc->cur == osc->max) osc->cur = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unload waveform tables
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void osc_exit(struct osc_t *osc) {
|
||||||
|
free(osc->sin_wave);
|
||||||
|
free(osc->cos_wave);
|
||||||
|
osc->cur = 0;
|
||||||
|
osc->max = 0;
|
||||||
|
}
|
||||||
50
src/osc.h
Normal file
50
src/osc.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* context for MPX oscillator */
|
||||||
|
typedef struct osc_t {
|
||||||
|
/* the sample rate at which the oscillator operates */
|
||||||
|
uint32_t sample_rate;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* frequency for this instance
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
float freq;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arrays of carrier wave constants
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
float *sin_wave_out_of_phase;
|
||||||
|
float *sin_wave;
|
||||||
|
float *cos_wave;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wave phase
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
uint16_t cur;
|
||||||
|
uint16_t max;
|
||||||
|
} osc_t;
|
||||||
|
|
||||||
|
extern void osc_init(struct osc_t *osc, uint32_t sample_rate, const float freq);
|
||||||
|
extern float osc_get_sin(struct osc_t *osc);
|
||||||
|
extern float osc_get_cos(struct osc_t *osc);
|
||||||
|
extern void osc_update_pos(struct osc_t *osc);
|
||||||
|
extern void osc_exit(struct osc_t *osc);
|
||||||
778
src/rds.c
Normal file
778
src/rds.c
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "rds.h"
|
||||||
|
#include "modulator.h"
|
||||||
|
#ifdef RDS2
|
||||||
|
#include "rds2.h"
|
||||||
|
#endif
|
||||||
|
#include "lib.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static struct rds_params_t rds_data;
|
||||||
|
|
||||||
|
/* RDS data controls */
|
||||||
|
static struct {
|
||||||
|
/* Slow Labeling Codes */
|
||||||
|
uint8_t ecclic_enabled;
|
||||||
|
uint8_t ecc_or_lic;
|
||||||
|
/* Program Item Number */
|
||||||
|
uint8_t pin_enabled;
|
||||||
|
|
||||||
|
/* Program Service */
|
||||||
|
uint8_t ps_update;
|
||||||
|
uint8_t tps_update;
|
||||||
|
|
||||||
|
/* Radio Text*/
|
||||||
|
uint8_t rt1_enabled;
|
||||||
|
uint8_t rt_update;
|
||||||
|
uint8_t rt_ab;
|
||||||
|
uint8_t rt_segments;
|
||||||
|
|
||||||
|
/* PTYN */
|
||||||
|
uint8_t ptyn_enabled;
|
||||||
|
uint8_t ptyn_update;
|
||||||
|
uint8_t ptyn_ab;
|
||||||
|
|
||||||
|
/* Long PS */
|
||||||
|
uint8_t lps_on;
|
||||||
|
uint8_t lps_update;
|
||||||
|
uint8_t lps_segments;
|
||||||
|
|
||||||
|
uint8_t custom_group_in;
|
||||||
|
uint16_t custom_group[GROUP_LENGTH];
|
||||||
|
|
||||||
|
/* TODO: add UDG */
|
||||||
|
} rds_state;
|
||||||
|
|
||||||
|
#ifdef ODA
|
||||||
|
/* ODA */
|
||||||
|
static struct rds_oda_t odas[MAX_ODAS];
|
||||||
|
static struct {
|
||||||
|
uint8_t current;
|
||||||
|
uint8_t count;
|
||||||
|
} oda_state;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
/* RT+ */
|
||||||
|
static struct {
|
||||||
|
uint8_t group;
|
||||||
|
uint8_t enabled;
|
||||||
|
uint8_t running;
|
||||||
|
uint8_t toggle;
|
||||||
|
uint8_t type[2];
|
||||||
|
uint8_t start[2];
|
||||||
|
uint8_t len[2];
|
||||||
|
} rtplus_cfg;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ODA
|
||||||
|
static void register_oda(uint8_t group, uint16_t aid, uint16_t scb) {
|
||||||
|
|
||||||
|
if (oda_state.count >= MAX_ODAS) return; /* can't accept more ODAs */
|
||||||
|
|
||||||
|
odas[oda_state.count].group = group;
|
||||||
|
odas[oda_state.count].aid = aid;
|
||||||
|
odas[oda_state.count].scb = scb;
|
||||||
|
oda_state.count++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Get the next AF entry
|
||||||
|
*/
|
||||||
|
static uint16_t get_next_af() {
|
||||||
|
static uint8_t af_state;
|
||||||
|
uint16_t out;
|
||||||
|
|
||||||
|
if (rds_data.af.num_afs) {
|
||||||
|
if (af_state == 0) {
|
||||||
|
out = (AF_CODE_NUM_AFS_BASE + rds_data.af.num_afs) << 8;
|
||||||
|
out |= rds_data.af.afs[0];
|
||||||
|
af_state += 1;
|
||||||
|
} else {
|
||||||
|
out = rds_data.af.afs[af_state] << 8;
|
||||||
|
if (rds_data.af.afs[af_state + 1])
|
||||||
|
out |= rds_data.af.afs[af_state + 1];
|
||||||
|
else
|
||||||
|
out |= AF_CODE_FILLER;
|
||||||
|
af_state += 2;
|
||||||
|
}
|
||||||
|
if (af_state >= rds_data.af.num_entries) af_state = 0;
|
||||||
|
} else {
|
||||||
|
out = AF_CODE_NO_AF << 8 | AF_CODE_FILLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PS group (0A) */
|
||||||
|
static void get_rds_ps_group(uint16_t *blocks) {
|
||||||
|
static unsigned char ps_text[PS_LENGTH];
|
||||||
|
static unsigned char tps_text[PS_LENGTH];
|
||||||
|
static uint8_t ps_csegment;
|
||||||
|
|
||||||
|
if (ps_csegment == 0 && rds_state.ps_update) {
|
||||||
|
memcpy(ps_text, rds_data.ps, PS_LENGTH);
|
||||||
|
rds_state.ps_update = 0; /* rewind */
|
||||||
|
}
|
||||||
|
if(ps_csegment == 0 && rds_state.tps_update) {
|
||||||
|
memcpy(tps_text, rds_data.tps, PS_LENGTH);
|
||||||
|
rds_state.tps_update = 0; /* rewind */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TA */
|
||||||
|
blocks[1] |= rds_data.ta << 4;
|
||||||
|
|
||||||
|
/* MS */
|
||||||
|
blocks[1] |= rds_data.ms << 3;
|
||||||
|
|
||||||
|
/* DI */
|
||||||
|
blocks[1] |= ((rds_data.di >> (3 - ps_csegment)) & INT8_0) << 2; /* DI is a 4 bit number, we also have 4 segments, so we send a bit of the di number depending on the segment, so 0b0101 would be like: 0 1 0 1*/
|
||||||
|
|
||||||
|
/* PS segment address */
|
||||||
|
blocks[1] |= ps_csegment;
|
||||||
|
|
||||||
|
/* AF */
|
||||||
|
blocks[2] = get_next_af();
|
||||||
|
|
||||||
|
/* PS */
|
||||||
|
if(rds_data.ta && rds_data.traffic_ps_on) {
|
||||||
|
blocks[3] = tps_text[ps_csegment * 2] << 8 | tps_text[ps_csegment * 2 + 1];
|
||||||
|
} else {
|
||||||
|
/* TODO: Add DPS */
|
||||||
|
blocks[3] = ps_text[ps_csegment * 2] << 8 | ps_text[ps_csegment * 2 + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
ps_csegment++;
|
||||||
|
if (ps_csegment >= 4) ps_csegment = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RT group (2A) */
|
||||||
|
static uint8_t get_rds_rt_group(uint16_t *blocks) {
|
||||||
|
static unsigned char rt_text[RT_LENGTH];
|
||||||
|
static uint8_t rt_state;
|
||||||
|
|
||||||
|
if (rds_state.rt_update) {
|
||||||
|
memcpy(rt_text, rds_data.rt1, RT_LENGTH);
|
||||||
|
rds_state.rt_ab ^= 1;
|
||||||
|
rds_state.rt_update = 0;
|
||||||
|
rt_state = 0; /* rewind when new RT arrives */
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rt_text[0] == '\r' || !rds_state.rt1_enabled) {
|
||||||
|
/* RT strings lesser than 64 in size should have been ended with a \r if a return is on [0] then that means that our string is empty and thus we can not generate this group*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[1] |= 2 << 12;
|
||||||
|
blocks[1] |= rds_state.rt_ab << 4;
|
||||||
|
blocks[1] |= rt_state;
|
||||||
|
blocks[2] = rt_text[rt_state * 4 ] << 8;
|
||||||
|
blocks[2] |= rt_text[rt_state * 4 + 1];
|
||||||
|
blocks[3] = rt_text[rt_state * 4 + 2] << 8;
|
||||||
|
blocks[3] |= rt_text[rt_state * 4 + 3];
|
||||||
|
|
||||||
|
rt_state++;
|
||||||
|
if (rt_state >= rds_state.rt_segments) rt_state = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ODA group (3A)
|
||||||
|
*/
|
||||||
|
#ifdef ODA
|
||||||
|
static void get_rds_oda_group(uint16_t *blocks) {
|
||||||
|
/* select ODA */
|
||||||
|
struct rds_oda_t this_oda = odas[oda_state.current];
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
if(this_oda.aid == ODA_AID_RTPLUS && rtplus_cfg.enabled == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
blocks[1] |= 3 << 12;
|
||||||
|
|
||||||
|
blocks[1] |= GET_GROUP_TYPE(this_oda.group) << 1;
|
||||||
|
blocks[1] |= GET_GROUP_VER(this_oda.group);
|
||||||
|
blocks[2] = this_oda.scb;
|
||||||
|
blocks[3] = this_oda.aid;
|
||||||
|
|
||||||
|
oda_state.current++;
|
||||||
|
if (oda_state.current >= oda_state.count) oda_state.current = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static uint8_t get_rds_ct_group(uint16_t *blocks) {
|
||||||
|
static uint8_t latest_minutes;
|
||||||
|
struct tm *utc, *local_time;
|
||||||
|
time_t now;
|
||||||
|
uint8_t l;
|
||||||
|
uint32_t mjd;
|
||||||
|
int16_t offset;
|
||||||
|
|
||||||
|
/* Check time */
|
||||||
|
now = time(NULL);
|
||||||
|
utc = gmtime(&now);
|
||||||
|
|
||||||
|
if (utc->tm_min != latest_minutes) {
|
||||||
|
/* Generate CT group */
|
||||||
|
latest_minutes = utc->tm_min;
|
||||||
|
|
||||||
|
l = utc->tm_mon <= 1 ? 1 : 0;
|
||||||
|
mjd = 14956 + utc->tm_mday +
|
||||||
|
(uint32_t)((utc->tm_year - l) * 365.25f) +
|
||||||
|
(uint32_t)((utc->tm_mon + (1+1) + l * 12) * 30.6001f);
|
||||||
|
|
||||||
|
blocks[1] |= 4 << 12 | (mjd >> 15);
|
||||||
|
blocks[2] = (mjd << 1) | (utc->tm_hour >> 4);
|
||||||
|
blocks[3] = (utc->tm_hour & INT16_L4) << 12 | utc->tm_min << 6;
|
||||||
|
|
||||||
|
/* get local time (for the offset) */
|
||||||
|
local_time = localtime(&now);
|
||||||
|
|
||||||
|
/* tm_gmtoff doesn't exist in POSIX but __tm_gmtoff does */
|
||||||
|
offset = local_time->__tm_gmtoff / (30 * 60); /*__tm_gmtoff is time in seconds from utc, so utc + __tm_gmtoff = local time*/
|
||||||
|
if (offset < 0) blocks[3] |= 1 << 5; /* if less than 0 then set the flag*/
|
||||||
|
blocks[3] |= abs(offset); /* offset should be halfs of a hour, so offset 1 = 30 minutes, 2 = 1 hour*/
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PTYN group (10A) */
|
||||||
|
static void get_rds_ptyn_group(uint16_t *blocks) {
|
||||||
|
static unsigned char ptyn_text[PTYN_LENGTH];
|
||||||
|
static uint8_t ptyn_state;
|
||||||
|
|
||||||
|
if (ptyn_state == 0 && rds_state.ptyn_update) {
|
||||||
|
memcpy(ptyn_text, rds_data.ptyn, PTYN_LENGTH);
|
||||||
|
rds_state.ptyn_ab ^= 1;
|
||||||
|
rds_state.ptyn_update = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[1] |= 10 << 12 | ptyn_state;
|
||||||
|
blocks[1] |= rds_state.ptyn_ab << 4; /* no one knew about ptyn's ab until i saw sdr++'s decoder, it had decoded ptyn but not show it? it also did decode the ab flag */
|
||||||
|
blocks[2] = ptyn_text[ptyn_state * 4 + 0] << 8;
|
||||||
|
blocks[2] |= ptyn_text[ptyn_state * 4 + 1];
|
||||||
|
blocks[3] = ptyn_text[ptyn_state * 4 + 2] << 8;
|
||||||
|
blocks[3] |= ptyn_text[ptyn_state * 4 + 3];
|
||||||
|
|
||||||
|
ptyn_state++;
|
||||||
|
if (ptyn_state == 2) ptyn_state = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Long PS group (15A) */
|
||||||
|
static void get_rds_lps_group(uint16_t *blocks) {
|
||||||
|
static unsigned char lps_text[LPS_LENGTH];
|
||||||
|
static uint8_t lps_state;
|
||||||
|
|
||||||
|
if (lps_state == 0 && rds_state.lps_update) {
|
||||||
|
memcpy(lps_text, rds_data.lps, LPS_LENGTH);
|
||||||
|
rds_state.lps_update = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[1] |= 15 << 12 | lps_state;
|
||||||
|
/* does lps have an ab? */
|
||||||
|
blocks[2] = lps_text[lps_state * 4 ] << 8;
|
||||||
|
blocks[2] |= lps_text[lps_state * 4 + 1];
|
||||||
|
blocks[3] = lps_text[lps_state * 4 + 2] << 8;
|
||||||
|
blocks[3] |= lps_text[lps_state * 4 + 3];
|
||||||
|
|
||||||
|
lps_state++;
|
||||||
|
if (lps_state == rds_state.lps_segments) lps_state = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_rds_ecc_group(uint16_t *blocks) {
|
||||||
|
blocks[1] |= 1 << 12;
|
||||||
|
if(rds_state.ecclic_enabled) {
|
||||||
|
blocks[2] = rds_data.ecc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rds_state.pin_enabled) {
|
||||||
|
blocks[3] = rds_data.pin_day << 11; /* first 5 bits */
|
||||||
|
blocks[3] |= rds_data.pin_hour << 6; /* 5-10 bits from start */
|
||||||
|
blocks[3] |= rds_data.pin_minute; /* 10-16 bits from start */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_rds_lic_group(uint16_t *blocks) {
|
||||||
|
blocks[1] |= 1 << 12;
|
||||||
|
if(rds_state.ecclic_enabled) {
|
||||||
|
blocks[2] = 0x3000; /* 0b0011000000000000 first 1 bit is the linkage actuator, the 3 after are variant codes which is 3 in this case after theres space for data */
|
||||||
|
blocks[2] |= rds_data.lic;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rds_state.pin_enabled) {
|
||||||
|
/* pin is also in lic as it also is a 1a group*/
|
||||||
|
blocks[3] = rds_data.pin_day << 11;
|
||||||
|
blocks[3] |= rds_data.pin_hour << 6;
|
||||||
|
blocks[3] |= rds_data.pin_minute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RT+ */
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
static void init_rtplus(uint8_t group) {
|
||||||
|
register_oda(group, ODA_AID_RTPLUS, 0);
|
||||||
|
rtplus_cfg.group = group;
|
||||||
|
rtplus_cfg.enabled = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
/* RT+ group */
|
||||||
|
static void get_rds_rtplus_group(uint16_t *blocks) {
|
||||||
|
/* RT+ block format */
|
||||||
|
blocks[1] |= GET_GROUP_TYPE(rtplus_cfg.group) << 12;
|
||||||
|
blocks[1] |= GET_GROUP_VER(rtplus_cfg.group) << 11;
|
||||||
|
blocks[1] |= rtplus_cfg.toggle << 4 | rtplus_cfg.running << 3;
|
||||||
|
blocks[1] |= (rtplus_cfg.type[0] & INT8_U5) >> 3;
|
||||||
|
|
||||||
|
blocks[2] = (rtplus_cfg.type[0] & INT8_L3) << 13;
|
||||||
|
blocks[2] |= (rtplus_cfg.start[0] & INT8_L6) << 7;
|
||||||
|
blocks[2] |= (rtplus_cfg.len[0] & INT8_L6) << 1;
|
||||||
|
blocks[2] |= (rtplus_cfg.type[1] & INT8_U3) >> 5;
|
||||||
|
|
||||||
|
blocks[3] = (rtplus_cfg.type[1] & INT8_L5) << 11;
|
||||||
|
blocks[3] |= (rtplus_cfg.start[1] & INT8_L6) << 5;
|
||||||
|
blocks[3] |= rtplus_cfg.len[1] & INT8_L5;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Lower priority groups are placed in a subsequence
|
||||||
|
*/
|
||||||
|
static uint8_t get_rds_other_groups(uint16_t *blocks) {
|
||||||
|
static uint8_t group_counter[GROUP_15B];
|
||||||
|
|
||||||
|
/* Type 1A groups */
|
||||||
|
if (rds_data.ecc || rds_data.lic) {
|
||||||
|
if (++group_counter[GROUP_1A] >= 22) {
|
||||||
|
group_counter[GROUP_1A] = 0;
|
||||||
|
if(rds_data.ecc && rds_data.lic) {
|
||||||
|
if(rds_state.ecc_or_lic == 0) {
|
||||||
|
get_rds_ecc_group(blocks);
|
||||||
|
} else {
|
||||||
|
get_rds_lic_group(blocks);
|
||||||
|
}
|
||||||
|
rds_state.ecc_or_lic ^= 1;
|
||||||
|
} else if(rds_data.ecc) {
|
||||||
|
get_rds_ecc_group(blocks);
|
||||||
|
} else if(rds_data.ecc) {
|
||||||
|
get_rds_lic_group(blocks);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type 3A groups */
|
||||||
|
#ifdef ODA
|
||||||
|
if (oda_state.count) {
|
||||||
|
if (++group_counter[GROUP_3A] >= 25) {
|
||||||
|
group_counter[GROUP_3A] = 0;
|
||||||
|
get_rds_oda_group(blocks);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Type 10A groups */
|
||||||
|
if (rds_state.ptyn_enabled && rds_data.ptyn[0]) {
|
||||||
|
if (++group_counter[GROUP_10A] >= 13) {
|
||||||
|
group_counter[GROUP_10A] = 0;
|
||||||
|
/* Do not generate a 10A group if PTYN is off */
|
||||||
|
get_rds_ptyn_group(blocks);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RT+ groups */
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
if (++group_counter[rtplus_cfg.group] >= 30) {
|
||||||
|
group_counter[rtplus_cfg.group] = 0;
|
||||||
|
if(rtplus_cfg.enabled == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
get_rds_rtplus_group(blocks);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Codes a group once every 3 groups
|
||||||
|
* Ex: 0A, 2A, 0A, 12A, 2A, 0A, 2A, 12A, etc
|
||||||
|
*/
|
||||||
|
static uint8_t get_rds_long_text_groups(uint16_t *blocks) {
|
||||||
|
static uint8_t group_selector = 0;
|
||||||
|
static uint8_t group_slot_counter = 0;
|
||||||
|
|
||||||
|
/* exit early until the 4th call */
|
||||||
|
if (++group_slot_counter < 4)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* reset the slot counter */
|
||||||
|
group_slot_counter = 0;
|
||||||
|
|
||||||
|
/* 3:1 ratio */
|
||||||
|
switch (group_selector) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3: /* Long PS */
|
||||||
|
if (rds_data.lps[0] && rds_state.lps_on) {
|
||||||
|
get_rds_lps_group(blocks);
|
||||||
|
goto group_coded;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if no group was coded */
|
||||||
|
if (++group_selector >= 8) group_selector = 0;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
group_coded:
|
||||||
|
if (++group_selector >= 8) group_selector = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get a custom group if there is one*/
|
||||||
|
static uint8_t get_rds_custom_groups(uint16_t *blocks) {
|
||||||
|
if(rds_state.custom_group_in) {
|
||||||
|
rds_state.custom_group_in = 0;
|
||||||
|
blocks[0] = rds_state.custom_group[0];
|
||||||
|
blocks[1] = rds_state.custom_group[1];
|
||||||
|
blocks[2] = rds_state.custom_group[2];
|
||||||
|
blocks[3] = rds_state.custom_group[3];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates an RDS group.
|
||||||
|
* This generates sequences of the form 0A, 2A, 0A, 2A, 0A, 2A, etc.
|
||||||
|
*/
|
||||||
|
static void get_rds_group(uint16_t *blocks) {
|
||||||
|
static uint8_t state;
|
||||||
|
|
||||||
|
/* Basic block data */
|
||||||
|
blocks[0] = rds_data.pi;
|
||||||
|
blocks[1] = rds_data.tp << 10;
|
||||||
|
blocks[1] |= rds_data.pty << 5;
|
||||||
|
blocks[2] = 0;
|
||||||
|
blocks[3] = 0;
|
||||||
|
|
||||||
|
/* Generate block content */
|
||||||
|
|
||||||
|
/* CT (clock time) has priority over other group types */
|
||||||
|
if (rds_data.ct && get_rds_ct_group(blocks)) {
|
||||||
|
goto group_coded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next to top priority, if want top then just disable CT i guess */
|
||||||
|
if(get_rds_custom_groups(blocks)) {
|
||||||
|
goto group_coded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Longer text groups get medium priority */
|
||||||
|
if (get_rds_long_text_groups(blocks)) {
|
||||||
|
goto group_coded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Other groups */
|
||||||
|
if (get_rds_other_groups(blocks)) {
|
||||||
|
goto group_coded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard group sequence
|
||||||
|
This group sequence is way better than the offcial one, official goes like: 0A 2A which is shitty here it is 0A 0A 0A 0A 2A 2A 2A 2A (2A) so we're in sequence and for example immiedietly get PS and quite fast get RT */
|
||||||
|
if(state < 6) {
|
||||||
|
/* 0A */
|
||||||
|
get_rds_ps_group(blocks);
|
||||||
|
state++;
|
||||||
|
} else if(state >= 6) {
|
||||||
|
/* 2A */
|
||||||
|
if(!get_rds_rt_group(blocks)) { /* try to generate 2A if can't generate 2A than that means our text is empty so we generate PS instead of wasting groups on nothing*/
|
||||||
|
get_rds_ps_group(blocks);
|
||||||
|
}
|
||||||
|
state++;
|
||||||
|
}
|
||||||
|
if (state >= 12) state = 0;
|
||||||
|
|
||||||
|
group_coded:
|
||||||
|
/* for version B groups. good for custom groups */
|
||||||
|
if (IS_TYPE_B(blocks)) {
|
||||||
|
blocks[2] = rds_data.pi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_rds_bits(uint8_t *bits) {
|
||||||
|
static uint16_t out_blocks[GROUP_LENGTH];
|
||||||
|
get_rds_group(out_blocks);
|
||||||
|
#ifdef RDS2
|
||||||
|
add_checkwords(out_blocks, bits, false);
|
||||||
|
#else
|
||||||
|
add_checkwords(out_blocks, bits);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_rds_encoder(struct rds_params_t rds_params) {
|
||||||
|
|
||||||
|
/* AF */
|
||||||
|
if (rds_params.af.num_afs) {
|
||||||
|
set_rds_af(rds_params.af);
|
||||||
|
fprintf(stderr, show_af_list(rds_params.af));
|
||||||
|
}
|
||||||
|
|
||||||
|
set_rds_pi(rds_params.pi);
|
||||||
|
set_rds_ps(rds_params.ps);
|
||||||
|
rds_state.rt_ab = 1;
|
||||||
|
set_rds_rt1_enabled(1);
|
||||||
|
set_rds_rt1(rds_params.rt1);
|
||||||
|
set_rds_pty(rds_params.pty);
|
||||||
|
rds_state.ptyn_ab = 1;
|
||||||
|
set_rds_ptyn(rds_params.ptyn);
|
||||||
|
set_rds_lps(rds_params.lps);
|
||||||
|
set_rds_lpson(1);
|
||||||
|
set_rds_tp(rds_params.tp);
|
||||||
|
set_rds_ecc(rds_params.ecc);
|
||||||
|
set_rds_lic(rds_params.lic);
|
||||||
|
set_rds_ecclic_toggle(1);
|
||||||
|
set_rds_pin_enabled(1);
|
||||||
|
set_rds_ct(1);
|
||||||
|
set_rds_ms(1);
|
||||||
|
set_rds_di(DI_STEREO | DI_DPTY);
|
||||||
|
|
||||||
|
/* Assign the RT+ AID to group 11A */
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
init_rtplus(GROUP_11A);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* initialize modulator objects */
|
||||||
|
init_rds_objects();
|
||||||
|
#ifdef RDS2
|
||||||
|
init_rds2_encoder(rds_params.rds2_image_path);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit_rds_encoder() {
|
||||||
|
exit_rds_objects();
|
||||||
|
#ifdef RDS2
|
||||||
|
exit_rds2_encoder();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_pi(uint16_t pi_code) {
|
||||||
|
rds_data.pi = pi_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ecc(uint8_t ecc) {
|
||||||
|
rds_data.ecc = ecc; /* ecc is 8 bits, so a byte */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_lic(uint8_t lic) {
|
||||||
|
rds_data.lic = lic & INT16_L12; /* lic is 12 bits according to the docs, but the values can be less, for example english is 0x09 */
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ecclic_toggle(uint8_t toggle) {
|
||||||
|
rds_state.ecclic_enabled = toggle & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_pin_enabled(uint8_t enabled) {
|
||||||
|
rds_state.pin_enabled = enabled & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_pin(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||||
|
rds_data.pin_day = (day & INT8_L5);
|
||||||
|
rds_data.pin_hour = (hour & INT8_L5);
|
||||||
|
rds_data.pin_minute = (minute & INT8_L6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_rt1_enabled(uint8_t rt1en) {
|
||||||
|
rds_state.rt1_enabled = rt1en & INT8_0;
|
||||||
|
}
|
||||||
|
void set_rds_rt1(unsigned char *rt1) {
|
||||||
|
uint8_t i = 0, len = 0;
|
||||||
|
|
||||||
|
rds_state.rt_update = 1;
|
||||||
|
memset(rds_data.rt1, ' ', RT_LENGTH);
|
||||||
|
while (*rt1 != 0 && len < RT_LENGTH)
|
||||||
|
rds_data.rt1[len++] = *rt1++;
|
||||||
|
|
||||||
|
if (len < RT_LENGTH) {
|
||||||
|
rds_state.rt_segments = 0;
|
||||||
|
|
||||||
|
/* Terminate RT with '\r' (carriage return) if RT
|
||||||
|
* is < 64 characters long
|
||||||
|
*/
|
||||||
|
rds_data.rt1[len++] = '\r';
|
||||||
|
|
||||||
|
/* find out how many segments are needed */
|
||||||
|
while (i < len) {
|
||||||
|
i += 4;
|
||||||
|
rds_state.rt_segments++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Default to 16 if RT is 64 characters long */
|
||||||
|
rds_state.rt_segments = 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ps(unsigned char *ps) {
|
||||||
|
uint8_t len = 0;
|
||||||
|
|
||||||
|
rds_state.ps_update = 1;
|
||||||
|
memset(rds_data.ps, ' ', PS_LENGTH);
|
||||||
|
while (*ps != 0 && len < PS_LENGTH)
|
||||||
|
rds_data.ps[len++] = *ps++;
|
||||||
|
}
|
||||||
|
void set_rds_tpson(uint8_t tpson) {
|
||||||
|
rds_data.traffic_ps_on = tpson & INT8_0;
|
||||||
|
}
|
||||||
|
void set_rds_tps(unsigned char *tps) {
|
||||||
|
uint8_t len = 0;
|
||||||
|
|
||||||
|
rds_state.tps_update = 1;
|
||||||
|
memset(rds_data.tps, ' ', PS_LENGTH);
|
||||||
|
while (*tps != 0 && len < PS_LENGTH)
|
||||||
|
rds_data.tps[len++] = *tps++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_lpson(uint8_t lpson) {
|
||||||
|
rds_state.lps_on = lpson & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_lps(unsigned char *lps) {
|
||||||
|
uint8_t i = 0, len = 0;
|
||||||
|
|
||||||
|
if (!lps[0]) {
|
||||||
|
memset(rds_data.lps, 0, LPS_LENGTH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rds_state.lps_update = 1;
|
||||||
|
memset(rds_data.lps, '\r', LPS_LENGTH);
|
||||||
|
while (*lps != 0 && len < LPS_LENGTH)
|
||||||
|
rds_data.lps[len++] = *lps++;
|
||||||
|
|
||||||
|
if (len < LPS_LENGTH) {
|
||||||
|
rds_state.lps_segments = 0;
|
||||||
|
|
||||||
|
/* increment to allow adding an '\r' in all cases */
|
||||||
|
len++;
|
||||||
|
|
||||||
|
/* find out how many segments are needed */
|
||||||
|
while (i < len) {
|
||||||
|
i += 4;
|
||||||
|
rds_state.lps_segments++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* default to 8 if LPS is 32 characters long */
|
||||||
|
rds_state.lps_segments = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
void set_rds_rtplus_flags(uint8_t flags) {
|
||||||
|
if(flags == 2) {
|
||||||
|
rtplus_cfg.enabled = 0;
|
||||||
|
} else {
|
||||||
|
rtplus_cfg.enabled = 1;
|
||||||
|
}
|
||||||
|
rtplus_cfg.running = flags & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_rtplus_tags(uint8_t *tags) {
|
||||||
|
rtplus_cfg.type[0] = tags[0] & INT8_L6;
|
||||||
|
rtplus_cfg.start[0] = tags[1] & INT8_L6;
|
||||||
|
rtplus_cfg.len[0] = tags[2] & INT8_L6;
|
||||||
|
rtplus_cfg.type[1] = tags[3] & INT8_L6;
|
||||||
|
rtplus_cfg.start[1] = tags[4] & INT8_L6;
|
||||||
|
rtplus_cfg.len[1] = tags[5] & INT8_L5;
|
||||||
|
|
||||||
|
rtplus_cfg.toggle ^= 1;
|
||||||
|
rtplus_cfg.running = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void set_rds_af(struct rds_af_t new_af_list) {
|
||||||
|
memcpy(&rds_data.af, &new_af_list, sizeof(struct rds_af_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_rds_af() {
|
||||||
|
memset(&rds_data.af, 0, sizeof(struct rds_af_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_pty(uint8_t pty) {
|
||||||
|
rds_data.pty = pty & INT8_L5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ptyn_enabled(uint8_t enabled) {
|
||||||
|
rds_state.ptyn_enabled = enabled & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ptyn(unsigned char *ptyn) {
|
||||||
|
uint8_t len = 0;
|
||||||
|
|
||||||
|
if (!ptyn[0]) {
|
||||||
|
memset(rds_data.ptyn, 0, PTYN_LENGTH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rds_state.ptyn_update = 1;
|
||||||
|
memset(rds_data.ptyn, ' ', PTYN_LENGTH);
|
||||||
|
while (*ptyn != 0 && len < PTYN_LENGTH)
|
||||||
|
rds_data.ptyn[len++] = *ptyn++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ta(uint8_t ta) {
|
||||||
|
rds_data.ta = ta & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_tp(uint8_t tp) {
|
||||||
|
rds_data.tp = tp & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ms(uint8_t ms) {
|
||||||
|
rds_data.ms = ms & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_di(uint8_t di) {
|
||||||
|
rds_data.di = di & INT8_L4;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_ct(uint8_t ct) {
|
||||||
|
rds_data.ct = ct & INT8_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_rds_pi() {
|
||||||
|
/* this is for custom groups, specifically 'G=' */
|
||||||
|
return rds_data.pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rds_cg(uint16_t* blocks) {
|
||||||
|
rds_state.custom_group[0] = blocks[0];
|
||||||
|
rds_state.custom_group[1] = blocks[1];
|
||||||
|
rds_state.custom_group[2] = blocks[2];
|
||||||
|
rds_state.custom_group[3] = blocks[3];
|
||||||
|
rds_state.custom_group_in = 1;
|
||||||
|
}
|
||||||
336
src/rds.h
Normal file
336
src/rds.h
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RDS_H
|
||||||
|
#define RDS_H
|
||||||
|
|
||||||
|
/* The RDS error-detection code generator polynomial is
|
||||||
|
* x^10 + x^8 + x^7 + x^5 + x^4 + x^3 + x^0
|
||||||
|
*/
|
||||||
|
#define POLY 0x1B9
|
||||||
|
#define POLY_DEG 10
|
||||||
|
#define BLOCK_SIZE 16
|
||||||
|
|
||||||
|
#define GROUP_LENGTH 4
|
||||||
|
#define BITS_PER_GROUP (GROUP_LENGTH * (BLOCK_SIZE + POLY_DEG))
|
||||||
|
#define RDS_SAMPLE_RATE 190000
|
||||||
|
#define SAMPLES_PER_BIT 160
|
||||||
|
#define FILTER_SIZE 1120
|
||||||
|
#define SAMPLE_BUFFER_SIZE (SAMPLES_PER_BIT + FILTER_SIZE)
|
||||||
|
|
||||||
|
/* Text items
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define RT_LENGTH 64
|
||||||
|
#define PS_LENGTH 8
|
||||||
|
#define PTYN_LENGTH 8
|
||||||
|
#define LPS_LENGTH 32
|
||||||
|
|
||||||
|
/* AF list size
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define MAX_AFS 25
|
||||||
|
|
||||||
|
typedef struct rds_af_t {
|
||||||
|
uint8_t num_entries;
|
||||||
|
uint8_t num_afs;
|
||||||
|
uint8_t afs[MAX_AFS];
|
||||||
|
} rds_af_t;
|
||||||
|
|
||||||
|
/* AF codes */
|
||||||
|
#define AF_CODE_FILLER 205
|
||||||
|
#define AF_CODE_NO_AF 224
|
||||||
|
#define AF_CODE_NUM_AFS_BASE AF_CODE_NO_AF
|
||||||
|
#define AF_CODE_LFMF_FOLLOWS 250
|
||||||
|
|
||||||
|
typedef struct rds_params_t {
|
||||||
|
uint16_t pi; /*Program Identification*/
|
||||||
|
uint16_t lic; /* Language Identification code. lic should have 12 bits, 16 is the closest */
|
||||||
|
uint8_t ecc; /* Extended Country Code*/
|
||||||
|
uint8_t ta; /* Traffic Annoucement*/
|
||||||
|
uint8_t pty; /* Program Type */
|
||||||
|
uint8_t tp; /* Traffic Program */
|
||||||
|
uint8_t ms; /* Music/Speech */
|
||||||
|
uint8_t di; /* Decoder ID */
|
||||||
|
|
||||||
|
/* Program Service */
|
||||||
|
unsigned char ps[PS_LENGTH];
|
||||||
|
/* Traffic PS */
|
||||||
|
uint8_t traffic_ps_on;
|
||||||
|
unsigned char tps[PS_LENGTH];
|
||||||
|
|
||||||
|
/* Radio Text */
|
||||||
|
unsigned char rt1[RT_LENGTH];
|
||||||
|
|
||||||
|
/* Program Type Name */
|
||||||
|
unsigned char ptyn[PTYN_LENGTH];
|
||||||
|
|
||||||
|
/* Alternative Frequencies */
|
||||||
|
struct rds_af_t af;
|
||||||
|
|
||||||
|
/* Clock-time */
|
||||||
|
uint8_t ct;
|
||||||
|
|
||||||
|
/* Long PS */
|
||||||
|
unsigned char lps[LPS_LENGTH];
|
||||||
|
|
||||||
|
/* Program Item Number */
|
||||||
|
uint8_t pin_day;
|
||||||
|
uint8_t pin_hour;
|
||||||
|
uint8_t pin_minute;
|
||||||
|
|
||||||
|
#ifdef RDS2
|
||||||
|
/* RDS2 image path*/
|
||||||
|
char rds2_image_path[51];
|
||||||
|
#endif
|
||||||
|
} rds_params_t;
|
||||||
|
/* Here, the first member of the struct must be a scalar to avoid a
|
||||||
|
warning on -Wmissing-braces with GCC < 4.8.3
|
||||||
|
(bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Group type
|
||||||
|
*
|
||||||
|
* 0-15
|
||||||
|
*/
|
||||||
|
#define GROUP_TYPE_0 ( 0 << 4)
|
||||||
|
#define GROUP_TYPE_1 ( 1 << 4)
|
||||||
|
#define GROUP_TYPE_2 ( 2 << 4)
|
||||||
|
#define GROUP_TYPE_3 ( 3 << 4)
|
||||||
|
#define GROUP_TYPE_4 ( 4 << 4)
|
||||||
|
#define GROUP_TYPE_5 ( 5 << 4)
|
||||||
|
#define GROUP_TYPE_6 ( 6 << 4)
|
||||||
|
#define GROUP_TYPE_7 ( 7 << 4)
|
||||||
|
#define GROUP_TYPE_8 ( 8 << 4)
|
||||||
|
#define GROUP_TYPE_9 ( 9 << 4)
|
||||||
|
#define GROUP_TYPE_10 (10 << 4)
|
||||||
|
#define GROUP_TYPE_11 (11 << 4)
|
||||||
|
#define GROUP_TYPE_12 (12 << 4)
|
||||||
|
#define GROUP_TYPE_13 (13 << 4)
|
||||||
|
#define GROUP_TYPE_14 (14 << 4)
|
||||||
|
#define GROUP_TYPE_15 (15 << 4)
|
||||||
|
|
||||||
|
/* Group versions
|
||||||
|
*
|
||||||
|
* The first 4 bits are the group number and the remaining 4 are
|
||||||
|
* the group version
|
||||||
|
*/
|
||||||
|
#define GROUP_VER_A 0
|
||||||
|
#define GROUP_VER_B 1
|
||||||
|
|
||||||
|
/* Version A groups */
|
||||||
|
#define GROUP_0A (GROUP_TYPE_0 | GROUP_VER_A)
|
||||||
|
#define GROUP_1A (GROUP_TYPE_1 | GROUP_VER_A)
|
||||||
|
#define GROUP_2A (GROUP_TYPE_2 | GROUP_VER_A)
|
||||||
|
#define GROUP_3A (GROUP_TYPE_3 | GROUP_VER_A)
|
||||||
|
#define GROUP_4A (GROUP_TYPE_4 | GROUP_VER_A)
|
||||||
|
#define GROUP_5A (GROUP_TYPE_5 | GROUP_VER_A)
|
||||||
|
#define GROUP_6A (GROUP_TYPE_6 | GROUP_VER_A)
|
||||||
|
#define GROUP_7A (GROUP_TYPE_7 | GROUP_VER_A)
|
||||||
|
#define GROUP_8A (GROUP_TYPE_8 | GROUP_VER_A)
|
||||||
|
#define GROUP_9A (GROUP_TYPE_9 | GROUP_VER_A)
|
||||||
|
#define GROUP_10A (GROUP_TYPE_10 | GROUP_VER_A)
|
||||||
|
#define GROUP_11A (GROUP_TYPE_11 | GROUP_VER_A)
|
||||||
|
#define GROUP_12A (GROUP_TYPE_12 | GROUP_VER_A)
|
||||||
|
#define GROUP_13A (GROUP_TYPE_13 | GROUP_VER_A)
|
||||||
|
#define GROUP_14A (GROUP_TYPE_14 | GROUP_VER_A)
|
||||||
|
#define GROUP_15A (GROUP_TYPE_15 | GROUP_VER_A)
|
||||||
|
|
||||||
|
/* Version B groups */
|
||||||
|
#define GROUP_0B (GROUP_TYPE_0 | GROUP_VER_B)
|
||||||
|
#define GROUP_1B (GROUP_TYPE_1 | GROUP_VER_B)
|
||||||
|
#define GROUP_2B (GROUP_TYPE_2 | GROUP_VER_B)
|
||||||
|
#define GROUP_3B (GROUP_TYPE_3 | GROUP_VER_B)
|
||||||
|
#define GROUP_4B (GROUP_TYPE_4 | GROUP_VER_B)
|
||||||
|
#define GROUP_5B (GROUP_TYPE_5 | GROUP_VER_B)
|
||||||
|
#define GROUP_6B (GROUP_TYPE_6 | GROUP_VER_B)
|
||||||
|
#define GROUP_7B (GROUP_TYPE_7 | GROUP_VER_B)
|
||||||
|
#define GROUP_8B (GROUP_TYPE_8 | GROUP_VER_B)
|
||||||
|
#define GROUP_9B (GROUP_TYPE_9 | GROUP_VER_B)
|
||||||
|
#define GROUP_10B (GROUP_TYPE_10 | GROUP_VER_B)
|
||||||
|
#define GROUP_11B (GROUP_TYPE_11 | GROUP_VER_B)
|
||||||
|
#define GROUP_12B (GROUP_TYPE_12 | GROUP_VER_B)
|
||||||
|
#define GROUP_13B (GROUP_TYPE_13 | GROUP_VER_B)
|
||||||
|
#define GROUP_14B (GROUP_TYPE_14 | GROUP_VER_B)
|
||||||
|
#define GROUP_15B (GROUP_TYPE_15 | GROUP_VER_B)
|
||||||
|
|
||||||
|
#define GET_GROUP_TYPE(x) ((x >> 4) & 15)
|
||||||
|
#define GET_GROUP_VER(x) (x & 1) /* only check bit 0 */
|
||||||
|
|
||||||
|
#define DI_STEREO (1 << 0) /* 1 - Stereo */
|
||||||
|
#define DI_AH (1 << 1) /* 2 - Artificial Head */
|
||||||
|
#define DI_COMPRESSED (1 << 2) /* 4 - Compressed */
|
||||||
|
#define DI_DPTY (1 << 3) /* 8 - Dynamic PTY */
|
||||||
|
|
||||||
|
/* Bit mask */
|
||||||
|
|
||||||
|
/* 8 bit */
|
||||||
|
#define INT8_ALL 0xff
|
||||||
|
/* Lower */
|
||||||
|
#define INT8_L1 0x01
|
||||||
|
#define INT8_L2 0x03
|
||||||
|
#define INT8_L3 0x07
|
||||||
|
#define INT8_L4 0x0f
|
||||||
|
#define INT8_L5 0x1f
|
||||||
|
#define INT8_L6 0x3f
|
||||||
|
#define INT8_L7 0x7f
|
||||||
|
/* Upper */
|
||||||
|
#define INT8_U7 0xfe
|
||||||
|
#define INT8_U6 0xfc
|
||||||
|
#define INT8_U5 0xf8
|
||||||
|
#define INT8_U4 0xf0
|
||||||
|
#define INT8_U3 0xe0
|
||||||
|
#define INT8_U2 0xc0
|
||||||
|
#define INT8_U1 0x80
|
||||||
|
/* Single */
|
||||||
|
#define INT8_0 0x01
|
||||||
|
#define INT8_1 0x02
|
||||||
|
#define INT8_2 0x04
|
||||||
|
#define INT8_3 0x08
|
||||||
|
#define INT8_4 0x10
|
||||||
|
#define INT8_5 0x20
|
||||||
|
#define INT8_6 0x40
|
||||||
|
#define INT8_7 0x80
|
||||||
|
|
||||||
|
/* 16 bit */
|
||||||
|
#define INT16_ALL 0xffff
|
||||||
|
/* Lower */
|
||||||
|
#define INT16_L1 0x0001
|
||||||
|
#define INT16_L2 0x0003
|
||||||
|
#define INT16_L3 0x0007
|
||||||
|
#define INT16_L4 0x000f
|
||||||
|
#define INT16_L5 0x001f
|
||||||
|
#define INT16_L6 0x003f
|
||||||
|
#define INT16_L7 0x007f
|
||||||
|
#define INT16_L8 0x00ff
|
||||||
|
#define INT16_L9 0x01ff
|
||||||
|
#define INT16_L10 0x03ff
|
||||||
|
#define INT16_L11 0x07ff
|
||||||
|
#define INT16_L12 0x0fff
|
||||||
|
#define INT16_L13 0x1fff
|
||||||
|
#define INT16_L14 0x3fff
|
||||||
|
#define INT16_L15 0x7fff
|
||||||
|
/* Upper */
|
||||||
|
#define INT16_U15 0xfffe
|
||||||
|
#define INT16_U14 0xfffc
|
||||||
|
#define INT16_U13 0xfff8
|
||||||
|
#define INT16_U12 0xfff0
|
||||||
|
#define INT16_U11 0xffe0
|
||||||
|
#define INT16_U10 0xffc0
|
||||||
|
#define INT16_U9 0xff80
|
||||||
|
#define INT16_U8 0xff00
|
||||||
|
#define INT16_U7 0xfe00
|
||||||
|
#define INT16_U6 0xfc00
|
||||||
|
#define INT16_U5 0xf800
|
||||||
|
#define INT16_U4 0xf000
|
||||||
|
#define INT16_U3 0xe000
|
||||||
|
#define INT16_U2 0xc000
|
||||||
|
#define INT16_U1 0x8000
|
||||||
|
/* Single */
|
||||||
|
#define INT16_0 0x0001
|
||||||
|
#define INT16_1 0x0002
|
||||||
|
#define INT16_2 0x0004
|
||||||
|
#define INT16_3 0x0008
|
||||||
|
#define INT16_4 0x0010
|
||||||
|
#define INT16_5 0x0020
|
||||||
|
#define INT16_6 0x0040
|
||||||
|
#define INT16_7 0x0080
|
||||||
|
#define INT16_8 0x0100
|
||||||
|
#define INT16_9 0x0200
|
||||||
|
#define INT16_10 0x0400
|
||||||
|
#define INT16_11 0x0800
|
||||||
|
#define INT16_12 0x1000
|
||||||
|
#define INT16_13 0x2000
|
||||||
|
#define INT16_14 0x4000
|
||||||
|
#define INT16_15 0x8000
|
||||||
|
|
||||||
|
/* 18 bit (for RDS2) */
|
||||||
|
#define INT18_U2 0x30000
|
||||||
|
#define INT18_L4 0x0000f
|
||||||
|
|
||||||
|
#define IS_TYPE_B(a) (a[1] & INT16_11)
|
||||||
|
#ifdef RDS2
|
||||||
|
#define IS_TUNNELING(a) (a[0] == 0x0000)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ODA
|
||||||
|
/*
|
||||||
|
* RDS ODA ID group
|
||||||
|
*
|
||||||
|
* This struct is for defining ODAs that will be transmitted
|
||||||
|
*
|
||||||
|
* Can signal version A or B data groups
|
||||||
|
*/
|
||||||
|
typedef struct rds_oda_t {
|
||||||
|
uint8_t group;
|
||||||
|
uint16_t aid;
|
||||||
|
uint16_t scb;
|
||||||
|
} rds_oda_t;
|
||||||
|
|
||||||
|
#define MAX_ODAS 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ODA AID
|
||||||
|
*
|
||||||
|
* Extensive list: https://www.nrscstandards.org/committees/dsm/archive/rds-oda-aids.pdf
|
||||||
|
*/
|
||||||
|
#define ODA_AID_RTPLUS 0x4bd7
|
||||||
|
#endif
|
||||||
|
/* RDS2 */
|
||||||
|
#define ODA_AID_RFT 0xff7f
|
||||||
|
#define ODA_AID_RFTPLUS 0xff80
|
||||||
|
|
||||||
|
extern void init_rds_encoder(struct rds_params_t rds_params);
|
||||||
|
extern void exit_rds_encoder();
|
||||||
|
extern void get_rds_bits(uint8_t *bits);
|
||||||
|
extern void set_rds_pi(uint16_t pi_code);
|
||||||
|
extern void set_rds_ecc(uint8_t ecc);
|
||||||
|
extern void set_rds_lic(uint8_t lic);
|
||||||
|
extern void set_rds_ecclic_toggle(uint8_t toggle);
|
||||||
|
extern void set_rds_pin_enabled(uint8_t enabled);
|
||||||
|
extern void set_rds_pin(uint8_t day, uint8_t hour, uint8_t minute);
|
||||||
|
extern void set_rds_rt1_enabled(uint8_t rt1en);
|
||||||
|
extern void set_rds_rt1(unsigned char *rt1);
|
||||||
|
extern void set_rds_ps(unsigned char *ps);
|
||||||
|
extern void set_rds_tpson(uint8_t tpson);
|
||||||
|
extern void set_rds_tps(unsigned char *ps);
|
||||||
|
extern void set_rds_lpson(uint8_t lpson);
|
||||||
|
extern void set_rds_lps(unsigned char *lps);
|
||||||
|
#ifdef ODA_RTP
|
||||||
|
extern void set_rds_rtplus_flags(uint8_t flags);
|
||||||
|
extern void set_rds_rtplus_tags(uint8_t *tags);
|
||||||
|
#endif
|
||||||
|
extern void set_rds_ta(uint8_t ta);
|
||||||
|
extern void set_rds_pty(uint8_t pty);
|
||||||
|
extern void set_rds_ptyn_enabled(uint8_t enabled);
|
||||||
|
extern void set_rds_ptyn(unsigned char *ptyn);
|
||||||
|
extern void set_rds_af(struct rds_af_t new_af_list);
|
||||||
|
extern void clear_rds_af();
|
||||||
|
extern void set_rds_tp(uint8_t tp);
|
||||||
|
extern void set_rds_ms(uint8_t ms);
|
||||||
|
extern void set_rds_ct(uint8_t ct);
|
||||||
|
extern void set_rds_di(uint8_t di);
|
||||||
|
#ifdef RDS2
|
||||||
|
extern float get_rds_sample(uint8_t stream_num, uint8_t rds2tunneling);
|
||||||
|
#else
|
||||||
|
extern float get_rds_sample(uint8_t stream_num);
|
||||||
|
#endif
|
||||||
|
extern uint16_t get_rds_pi();
|
||||||
|
extern void set_rds_cg(uint16_t* blocks);
|
||||||
|
|
||||||
|
#endif /* RDS_H */
|
||||||
452
src/rds2.c
Normal file
452
src/rds2.c
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019-2020 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "rds.h"
|
||||||
|
#include "rds2.h"
|
||||||
|
#include "lib.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RDS2-specific stuff
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* RFT */
|
||||||
|
|
||||||
|
/* fallback station logo */
|
||||||
|
#include "rds2_image_data.c"
|
||||||
|
|
||||||
|
/* station logo */
|
||||||
|
static struct rft_t station_logo_stream;
|
||||||
|
|
||||||
|
static void init_rft(struct rft_t *rft, uint8_t file_id,
|
||||||
|
bool usecrc, uint8_t crc_mode, char *file_path) {
|
||||||
|
uint32_t seg_counter = 0;
|
||||||
|
uint32_t crc_chunk_counter = 0;
|
||||||
|
uint16_t crc_chunk_size = 0;
|
||||||
|
FILE *image_file_hdlr;
|
||||||
|
|
||||||
|
rft->file_path = malloc(MAX_FILE_NAME_LEN + 1);
|
||||||
|
rft->file_path[MAX_FILE_NAME_LEN] = 0;
|
||||||
|
rft->file_data = malloc(MAX_IMAGE_LEN);
|
||||||
|
rft->crcs = malloc(MAX_CRC_CHUNK_ADDR * sizeof(uint16_t));
|
||||||
|
|
||||||
|
/* unused data must be padded with zeroes */
|
||||||
|
memset(rft->file_data, 0, MAX_IMAGE_LEN);
|
||||||
|
|
||||||
|
strncpy(rft->file_path, file_path, MAX_FILE_NAME_LEN);
|
||||||
|
|
||||||
|
/* open file in binary mode */
|
||||||
|
image_file_hdlr = fopen(file_path, "rb");
|
||||||
|
if (image_file_hdlr != NULL) {
|
||||||
|
fseek(image_file_hdlr, 0, SEEK_END);
|
||||||
|
rft->file_len = ftell(image_file_hdlr);
|
||||||
|
rewind(image_file_hdlr);
|
||||||
|
|
||||||
|
/* image cannot be larger than 163840 bytes */
|
||||||
|
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||||
|
#ifdef RDS2_DEBUG
|
||||||
|
fprintf(stderr, "W: Image too large!\n");
|
||||||
|
#endif
|
||||||
|
rft->file_len = MAX_IMAGE_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy image data to local buffer */
|
||||||
|
fread(rft->file_data, rft->file_len, 1, image_file_hdlr);
|
||||||
|
|
||||||
|
fclose(image_file_hdlr);
|
||||||
|
} else { /* fallback in case file can't be read at startup */
|
||||||
|
rft->file_len = station_logo_len;
|
||||||
|
|
||||||
|
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||||
|
rft->file_len = MAX_IMAGE_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(rft->file_data, station_logo, rft->file_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep track of file lengths so we know when to toggle */
|
||||||
|
rft->prev_file_len = rft->file_len;
|
||||||
|
rft->channel = 0;
|
||||||
|
rft->file_id = file_id;
|
||||||
|
rft->toggle = 0;
|
||||||
|
rft->num_segs = 0;
|
||||||
|
|
||||||
|
/* determine how many segments we need */
|
||||||
|
while (seg_counter < rft->file_len) {
|
||||||
|
seg_counter += 5;
|
||||||
|
rft->num_segs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rft->use_crc = usecrc;
|
||||||
|
|
||||||
|
/* if CRC is disabled exit early */
|
||||||
|
if (!rft->use_crc) {
|
||||||
|
rft->crc_mode = 0;
|
||||||
|
rft->num_crc_chunks = 1; /* only 1 */
|
||||||
|
rft->crcs[0] = 0x0000;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rft->crc_mode = crc_mode & 7;
|
||||||
|
|
||||||
|
switch (rft->crc_mode) {
|
||||||
|
case RFT_CRC_MODE_ENTIRE_FILE:
|
||||||
|
/* no-op */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_16_GROUPS:
|
||||||
|
crc_chunk_size = 16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_32_GROUPS:
|
||||||
|
crc_chunk_size = 32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_64_GROUPS:
|
||||||
|
crc_chunk_size = 64;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_128_GROUPS:
|
||||||
|
crc_chunk_size = 128;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_256_GROUPS:
|
||||||
|
crc_chunk_size = 256;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RFT_CRC_MODE_RFU: /* reserved - use auto mode */
|
||||||
|
case RFT_CRC_MODE_AUTO:
|
||||||
|
if (rft->file_len >= 81920) {
|
||||||
|
crc_chunk_size = 64;
|
||||||
|
rft->crc_mode = RFT_CRC_MODE_64_GROUPS;
|
||||||
|
} else if (rft->file_len > 40960) {
|
||||||
|
crc_chunk_size = 32;
|
||||||
|
rft->crc_mode = RFT_CRC_MODE_32_GROUPS;
|
||||||
|
} else {
|
||||||
|
crc_chunk_size = 16;
|
||||||
|
rft->crc_mode = RFT_CRC_MODE_16_GROUPS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rft->crc_mode) { /* chunked modes */
|
||||||
|
/* RFT packet size = chunk size * 5 data bytes per group */
|
||||||
|
rft->pkt_size = crc_chunk_size * 5;
|
||||||
|
|
||||||
|
/* the remainder (if file size is not a multiple of pkt_size) */
|
||||||
|
rft->pkt_size_rem = rft->file_len % rft->pkt_size;
|
||||||
|
|
||||||
|
/* calculate number of CRC chunks */
|
||||||
|
while (crc_chunk_counter <= rft->file_len) {
|
||||||
|
crc_chunk_counter += rft->pkt_size;
|
||||||
|
rft->num_crc_chunks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rft->crc_chunks = malloc(rft->pkt_size);
|
||||||
|
|
||||||
|
/* calculate CRC's for chunks */
|
||||||
|
for (uint16_t i = 0; i < rft->num_crc_chunks; i++) {
|
||||||
|
memset(rft->crc_chunks, 0, rft->pkt_size);
|
||||||
|
memcpy(rft->crc_chunks,
|
||||||
|
rft->file_data + i * rft->pkt_size, rft->pkt_size);
|
||||||
|
if ((i == rft->num_crc_chunks - 1) && rft->pkt_size_rem) {
|
||||||
|
/* last chunk may have less bytes */
|
||||||
|
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size_rem);
|
||||||
|
} else {
|
||||||
|
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(rft->crc_chunks);
|
||||||
|
} else { /* CRC of entire file */
|
||||||
|
rft->num_crc_chunks = 1; /* only 1 */
|
||||||
|
rft->crcs[0] = crc16(rft->file_data, rft->file_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_rft(struct rft_t *rft) {
|
||||||
|
uint32_t seg_counter = 0;
|
||||||
|
FILE *image_file_hdlr;
|
||||||
|
|
||||||
|
/* open file in binary mode */
|
||||||
|
image_file_hdlr = fopen(rft->file_path, "rb");
|
||||||
|
if (image_file_hdlr == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rft->prev_file_len = rft->file_len;
|
||||||
|
|
||||||
|
/* update size field */
|
||||||
|
fseek(image_file_hdlr, 0, SEEK_END);
|
||||||
|
rft->file_len = ftell(image_file_hdlr);
|
||||||
|
rewind(image_file_hdlr);
|
||||||
|
|
||||||
|
/* file didn't change so return early */
|
||||||
|
if (rft->file_len == rft->prev_file_len) {
|
||||||
|
fclose(image_file_hdlr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* image cannot be larger than 163840 bytes */
|
||||||
|
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||||
|
#ifdef RDS2_DEBUG
|
||||||
|
fprintf(stderr, "W: Image too large!\n");
|
||||||
|
#endif
|
||||||
|
rft->file_len = MAX_IMAGE_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy image data to local buffer */
|
||||||
|
fread(rft->file_data, rft->file_len, 1, image_file_hdlr);
|
||||||
|
|
||||||
|
fclose(image_file_hdlr);
|
||||||
|
|
||||||
|
rft->num_segs = 0;
|
||||||
|
|
||||||
|
/* recalculate # of needed segments */
|
||||||
|
while (seg_counter < rft->file_len) {
|
||||||
|
seg_counter += 5;
|
||||||
|
rft->num_segs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rft->crc_mode) {
|
||||||
|
/* recalculate CRC's for chunks */
|
||||||
|
for (uint16_t i = 0; i < rft->num_crc_chunks; i++) {
|
||||||
|
memset(rft->crc_chunks, 0, rft->pkt_size);
|
||||||
|
memcpy(rft->crc_chunks,
|
||||||
|
rft->file_data + i * rft->pkt_size, rft->pkt_size);
|
||||||
|
if ((i == rft->num_crc_chunks - 1) && rft->pkt_size_rem) {
|
||||||
|
/* last chunk may have less bytes */
|
||||||
|
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size_rem);
|
||||||
|
} else {
|
||||||
|
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(rft->crc_chunks);
|
||||||
|
} else { /* CRC of entire file */
|
||||||
|
rft->num_crc_chunks = 1; /* only 1 */
|
||||||
|
rft->crcs[0] = crc16(rft->file_data, rft->file_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++rft->file_version == 8) {
|
||||||
|
rft->file_version = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exit_rft(struct rft_t *rft) {
|
||||||
|
free(rft->file_data);
|
||||||
|
free(rft->file_path);
|
||||||
|
free(rft->crcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RFT variant 0 (file metadata)
|
||||||
|
*
|
||||||
|
* Carries:
|
||||||
|
* - Function Header
|
||||||
|
* - Pipe Number
|
||||||
|
* - ODA ID
|
||||||
|
* - Variant code (0)
|
||||||
|
* - CRC-16 Flag
|
||||||
|
* - File ID
|
||||||
|
* - File size (18 bits)
|
||||||
|
*/
|
||||||
|
static void get_rft_var_0_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||||
|
/* function header */
|
||||||
|
blocks[0] = 1 << 15;
|
||||||
|
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||||
|
|
||||||
|
/* ODA ID */
|
||||||
|
blocks[1] = ODA_AID_RFT;
|
||||||
|
|
||||||
|
blocks[2] = (0 & INT18_L4) << 12; /* variant code */
|
||||||
|
blocks[2] |= ((rft->use_crc ? 1 : 0) & INT8_L1) << 11; /* CRC */
|
||||||
|
blocks[2] |= (rft->file_version & INT8_L3) << 8; /* file version */
|
||||||
|
blocks[2] |= (rft->file_id & INT8_L6) << 2; /* file ID */
|
||||||
|
blocks[2] |= (rft->file_len & INT18_U2) >> 16;
|
||||||
|
|
||||||
|
blocks[3] = rft->file_len & INT16_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RFT variant 1 (chunk to CRC mappings)
|
||||||
|
*
|
||||||
|
* Carries:
|
||||||
|
* - Function Header
|
||||||
|
* - Pipe Number
|
||||||
|
* - ODA ID
|
||||||
|
* - Variant Code (1)
|
||||||
|
* - CRC Mode
|
||||||
|
* - Chunk Address (9 bits)
|
||||||
|
* - CRC
|
||||||
|
*/
|
||||||
|
static void get_rft_var_1_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||||
|
/* function header */
|
||||||
|
blocks[0] = 1 << 15;
|
||||||
|
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||||
|
|
||||||
|
/* ODA ID */
|
||||||
|
blocks[1] = ODA_AID_RFT;
|
||||||
|
|
||||||
|
blocks[2] = (1 & INT8_L4) << 12; /* variant code */
|
||||||
|
blocks[2] |= (rft->crc_mode & INT8_L3) << 9; /* CRC mode */
|
||||||
|
/* chunk address */
|
||||||
|
blocks[2] |= rft->crc_chunk_addr & INT16_L9;
|
||||||
|
|
||||||
|
/* CRC */
|
||||||
|
blocks[3] = rft->crcs[rft->crc_chunk_addr];
|
||||||
|
|
||||||
|
if (++rft->crc_chunk_addr > rft->num_crc_chunks) {
|
||||||
|
#ifdef RDS2_DEBUG
|
||||||
|
fprintf(stderr, "File CRC sending complete\n");
|
||||||
|
#endif
|
||||||
|
rft->crc_chunk_addr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RFT variants 2-15 (custom variants)
|
||||||
|
*
|
||||||
|
* Carries:
|
||||||
|
* - Function Header
|
||||||
|
* - Pipe Number
|
||||||
|
* - Variant code
|
||||||
|
* - Variant data:
|
||||||
|
* - 2-7 carries file-related data
|
||||||
|
* - 8-15 ODA data that is not related
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
static void get_rft_var_2_15_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||||
|
/* function header */
|
||||||
|
blocks[0] = (2 << 6) << 8;
|
||||||
|
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||||
|
|
||||||
|
/* ODA ID */
|
||||||
|
blocks[1] = ODA_AID_RFT;
|
||||||
|
|
||||||
|
blocks[2] = (8 & INT8_L4) << 12; /* variant code */
|
||||||
|
blocks[2] |= 0 & INT16_L12;
|
||||||
|
blocks[3] = 0 & INT16_ALL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RFT data group (for file data TXing)
|
||||||
|
*
|
||||||
|
* Carries:
|
||||||
|
* - Function Header
|
||||||
|
* - Pipe Number
|
||||||
|
* - Toggle Bit
|
||||||
|
* - Segment address (15 bits)
|
||||||
|
* - File data (5 bytes per group)
|
||||||
|
*/
|
||||||
|
static void get_rft_file_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||||
|
/* function header */
|
||||||
|
blocks[0] = 2 << 12;
|
||||||
|
blocks[0] |= (rft->channel & INT8_L4) << 8; /* pipe number */
|
||||||
|
|
||||||
|
/* toggle bit */
|
||||||
|
blocks[0] |= (rft->toggle & INT16_L1) << 7;
|
||||||
|
|
||||||
|
/* segment address */
|
||||||
|
blocks[0] |= ((rft->seg_addr_img & INT16_U8) >> 8) & INT16_L7;
|
||||||
|
blocks[1] = (rft->seg_addr_img & INT16_L8) << 8;
|
||||||
|
|
||||||
|
/* image data */
|
||||||
|
blocks[1] |= rft->file_data[rft->seg_addr_img * 5 + 0];
|
||||||
|
blocks[2] = rft->file_data[rft->seg_addr_img * 5 + 1] << 8;
|
||||||
|
blocks[2] |= rft->file_data[rft->seg_addr_img * 5 + 2];
|
||||||
|
blocks[3] = rft->file_data[rft->seg_addr_img * 5 + 3] << 8;
|
||||||
|
blocks[3] |= rft->file_data[rft->seg_addr_img * 5 + 4];
|
||||||
|
|
||||||
|
if (++rft->seg_addr_img > rft->num_segs) {
|
||||||
|
#ifdef RDS2_DEBUG
|
||||||
|
fprintf(stderr, "File sending complete\n");
|
||||||
|
#endif
|
||||||
|
rft->seg_addr_img = 0;
|
||||||
|
rft->toggle ^= 1;
|
||||||
|
update_rft(rft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_rft_stream(uint16_t *blocks) {
|
||||||
|
static uint8_t rft_state;
|
||||||
|
|
||||||
|
switch (rft_state) {
|
||||||
|
case 0:
|
||||||
|
get_rft_var_0_data_group(&station_logo_stream, blocks);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
get_rft_var_1_data_group(&station_logo_stream, blocks);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
get_rft_var_0_data_group(&station_logo_stream, blocks);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
get_rft_var_1_data_group(&station_logo_stream, blocks);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
get_rft_file_data_group(&station_logo_stream, blocks);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rft_state++;
|
||||||
|
if (rft_state == 50) rft_state = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RDS 2 group sequence
|
||||||
|
*/
|
||||||
|
static void get_rds2_group(uint8_t stream_num, uint16_t *blocks) {
|
||||||
|
|
||||||
|
switch (stream_num) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
get_rft_stream(blocks);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RDS2_DEBUG
|
||||||
|
fprintf(stderr, "Stream %u: %04x %04x %04x %04x\n",
|
||||||
|
stream_num, blocks[0], blocks[1], blocks[2], blocks[3]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_rds2_bits(uint8_t stream, uint8_t *bits) {
|
||||||
|
static uint16_t out_blocks[GROUP_LENGTH];
|
||||||
|
get_rds2_group(stream, out_blocks);
|
||||||
|
add_checkwords(out_blocks, bits, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_rds2_encoder(char *station_logo_path) {
|
||||||
|
/* create a new stream for the station logo */
|
||||||
|
init_rft(&station_logo_stream,
|
||||||
|
0 /* file ID */,
|
||||||
|
false /* don't use crc */,
|
||||||
|
RFT_CRC_MODE_AUTO,
|
||||||
|
station_logo_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit_rds2_encoder() {
|
||||||
|
exit_rft(&station_logo_stream);
|
||||||
|
}
|
||||||
71
src/rds2.h
Normal file
71
src/rds2.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019-2020 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct rds2_oda_t {
|
||||||
|
uint16_t aid;
|
||||||
|
uint8_t channel;
|
||||||
|
} rds2_oda_t;
|
||||||
|
|
||||||
|
#define GET_RDS2_ODA_CHANNEL(x) (x & INT8_L5)
|
||||||
|
|
||||||
|
#define MAX_FILE_NAME_LEN 64 /* does not include terminating null char */
|
||||||
|
#define MAX_IMAGE_LEN 163840
|
||||||
|
#define MAX_CRC_CHUNK_ADDR 511
|
||||||
|
|
||||||
|
/* RFT CRC mode */
|
||||||
|
enum rft_crc_mode {
|
||||||
|
RFT_CRC_MODE_ENTIRE_FILE, /* over entire file (universal) */
|
||||||
|
RFT_CRC_MODE_16_GROUPS, /* chunks of 16 groups (size <= 40,960) */
|
||||||
|
RFT_CRC_MODE_32_GROUPS, /* chunks of 32 groups (40,960 < size <= 81,920) */
|
||||||
|
RFT_CRC_MODE_64_GROUPS, /* chunks of 64 groups (size > 81,960) */
|
||||||
|
RFT_CRC_MODE_128_GROUPS, /* chunks of 128 groups (size > 81,960) */
|
||||||
|
RFT_CRC_MODE_256_GROUPS, /* chunks of 256 groups (size > 81,960) */
|
||||||
|
RFT_CRC_MODE_RFU, /* reserved */
|
||||||
|
RFT_CRC_MODE_AUTO /* automatic between 1-3 based on size */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* RDS2 File Transfer */
|
||||||
|
typedef struct rft_t {
|
||||||
|
uint8_t channel;
|
||||||
|
|
||||||
|
char *file_path;
|
||||||
|
unsigned char *file_data;
|
||||||
|
size_t file_len;
|
||||||
|
size_t prev_file_len;
|
||||||
|
uint8_t file_version;
|
||||||
|
uint8_t file_id;
|
||||||
|
uint8_t variant_code;
|
||||||
|
uint8_t toggle;
|
||||||
|
|
||||||
|
uint16_t pkt_size;
|
||||||
|
uint16_t pkt_size_rem;
|
||||||
|
uint16_t seg_addr_img;
|
||||||
|
uint16_t num_segs;
|
||||||
|
|
||||||
|
/* CRC chunk map */
|
||||||
|
bool use_crc;
|
||||||
|
uint8_t crc_mode;
|
||||||
|
uint16_t crc_chunk_addr;
|
||||||
|
uint16_t num_crc_chunks;
|
||||||
|
uint8_t *crc_chunks;
|
||||||
|
uint16_t *crcs;
|
||||||
|
} rft_t;
|
||||||
|
|
||||||
|
extern void get_rds2_bits(uint8_t stream_num, uint8_t *bits);
|
||||||
|
extern void init_rds2_encoder(char *station_logo_path);
|
||||||
|
extern void exit_rds2_encoder();
|
||||||
181
src/rds2_image_data.c
Normal file
181
src/rds2_image_data.c
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
static const unsigned char station_logo[] = {
|
||||||
|
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
|
||||||
|
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xe2, 0x01, 0xd8,
|
||||||
|
0x49, 0x43, 0x43, 0x5f, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x00,
|
||||||
|
0x01, 0x01, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30,
|
||||||
|
0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59,
|
||||||
|
0x5a, 0x20, 0x07, 0xe0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||||
|
0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x09, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
|
||||||
|
0x00, 0x24, 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00,
|
||||||
|
0x00, 0x14, 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x28, 0x00, 0x00,
|
||||||
|
0x00, 0x14, 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x3c, 0x00, 0x00,
|
||||||
|
0x00, 0x14, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00,
|
||||||
|
0x00, 0x14, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||||
|
0x00, 0x28, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||||
|
0x00, 0x28, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||||
|
0x00, 0x28, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00,
|
||||||
|
0x00, 0x3c, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00,
|
||||||
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x73, 0x00, 0x52, 0x00, 0x47,
|
||||||
|
0x00, 0x42, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x6f, 0xa2, 0x00, 0x00, 0x38, 0xf5, 0x00, 0x00, 0x03, 0x90, 0x58, 0x59,
|
||||||
|
0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, 0x00, 0x00,
|
||||||
|
0xb7, 0x85, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x84, 0x00, 0x00,
|
||||||
|
0xb6, 0xcf, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x70, 0x61,
|
||||||
|
0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x66, 0x66, 0x00, 0x00, 0xf2, 0xa7, 0x00, 0x00, 0x0d, 0x59, 0x00, 0x00,
|
||||||
|
0x13, 0xd0, 0x00, 0x00, 0x0a, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f,
|
||||||
|
0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e,
|
||||||
|
0x00, 0x63, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31,
|
||||||
|
0x00, 0x36, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x1b, 0x12, 0x14, 0x17, 0x14,
|
||||||
|
0x11, 0x1b, 0x17, 0x16, 0x17, 0x1e, 0x1c, 0x1b, 0x20, 0x28, 0x42, 0x2b,
|
||||||
|
0x28, 0x25, 0x25, 0x28, 0x51, 0x3a, 0x3d, 0x30, 0x42, 0x60, 0x55, 0x65,
|
||||||
|
0x64, 0x5f, 0x55, 0x5d, 0x5b, 0x6a, 0x78, 0x99, 0x81, 0x6a, 0x71, 0x90,
|
||||||
|
0x73, 0x5b, 0x5d, 0x85, 0xb5, 0x86, 0x90, 0x9e, 0xa3, 0xab, 0xad, 0xab,
|
||||||
|
0x67, 0x80, 0xbc, 0xc9, 0xba, 0xa6, 0xc7, 0x99, 0xa8, 0xab, 0xa4, 0xff,
|
||||||
|
0xdb, 0x00, 0x43, 0x01, 0x1c, 0x1e, 0x1e, 0x28, 0x23, 0x28, 0x4e, 0x2b,
|
||||||
|
0x2b, 0x4e, 0xa4, 0x6e, 0x5d, 0x6e, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||||
|
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||||
|
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||||
|
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||||
|
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xff, 0xc0, 0x00, 0x11,
|
||||||
|
0x08, 0x00, 0xcd, 0x00, 0xcd, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01,
|
||||||
|
0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1a, 0x00, 0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x05, 0x04, 0x06, 0x02, 0x03, 0x01, 0xff, 0xc4, 0x00, 0x3c, 0x10,
|
||||||
|
0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x05, 0x09, 0x05, 0x08, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x11, 0x12,
|
||||||
|
0x21, 0x06, 0x31, 0x13, 0x22, 0x41, 0x91, 0xa1, 0x14, 0x32, 0x51, 0x61,
|
||||||
|
0x71, 0x73, 0x81, 0xb1, 0xd1, 0x34, 0x36, 0x52, 0xc2, 0xe1, 0x16, 0x24,
|
||||||
|
0x33, 0x35, 0x42, 0x44, 0x82, 0xc1, 0x54, 0xb2, 0xf0, 0xff, 0xc4, 0x00,
|
||||||
|
0x14, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
|
||||||
|
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xe6, 0x40, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x56,
|
||||||
|
0xc7, 0x68, 0x65, 0xd1, 0x26, 0x57, 0xcc, 0xe8, 0xfa, 0x3d, 0x3c, 0x93,
|
||||||
|
0x39, 0xce, 0x7e, 0x85, 0x2a, 0x7e, 0x14, 0x6a, 0xac, 0x8b, 0x51, 0x3b,
|
||||||
|
0x91, 0x35, 0x2a, 0x31, 0x18, 0x89, 0x9c, 0x76, 0x2a, 0xfd, 0x00, 0xe6,
|
||||||
|
0x01, 0x42, 0x9e, 0xd8, 0xda, 0xab, 0xa3, 0xa8, 0xa0, 0xaa, 0x8d, 0xcd,
|
||||||
|
0x6a, 0xff, 0x00, 0x15, 0x76, 0xd4, 0x89, 0xcf, 0x09, 0xdb, 0xdb, 0xdc,
|
||||||
|
0x5b, 0x4e, 0x13, 0xa7, 0xe8, 0xb0, 0xb5, 0x32, 0xf4, 0x9f, 0x8b, 0x09,
|
||||||
|
0x8e, 0xef, 0xd4, 0x0e, 0x50, 0x1b, 0x6e, 0xb6, 0xc9, 0x6d, 0x93, 0xa4,
|
||||||
|
0x72, 0x2a, 0x3d, 0x8e, 0x4c, 0xb1, 0xe8, 0x98, 0xcf, 0xea, 0x6d, 0xb2,
|
||||||
|
0xd8, 0x16, 0xe1, 0x0f, 0x94, 0x4d, 0x22, 0xc7, 0x12, 0xae, 0x1a, 0x8d,
|
||||||
|
0x4d, 0xdd, 0xe9, 0x5f, 0x56, 0xe0, 0x67, 0xb1, 0xdb, 0xa3, 0xb9, 0xd4,
|
||||||
|
0xbe, 0x29, 0x1e, 0xe6, 0x23, 0x59, 0xab, 0x2d, 0xf6, 0xa1, 0xf9, 0x55,
|
||||||
|
0x6f, 0x8e, 0x1b, 0xe3, 0x68, 0x1a, 0xf7, 0x2b, 0x16, 0x46, 0x33, 0x52,
|
||||||
|
0xf3, 0xeb, 0x63, 0xea, 0x74, 0x76, 0xab, 0x2a, 0xdb, 0x2e, 0x2f, 0x92,
|
||||||
|
0x39, 0x3a, 0x48, 0x5f, 0x1a, 0xa2, 0x2a, 0xf3, 0x6a, 0xe5, 0x36, 0x5f,
|
||||||
|
0x49, 0x12, 0xee, 0xc5, 0x97, 0x8a, 0x56, 0x36, 0xbd, 0x58, 0xaf, 0x92,
|
||||||
|
0x36, 0xa3, 0x9b, 0xcd, 0xb9, 0x46, 0xee, 0x80, 0x78, 0xbf, 0x5a, 0x62,
|
||||||
|
0xb5, 0xf4, 0x1d, 0x14, 0x8f, 0x7f, 0x49, 0xab, 0x3a, 0xb1, 0xb6, 0x31,
|
||||||
|
0xf5, 0x24, 0x96, 0xb8, 0x8a, 0x86, 0x5a, 0x3f, 0x27, 0xe9, 0x2b, 0x26,
|
||||||
|
0xa9, 0xd7, 0xab, 0x1d, 0x22, 0xaa, 0xe9, 0xc6, 0x39, 0x6f, 0xeb, 0x3f,
|
||||||
|
0x6d, 0x1c, 0x3d, 0x25, 0x74, 0x29, 0x51, 0x3c, 0x8b, 0x14, 0x4e, 0x5e,
|
||||||
|
0xaa, 0x22, 0x65, 0xce, 0x4f, 0x4f, 0xa8, 0x08, 0x87, 0xa8, 0xa3, 0x7c,
|
||||||
|
0xd2, 0xb2, 0x28, 0xd3, 0x53, 0xde, 0xa8, 0xd6, 0xa7, 0xa5, 0x54, 0xe9,
|
||||||
|
0x6a, 0xb8, 0x4d, 0xbd, 0x1a, 0xad, 0x25, 0x43, 0xb5, 0xa2, 0x79, 0xb2,
|
||||||
|
0x22, 0x6f, 0xf1, 0x4e, 0x44, 0xbb, 0x03, 0x7a, 0x2b, 0xf4, 0x0c, 0x95,
|
||||||
|
0x34, 0xb9, 0xae, 0x73, 0x55, 0x1d, 0xd8, 0xba, 0x57, 0x6e, 0xf0, 0x2b,
|
||||||
|
0xd3, 0x70, 0x9c, 0x29, 0x1f, 0xef, 0x35, 0x12, 0x39, 0xeb, 0xd9, 0x1e,
|
||||||
|
0x11, 0x13, 0xbd, 0x17, 0x3e, 0x04, 0xbb, 0xcd, 0x8e, 0x4b, 0x6b, 0x52,
|
||||||
|
0x68, 0xde, 0xb2, 0xc0, 0xab, 0x85, 0x5c, 0x61, 0x5a, 0xbe, 0xbf, 0xa9,
|
||||||
|
0x53, 0x8a, 0x22, 0xaf, 0x92, 0x68, 0x3c, 0x99, 0xb2, 0xbe, 0x14, 0x4c,
|
||||||
|
0xe2, 0x24, 0x55, 0xc3, 0xb3, 0xcd, 0x71, 0xf0, 0xc7, 0xc4, 0xd5, 0x77,
|
||||||
|
0x47, 0xb7, 0x86, 0x5c, 0x95, 0x19, 0x59, 0x12, 0x38, 0xd1, 0xfb, 0xe5,
|
||||||
|
0x75, 0x65, 0x3f, 0xd8, 0x11, 0x6c, 0x76, 0x58, 0x6e, 0x74, 0xf2, 0x49,
|
||||||
|
0x24, 0xaf, 0x62, 0xb5, 0xfa, 0x51, 0x1b, 0x8f, 0x41, 0x4b, 0xf6, 0x4e,
|
||||||
|
0x97, 0xfe, 0x44, 0xde, 0x07, 0xef, 0x07, 0x7d, 0x86, 0x7f, 0x7b, 0xfe,
|
||||||
|
0x90, 0xf3, 0x51, 0x64, 0xb9, 0x4b, 0x2c, 0xf2, 0xb2, 0xe0, 0xac, 0xd4,
|
||||||
|
0xf7, 0x39, 0x8c, 0xd6, 0xec, 0x22, 0x2a, 0xed, 0xbf, 0x67, 0x70, 0x10,
|
||||||
|
0xef, 0x54, 0x0c, 0xb7, 0x56, 0x24, 0x11, 0xbd, 0xcf, 0x45, 0x62, 0x3b,
|
||||||
|
0x2e, 0xe7, 0xda, 0x60, 0x3e, 0xf5, 0xac, 0xa8, 0x8a, 0xa5, 0xf1, 0xd5,
|
||||||
|
0xab, 0xd6, 0x56, 0x6c, 0xba, 0x9d, 0x93, 0xe0, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x07, 0x4f, 0xc1, 0x9e, 0x6d, 0x5f, 0xb5, 0x9f,
|
||||||
|
0x98, 0x97, 0x7a, 0xad, 0xa9, 0x75, 0xda, 0xa3, 0x13, 0x48, 0xd4, 0x63,
|
||||||
|
0x95, 0x8d, 0x46, 0xb9, 0x51, 0x11, 0x39, 0x78, 0x95, 0x38, 0x33, 0xcd,
|
||||||
|
0xab, 0xf6, 0xb3, 0xf3, 0x10, 0xee, 0xff, 0x00, 0xcd, 0x6a, 0xfd, 0xeb,
|
||||||
|
0xbe, 0x60, 0x7d, 0x2d, 0x16, 0xea, 0x8b, 0x85, 0x4e, 0x20, 0x7f, 0x47,
|
||||||
|
0xd1, 0xe1, 0xce, 0x93, 0xf0, 0xef, 0xb6, 0x3d, 0x7f, 0x42, 0xe3, 0x38,
|
||||||
|
0x6a, 0x76, 0xd4, 0xa5, 0x42, 0x5c, 0xde, 0xb3, 0x22, 0xe7, 0x5a, 0xc7,
|
||||||
|
0x95, 0xff, 0x00, 0xb1, 0x97, 0x84, 0x2a, 0x62, 0x8a, 0x6a, 0x88, 0x64,
|
||||||
|
0x7a, 0x35, 0xf2, 0xa3, 0x74, 0x65, 0x71, 0x9c, 0x67, 0x6f, 0x6e, 0xe6,
|
||||||
|
0x9a, 0xae, 0x1e, 0xa5, 0x8e, 0xa2, 0x4a, 0xba, 0x9a, 0xd7, 0x36, 0x15,
|
||||||
|
0x72, 0xbd, 0xc8, 0xa9, 0xbe, 0xfb, 0xe3, 0x3f, 0xa0, 0x0e, 0x31, 0x6a,
|
||||||
|
0x79, 0x35, 0x3b, 0xb1, 0xba, 0x3d, 0x53, 0x3f, 0x0f, 0xd0, 0xd8, 0xd7,
|
||||||
|
0x2d, 0x3f, 0x0b, 0xb5, 0xd1, 0x75, 0x1c, 0x94, 0xc8, 0xa8, 0xa9, 0xb6,
|
||||||
|
0x15, 0x53, 0x9f, 0x89, 0x97, 0x8c, 0x7e, 0xc7, 0x07, 0xbc, 0x5f, 0x91,
|
||||||
|
0xf6, 0xb0, 0xd5, 0xc1, 0x5f, 0x6a, 0x4a, 0x39, 0x5c, 0x8b, 0x23, 0x18,
|
||||||
|
0xb1, 0xbd, 0x8b, 0xb2, 0xab, 0x79, 0x22, 0xa7, 0xc3, 0x00, 0x4f, 0xe0,
|
||||||
|
0xf9, 0xa4, 0x5a, 0xa9, 0xe2, 0x57, 0xaa, 0xb1, 0x59, 0xaf, 0x0a, 0xbd,
|
||||||
|
0xb9, 0x4d, 0xfc, 0x4f, 0x8d, 0xc7, 0xef, 0x7b, 0x3d, 0xf4, 0x5f, 0x26,
|
||||||
|
0x97, 0x6d, 0x96, 0xea, 0x2b, 0x6d, 0x53, 0xe2, 0x82, 0x47, 0x3e, 0x67,
|
||||||
|
0xb3, 0x2a, 0x8f, 0x54, 0x55, 0x6b, 0x51, 0x7d, 0x49, 0xb7, 0x3f, 0x02,
|
||||||
|
0x15, 0xc7, 0xef, 0x7b, 0x3d, 0xf4, 0x5f, 0x26, 0x81, 0xab, 0x8c, 0xff,
|
||||||
|
0x00, 0xb3, 0xff, 0x00, 0x3f, 0xca, 0x6f, 0xb8, 0x48, 0xfa, 0x5e, 0x19,
|
||||||
|
0x47, 0x40, 0xf5, 0x63, 0x9b, 0x14, 0x68, 0x8e, 0x45, 0xdd, 0x39, 0x27,
|
||||||
|
0xc8, 0xc1, 0xc6, 0x7f, 0xd9, 0xff, 0x00, 0x9f, 0xe5, 0x36, 0x5a, 0xea,
|
||||||
|
0xa9, 0x2e, 0xd6, 0x96, 0xd1, 0x4c, 0xa9, 0xad, 0x18, 0x8c, 0x7b, 0x35,
|
||||||
|
0x61, 0x57, 0x1c, 0x95, 0x3b, 0x91, 0x40, 0xc5, 0xc2, 0x35, 0x53, 0x3e,
|
||||||
|
0x79, 0xe1, 0x7c, 0x8e, 0x7b, 0x34, 0x23, 0x91, 0x1c, 0xaa, 0xb8, 0x5c,
|
||||||
|
0xf6, 0x77, 0x93, 0x6f, 0xeb, 0xd0, 0xdf, 0xa7, 0x74, 0x5d, 0x47, 0x35,
|
||||||
|
0xcd, 0x72, 0x2a, 0x6d, 0x85, 0xd2, 0x8b, 0x9e, 0xf3, 0xa6, 0xa5, 0xa3,
|
||||||
|
0xa0, 0xb1, 0x43, 0x24, 0x8b, 0x2a, 0xb5, 0x1d, 0xcd, 0xf2, 0xb9, 0x32,
|
||||||
|
0xb8, 0xec, 0x43, 0x8f, 0xb8, 0xd5, 0x2d, 0x6d, 0x74, 0xd5, 0x18, 0xc2,
|
||||||
|
0x3d, 0xdb, 0x22, 0xf6, 0x27, 0x24, 0xf0, 0xc0, 0x1d, 0x1d, 0xa3, 0x88,
|
||||||
|
0x27, 0xad, 0xa8, 0x8a, 0x95, 0xd4, 0xad, 0x73, 0xd7, 0xce, 0x91, 0x1d,
|
||||||
|
0x84, 0x44, 0x4e, 0xdc, 0x63, 0xff, 0x00, 0x29, 0xe3, 0x8b, 0xeb, 0x34,
|
||||||
|
0xc5, 0x15, 0x1b, 0x57, 0x77, 0x2f, 0x48, 0xff, 0x00, 0x67, 0x24, 0xf1,
|
||||||
|
0xcf, 0x71, 0xeb, 0x84, 0xa8, 0xd2, 0x2a, 0x69, 0x2b, 0x5f, 0xb2, 0xc9,
|
||||||
|
0xd5, 0x6a, 0xaf, 0x63, 0x53, 0x9a, 0xf7, 0xfc, 0x8e, 0x7a, 0xe7, 0x56,
|
||||||
|
0xb5, 0xd5, 0xf3, 0x54, 0x7f, 0x4b, 0x9d, 0xd5, 0x4f, 0x43, 0x53, 0x64,
|
||||||
|
0xf0, 0x03, 0x6d, 0x9a, 0xf0, 0xfb, 0x6c, 0x0f, 0x8d, 0xb4, 0xab, 0x32,
|
||||||
|
0x39, 0xda, 0xb3, 0xab, 0x18, 0xdb, 0xd8, 0x6c, 0xb3, 0x5e, 0xab, 0x6a,
|
||||||
|
0xef, 0x2c, 0x8e, 0x59, 0x11, 0x62, 0x97, 0x57, 0x53, 0x09, 0x86, 0xec,
|
||||||
|
0xaa, 0x98, 0x3c, 0xf0, 0xad, 0xce, 0x1a, 0x74, 0x92, 0x92, 0x77, 0xb6,
|
||||||
|
0x34, 0x7b, 0xb5, 0xb1, 0xce, 0xd9, 0x33, 0x8d, 0xd1, 0x57, 0xe0, 0x85,
|
||||||
|
0x88, 0x6d, 0x14, 0x14, 0x55, 0x6f, 0xae, 0x6a, 0xab, 0x15, 0x32, 0xbd,
|
||||||
|
0x67, 0x22, 0x31, 0x99, 0xe6, 0xa8, 0x04, 0x4e, 0x30, 0x6e, 0x2e, 0x31,
|
||||||
|
0x3b, 0xd3, 0x12, 0x27, 0x8a, 0x90, 0x4a, 0x7c, 0x43, 0x5d, 0x1d, 0x7d,
|
||||||
|
0xc5, 0x5f, 0x0e, 0xf1, 0xc6, 0xd4, 0x62, 0x3b, 0xf1, 0x73, 0x5c, 0xf8,
|
||||||
|
0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd9
|
||||||
|
};
|
||||||
|
static const unsigned int station_logo_len = sizeof(station_logo);
|
||||||
52
src/resampler.c
Normal file
52
src/resampler.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "resampler.h"
|
||||||
|
|
||||||
|
int8_t resampler_init(SRC_STATE **src_state, uint8_t channels) {
|
||||||
|
int src_error;
|
||||||
|
|
||||||
|
*src_state = src_new(CONVERTER_TYPE, channels, &src_error);
|
||||||
|
|
||||||
|
if (*src_state == NULL) {
|
||||||
|
fprintf(stderr, "Error: src_new failed: %s\n", src_strerror(src_error));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t resample(SRC_STATE *src_state, SRC_DATA src_data, size_t *frames_generated) {
|
||||||
|
int src_error;
|
||||||
|
|
||||||
|
src_error = src_process(src_state, &src_data);
|
||||||
|
|
||||||
|
if (src_error) {
|
||||||
|
fprintf(stderr, "Error: src_process failed: %s\n", src_strerror(src_error));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*frames_generated = src_data.output_frames_gen;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resampler_exit(SRC_STATE *src_state) {
|
||||||
|
src_delete(src_state);
|
||||||
|
}
|
||||||
25
src/resampler.h
Normal file
25
src/resampler.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||||
|
* Copyright (C) 2019 Anthony96922
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <samplerate.h>
|
||||||
|
|
||||||
|
#define CONVERTER_TYPE SRC_SINC_MEDIUM_QUALITY
|
||||||
|
|
||||||
|
extern int8_t resampler_init(SRC_STATE **src_state, uint8_t channels);
|
||||||
|
extern int8_t resample(SRC_STATE *src_state, SRC_DATA src_data, size_t *frames_generated);
|
||||||
|
extern void resampler_exit(SRC_STATE *src_state);
|
||||||
BIN
src/station_logo.jpg
Normal file
BIN
src/station_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
27
src/supported_cmds.txt
Normal file
27
src/supported_cmds.txt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
AF - Mode A only
|
||||||
|
AFCH - Mode A only
|
||||||
|
DI
|
||||||
|
MS
|
||||||
|
PI
|
||||||
|
PS
|
||||||
|
TPS
|
||||||
|
PTY
|
||||||
|
PTYN
|
||||||
|
PTYNEN
|
||||||
|
TEXT | RT1
|
||||||
|
RT1EN
|
||||||
|
TA
|
||||||
|
TP
|
||||||
|
CT
|
||||||
|
LEVEL - Not redecommended, if possible use `MPX`
|
||||||
|
RDSGEN
|
||||||
|
ECC
|
||||||
|
ECCEN
|
||||||
|
G
|
||||||
|
LIC
|
||||||
|
PIN
|
||||||
|
PINEN
|
||||||
|
RTP
|
||||||
|
RTPRUN
|
||||||
|
|
||||||
|
(Magic RDS 3 'Program' page is fully supported.)
|
||||||
26
src/todo.txt
Normal file
26
src/todo.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Current TODO for RDS encoding:
|
||||||
|
- UECP Decoding
|
||||||
|
- Dynamic PS (may not be implemented)
|
||||||
|
- Bidirectionality
|
||||||
|
|
||||||
|
ASCII commands to implement:
|
||||||
|
- Group sequence
|
||||||
|
- Short RT
|
||||||
|
|
||||||
|
ASCII Group seqeunce:
|
||||||
|
0 - Four 0As (PS x 4)
|
||||||
|
1 - 1A (ECC/LIC, PIN)
|
||||||
|
2 - 2A (RT)
|
||||||
|
A - 10A (PTYN)
|
||||||
|
E - 14A 14B (EON, yet to implement)
|
||||||
|
X - UDG1 (yet to implement, max 8 groups per udg)
|
||||||
|
Y - UDG2 (yet to implement)
|
||||||
|
R - RT+ 3A 11A (do i switch between them like ecc lic?)
|
||||||
|
F - 15A (LPS)
|
||||||
|
Max 24 items in a sequence.
|
||||||
|
|
||||||
|
Note that most of this is takes out of pira.cz's P164 RDS Encoder's manual: https://pira.cz/rds/p164man.pdf
|
||||||
|
|
||||||
|
I plan for this's commands to be similiar to PIRA32's or P164's (both are made by pira.cz)
|
||||||
|
|
||||||
|
In theory, now you can use the MiniRDS as a PIRA32 type device in Magic RDS 4 (but not everything is supported)
|
||||||
21
src/uecp_rds_todo.txt
Normal file
21
src/uecp_rds_todo.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
This aims to have the around the same options as UECP should have, this is what is not yet implemented:
|
||||||
|
- PIN (low priority, how do i decode that?)
|
||||||
|
- EON AF (very low priority)
|
||||||
|
- Linkage information (very low priority)
|
||||||
|
- (ODA skipped)
|
||||||
|
- TDC (low priority)
|
||||||
|
- EWS (how do i even implement that? can someone give docs?)
|
||||||
|
- IH (low priority)
|
||||||
|
- (Paging skipped)
|
||||||
|
- (No CT time adjustments)
|
||||||
|
- RDS on/off (low priority)
|
||||||
|
- RDS phase (might be medium, 9.5 degree steps)
|
||||||
|
- (ARI skipped)
|
||||||
|
|
||||||
|
Others:
|
||||||
|
- Encoder identification (site and encoder address)
|
||||||
|
- (PSN skipped)
|
||||||
|
- (EON skipped)
|
||||||
|
- Data set select
|
||||||
|
- Group sequence
|
||||||
|
- (other sequence related comamnds skipped)
|
||||||
7
src/waveforms.c
Normal file
7
src/waveforms.c
Normal file
File diff suppressed because one or more lines are too long
7
src/waveforms.h
Normal file
7
src/waveforms.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
/* This file was automatically generated by "generate_waveforms.py".
|
||||||
|
(C) 2014 Christophe Jacquet.
|
||||||
|
Released under the GNU GPL v3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern float waveform_biphase[1120];
|
||||||
Reference in New Issue
Block a user