pax_global_header00006660000000000000000000000064135737654130014530gustar00rootroot0000000000000052 comment=fc519b017e0e5ea336e98a10c8c0f460908e754d hacktv-master/000077500000000000000000000000001357376541300136455ustar00rootroot00000000000000hacktv-master/.gitignore000066400000000000000000000000411357376541300156300ustar00rootroot00000000000000*.o *.d *.exe build_win64 hacktv hacktv-master/COPYING000066400000000000000000001045131357376541300147040ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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. Copyright (C) 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 . 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: Copyright (C) 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 . 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 . hacktv-master/Makefile000066400000000000000000000021201357376541300153000ustar00rootroot00000000000000 CC := $(CROSS_HOST)gcc PKGCONF := $(CROSS_HOST)pkg-config CFLAGS := -g -Wall -pthread -O3 $(EXTRA_CFLAGS) LDFLAGS := -g -lm -pthread $(EXTRA_LDFLAGS) OBJS := hacktv.o common.o fir.o vbidata.o teletext.o wss.o video.o mac.o videocrypt.o videocrypts.o syster.o acp.o nicam728.o test.o ffmpeg.o file.o hackrf.o PKGS := libavcodec libavformat libavdevice libswscale libswresample libavutil libhackrf $(EXTRA_PKGS) SOAPYSDR := $(shell $(PKGCONF) --exists SoapySDR && echo SoapySDR) ifeq ($(SOAPYSDR),SoapySDR) OBJS += soapysdr.o PKGS += SoapySDR CFLAGS += -DHAVE_SOAPYSDR endif FL2K := $(shell $(PKGCONF) --exists libosmo-fl2k && echo fl2k) ifeq ($(FL2K),fl2k) OBJS += fl2k.o PKGS += libosmo-fl2k CFLAGS += -DHAVE_FL2K endif CFLAGS += $(shell $(PKGCONF) --cflags $(PKGS)) LDFLAGS += $(shell $(PKGCONF) --libs $(PKGS)) all: hacktv hacktv: $(OBJS) $(CC) -o hacktv $(OBJS) $(LDFLAGS) %.o: %.c Makefile $(CC) $(CFLAGS) -c $< -o $@ @$(CC) $(CFLAGS) -MM $< -o $(@:.o=.d) install: cp -f hacktv $(PREFIX)/usr/local/bin/ clean: rm -f *.o *.d hacktv hacktv.exe -include $(OBJS:.o=.d) hacktv-master/README000066400000000000000000000036071357376541300145330ustar00rootroot00000000000000 -[ HackTV - Analogue TV transmitter for the HackRF ]- WHAT'S IT DO Generates a PAL, NTSC, SECAM*, D/D2-MAC video signal from a video file, stream or test pattern. Also supports older 819, 405, 240 and 30 line standards, as well as the NASA Apollo video standards, both colour and mono. Input is any file type or URL supported by ffmpeg. Output can be to a file, HackRF, fl2k-supported VGA adaptors or any SDR supported by SoapySDR. It also supports: + Teletext (625-line only) + NICAM stereo audio + Videocrypt I/II hardware support + Videocrypt S simulator + Partial Nagravision Syster hardware support + Analogue Copy Protection system, similar to Macrovision * SECAM support is very basic and won't produce a good image WHAT'S IT NOT DO (yet) + There are no filters. Needed for proper audio and VSB modulation + An optional notch filter for the colour subcarrier would be nice WHAT IT WON'T DO + DVB or other pure digital signals + Bring back Firefly :( REQUIREMENTS Depends on libhackrf and various ffmpeg libraries. * For Fedora (with rpmfusion) yum install hackrf-devel osmo-fl2k-devel SoapySDR-devel ffmpeg-devel * For Debian and related apt-get update apt-get install libhackrf-dev libavutil-dev libavdevice-dev libswresample-dev libswscale-dev libavformat-dev libavcodec-dev * On Debian (sid) apt-get install hacktv INSTALL make make install EXAMPLES # Generate a file containing a PAL baseband signal from a video $ hacktv -o baseband.bin -m pal example.mkv # Transmit a test pattern on UHF channel 31 (PAL System I), 47dB TX gain $ hacktv -f 551250000 -m i -g 47 test # Transmit a test pattern with teletext $ hacktv -f 551250000 -m i -g 47 --teletext demo.tti test # Download and transmit teletext pages from the Teefax service $ svn checkout http://teastop.plus.com/svn/teletext/ teefax $ hacktv -f 551250000 -m i -g 47 --teletext teefax test -Philip Heron hacktv-master/acp.c000066400000000000000000000066231357376541300145630ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ /* -=== ACP / Macrovision encoder ===- */ #include #include #include #include #include "video.h" int acp_init(acp_t *s, vid_t *vid) { double left; double spacing; double psync_width; int i; memset(s, 0, sizeof(acp_t)); s->vid = vid; if(s->vid->conf.lines == 625) { left = 8.88e-6; spacing = 5.92e-6; psync_width = 2.368e-6; } else { left = 8.288e-6; spacing = 8.288e-6; psync_width = 2.222e-6; } /* Calculate the levels */ s->psync_level = vid->sync_level + round((vid->white_level - vid->sync_level) * 0.06); s->pagc_level = vid->sync_level + round((vid->white_level - vid->sync_level) * 1.10); /* Calculate the width of each pulse */ s->psync_width = round(vid->sample_rate * psync_width); s->pagc_width = round(vid->sample_rate * 2.7e-6); /* Left position of each pulse */ for(i = 0; i < 6; i++) { s->left[i] = round(vid->sample_rate * (left + spacing * i)); } return(VID_OK); } void acp_free(acp_t *s) { if(s == NULL) return; memset(s, 0, sizeof(acp_t)); } void acp_render_line(acp_t *s) { int i, x; i = 0; if(s->vid->line == 1) { /* Vary the AGC pulse level, clipped sawtooth waveform */ i = abs(s->vid->frame * 4 % 1712 - 856) - 150; if(i < 0) i = 0; else if(i > 255) i = 255; i = s->vid->y_level_lookup[i << 16 | i << 8 | i]; s->pagc_level = s->vid->sync_level + round((i - s->vid->sync_level) * 1.10); } i = 0; if(s->vid->conf.lines == 625) { /* For 625-line modes, ACP is rendered on lines 9-18 and 321-330 */ if(s->vid->line >= 9 && s->vid->line <= 18) i = 1; if(s->vid->line >= 321 && s->vid->line <= 330) i = 1; } else { /* For 525-line modes, ACP is rendered on lines 12-19 and 275-282 */ if(s->vid->line >= 12 && s->vid->line <= 19) i = 1; if(s->vid->line >= 275 && s->vid->line <= 282) i = 1; } if(i == 0) return; /* Render the P-Sync / AGC pulse pairs */ for(i = 0; i < 6; i++) { /* Render the P-Sync pulse */ for(x = s->left[i]; x < s->left[i] + s->psync_width; x++) { s->vid->output[x * 2] = s->psync_level; } /* Render the AGC pulse */ for(; x < s->left[i] + s->psync_width + s->pagc_width; x++) { s->vid->output[x * 2] = s->pagc_level; } } } hacktv-master/acp.h000066400000000000000000000030171357376541300145620ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ #ifndef _ACP_H #define _ACP_H #include #include "video.h" typedef struct { vid_t *vid; int left[6]; int16_t psync_level; int16_t pagc_level; int psync_width; int pagc_width; } acp_t; extern int acp_init(acp_t *s, vid_t *vid); extern void acp_free(acp_t *s); extern void acp_render_line(acp_t *s); #endif hacktv-master/build_win64.sh000077500000000000000000000055641357376541300163440ustar00rootroot00000000000000#!/bin/bash set -e set -x HOST=x86_64-w64-mingw32 PREFIX=$(pwd)/build_win64/install_root export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig mkdir -p $PREFIX cd build_win64 if [[ ! -f $PREFIX/lib/libusb-1.0.a ]]; then if [[ ! -f libusb-1.0.22.tar.bz2 ]]; then wget https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2 tar -xvjf libusb-1.0.22.tar.bz2 fi cd libusb-1.0.22 ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared make -j4 install cd .. fi if [[ ! -f $PREFIX/lib/libhackrf.a ]]; then if [[ ! -f hackrf-2018.01.1.tar.gz ]]; then wget https://github.com/mossmann/hackrf/archive/v2018.01.1/hackrf-2018.01.1.tar.gz tar -xvzf hackrf-2018.01.1.tar.gz fi rm -rf hackrf-2018.01.1/host/libhackrf/build mkdir -p hackrf-2018.01.1/host/libhackrf/build cd hackrf-2018.01.1/host/libhackrf/build mingw64-cmake \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DCMAKE_INSTALL_LIBPREFIX=$PREFIX/lib \ -DLIBUSB_INCLUDE_DIR=$PREFIX/include/libusb-1.0 \ -DLIBUSB_LIBRARIES=$PREFIX/lib/libusb-1.0.a make -j4 install cd ../../../.. mv $PREFIX/bin/*.a $PREFIX/lib/ find $PREFIX -name libhackrf\*.dll\* -delete fi if [[ ! -f $PREFIX/lib/libosmo-fl2k.a ]]; then if [[ ! -d osmo-fl2k ]]; then git clone --depth 1 git://git.osmocom.org/osmo-fl2k fi rm -rf osmo-fl2k/build mkdir -p osmo-fl2k/build cd osmo-fl2k/build mingw64-cmake \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DCMAKE_INSTALL_LIBPREFIX=$PREFIX \ -DCMAKE_INSTALL_LIBDIR=$PREFIX/lib \ -DLIBUSB_INCLUDE_DIR=$PREFIX/include/libusb-1.0 \ -DLIBUSB_LIBRARIES=$PREFIX/lib/libusb-1.0.a make -j4 install cd ../.. mv $PREFIX/lib/liblibosmo-fl2k_static.a $PREFIX/lib/libosmo-fl2k.a fi if [[ ! -f $PREFIX/lib/libfdk-aac.a ]]; then if [[ ! -d fdk-aac ]]; then git clone https://github.com/mstorsjo/fdk-aac.git fi cd fdk-aac ./autogen.sh ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared make -j4 install cd .. fi if [[ ! -f $PREFIX/lib/libopus.a ]]; then if [[ ! -f opus-1.3.tar.gz ]]; then wget https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz tar -xvzf opus-1.3.tar.gz fi cd opus-1.3 ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared --disable-doc --disable-extra-programs make -j4 install cd .. fi if [[ ! -f $PREFIX/lib/libavformat.a ]]; then if [[ ! -d ffmpeg ]]; then git clone --depth 1 https://github.com/FFmpeg/FFmpeg.git ffmpeg fi cd ffmpeg ./configure \ --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libopus \ --enable-static --disable-shared --disable-programs \ --disable-outdevs --disable-encoders \ --arch=x86_64 --target-os=mingw64 --cross-prefix=$HOST- \ --pkg-config=pkg-config --prefix=$PREFIX make -j4 install cd .. fi cd .. CROSS_HOST=$HOST- make -j4 EXTRA_LDFLAGS="-static" EXTRA_PKGS="libusb-1.0" mv -f hacktv hacktv.exe || true $HOST-strip hacktv.exe echo "Done" hacktv-master/common.c000066400000000000000000000033711357376541300153050ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #include #include #include #include "common.h" int gcd(int a, int b) { int c; while((c = a % b)) { a = b; b = c; } return(b); } cint16_t *sin_cint16(unsigned int length, unsigned int cycles, double level) { cint16_t *lut; unsigned int i; double d; lut = malloc(length * sizeof(cint16_t)); if(!lut) { return(NULL); } d = 2.0 * M_PI / length * cycles; for(i = 0; i < length; i++) { lut[i].i = round(cos(d * i) * level * INT16_MAX); lut[i].q = round(sin(d * i) * level * INT16_MAX); } return(lut); } hacktv-master/common.h000066400000000000000000000051261357376541300153120ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #ifndef _COMMON_H #define _COMMON_H #include typedef struct { int16_t i; int16_t q; } cint16_t; typedef struct { int32_t i; int32_t q; } cint32_t; static inline void cint16_mul(cint16_t *r, const cint16_t *a, const cint16_t *b) { int32_t i, q; i = (int32_t) a->i * (int32_t) b->i - (int32_t) a->q * (int32_t) b->q; q = (int32_t) a->i * (int32_t) b->q + (int32_t) a->q * (int32_t) b->i; r->i = i >> 15; r->q = q >> 15; } static inline void cint16_mula(cint16_t *r, const cint16_t *a, const cint16_t *b) { int32_t i, q; i = (int32_t) a->i * (int32_t) b->i - (int32_t) a->q * (int32_t) b->q; q = (int32_t) a->i * (int32_t) b->q + (int32_t) a->q * (int32_t) b->i; r->i += i >> 15; r->q += q >> 15; } static inline void cint32_mul(cint32_t *r, const cint32_t *a, const cint32_t *b) { int64_t i, q; i = (int64_t) a->i * (int64_t) b->i - (int64_t) a->q * (int64_t) b->q; q = (int64_t) a->i * (int64_t) b->q + (int64_t) a->q * (int64_t) b->i; r->i = i >> 31; r->q = q >> 31; } static inline void cint32_mula(cint32_t *r, const cint32_t *a, const cint32_t *b) { int64_t i, q; i = (int64_t) a->i * (int64_t) b->i - (int64_t) a->q * (int64_t) b->q; q = (int64_t) a->i * (int64_t) b->q + (int64_t) a->q * (int64_t) b->i; r->i += i >> 31; r->q += q >> 31; } extern int gcd(int a, int b); extern cint16_t *sin_cint16(unsigned int length, unsigned int cycles, double level); #endif hacktv-master/demo.tti000066400000000000000000000022551357376541300153170ustar00rootroot00000000000000PN,10000 DE,edit-tf PS,8000 SC,3F7F OL,1,D ]G \ OL,2,T oooooooooooooooooo OL,3,TZ9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9) OL,4,TZ ! "$ " ! $ ! $ OL,5,WZx t`~}0xt`~5`~=`~}0x t OL,6,WZ j7#k5#+/j}' "##!  OL,7,W ||j5 j5 ju    OL,8,W //j5 j7o}0    OL,9,W  j7#k5px|j5 k  +tx' OL,10,W + '"o5 j?!+'"o5 j? + "o?! OL,11,TZ $ 0 ( 00 0 ` 0$ 2( 0! OL,12,TZfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfd OL,13,T }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} OL,14,D ]G \ OL,15,D ]G Hello, World! \ OL,16,D ]G \ OL,17,D ]G \ OL,18,D ]G \ OL,19,D ]G \ OL,20,D ]G \ OL,21,D ]G \ OL,22,D ]Ghttps://github.com/fsphil/hacktv \ OL,23,D ]G \ hacktv-master/ffmpeg.c000066400000000000000000000674111357376541300152660ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* Thread summary: * * Input - Reads the data from disk/network and feeds the * audio and/or video packet queues. Sets an EOF * flag on all queues when the input reaches the * end. Ends at EOF or abort. * * Video decoder - Reads from the video packet queue and produces * the decoded video frames. * * Video scaler - Rescales decoded video frames to the correct * size and format required by hacktv. * * Audio thread - Reads from the audio packet queue and produces * the decoded. * * Audio resampler - Resamples the decoded audio frames to the format * required by hacktv (32000Hz, Stereo, 16-bit) */ #include #include #include #include #include #include #include #include #include #include "hacktv.h" /* Maximum length of the packet queue */ /* Taken from ffplay.c */ #define MAX_QUEUE_SIZE (15 * 1024 * 1024) typedef struct __packet_queue_item_t { AVPacket pkt; struct __packet_queue_item_t *next; } _packet_queue_item_t; typedef struct { int length; /* Number of packets */ int size; /* Number of bytes used */ int eof; /* End of stream / file flag */ int abort; /* Abort flag */ /* Pointers to the first and last packets in the queue */ _packet_queue_item_t *first; _packet_queue_item_t *last; /* Thread locking and signaling */ pthread_mutex_t mutex; pthread_cond_t cond; } _packet_queue_t; typedef struct { int ready; /* Frame ready flag */ int repeat; /* Repeat the previous frame */ int abort; /* Abort flag */ /* The AVFrame buffers */ AVFrame *frame[2]; /* Thread locking and signaling */ pthread_mutex_t mutex; pthread_cond_t cond; } _frame_dbuffer_t; typedef struct { AVFormatContext *format_ctx; /* Video decoder */ AVRational video_time_base; int64_t video_start_time; _packet_queue_t video_queue; AVStream *video_stream; AVCodecContext *video_codec_ctx; _frame_dbuffer_t in_video_buffer; int video_eof; /* Video scaling */ struct SwsContext *sws_ctx; _frame_dbuffer_t out_video_buffer; /* Audio decoder */ AVRational audio_time_base; int64_t audio_start_time; _packet_queue_t audio_queue; AVStream *audio_stream; AVCodecContext *audio_codec_ctx; _frame_dbuffer_t in_audio_buffer; int audio_eof; /* Audio resampler */ struct SwrContext *swr_ctx; _frame_dbuffer_t out_audio_buffer; int out_frame_size; int allowed_error; /* Threads */ pthread_t input_thread; pthread_t video_decode_thread; pthread_t video_scaler_thread; pthread_t audio_decode_thread; pthread_t audio_scaler_thread; volatile int thread_abort; } av_ffmpeg_t; static void _print_ffmpeg_error(int r) { char sb[128]; const char *sp = sb; if(av_strerror(r, sb, sizeof(sb)) < 0) { sp = strerror(AVUNERROR(r)); } fprintf(stderr, "%s\n", sp); } void _audio_offset(uint8_t const **dst, uint8_t const * const *src, int offset, int nb_channels, enum AVSampleFormat sample_fmt) { int planar = av_sample_fmt_is_planar(sample_fmt); int planes = planar ? nb_channels : 1; int block_align = av_get_bytes_per_sample(sample_fmt) * (planar ? 1 : nb_channels); int i; offset *= block_align; for(i = 0; i < planes; i++) { dst[i] = src[i] + offset; } } static int _packet_queue_init(_packet_queue_t *q) { q->length = 0; q->size = 0; q->eof = 0; q->abort = 0; pthread_mutex_init(&q->mutex, NULL); pthread_cond_init(&q->cond, NULL); return(0); } static int _packet_queue_flush(_packet_queue_t *q) { _packet_queue_item_t *p; pthread_mutex_lock(&q->mutex); while(q->length--) { /* Pop the first item off the list */ p = q->first; q->first = p->next; av_packet_unref(&p->pkt); free(p); } pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); return(0); } static void _packet_queue_free(_packet_queue_t *q) { _packet_queue_flush(q); pthread_cond_destroy(&q->cond); pthread_mutex_destroy(&q->mutex); } static void _packet_queue_abort(_packet_queue_t *q) { pthread_mutex_lock(&q->mutex); q->abort = 1; pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); } static int _packet_queue_write(_packet_queue_t *q, AVPacket *pkt) { _packet_queue_item_t *p; pthread_mutex_lock(&q->mutex); /* A NULL packet signals the end of the stream / file */ if(pkt == NULL) { q->eof = 1; } else { /* Limit the size of the queue */ while(q->abort == 0 && q->size + pkt->size + sizeof(_packet_queue_item_t) > MAX_QUEUE_SIZE) { pthread_cond_wait(&q->cond, &q->mutex); } if(q->abort == 1) { /* Abort was called while waiting for the queue size to drop */ av_packet_unref(pkt); pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); return(-2); } /* Allocate memory for queue item and copy packet */ p = malloc(sizeof(_packet_queue_item_t)); p->pkt = *pkt; p->next = NULL; /* Add the item to the end of the queue */ if(q->length == 0) { q->first = p; } else { q->last->next = p; } q->last = p; q->length++; q->size += pkt->size + sizeof(_packet_queue_item_t); } pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); return(0); } static int _packet_queue_read(_packet_queue_t *q, AVPacket *pkt) { _packet_queue_item_t *p; pthread_mutex_lock(&q->mutex); while(q->length == 0) { if(q->abort == 1 || q->eof == 1) { pthread_mutex_unlock(&q->mutex); return(q->abort == 1 ? -2 : -1); } pthread_cond_wait(&q->cond, &q->mutex); } p = q->first; *pkt = p->pkt; q->first = p->next; q->length--; q->size -= pkt->size + sizeof(_packet_queue_item_t); free(p); pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); return(0); } static int _frame_dbuffer_init(_frame_dbuffer_t *d) { d->ready = 0; d->repeat = 0; d->abort = 0; d->frame[0] = av_frame_alloc(); d->frame[1] = av_frame_alloc(); if(!d->frame[0] || !d->frame[1]) { av_frame_free(&d->frame[0]); av_frame_free(&d->frame[1]); return(-1); } pthread_mutex_init(&d->mutex, NULL); pthread_cond_init(&d->cond, NULL); return(0); } static void _frame_dbuffer_free(_frame_dbuffer_t *d) { pthread_cond_destroy(&d->cond); pthread_mutex_destroy(&d->mutex); av_frame_free(&d->frame[0]); av_frame_free(&d->frame[1]); } static void _frame_dbuffer_abort(_frame_dbuffer_t *d) { pthread_mutex_lock(&d->mutex); d->abort = 1; pthread_cond_signal(&d->cond); pthread_mutex_unlock(&d->mutex); } static AVFrame *_frame_dbuffer_back_buffer(_frame_dbuffer_t *d) { AVFrame *frame; pthread_mutex_lock(&d->mutex); /* Wait for the ready flag to be unset */ while(d->ready != 0 && d->abort == 0) { pthread_cond_wait(&d->cond, &d->mutex); } frame = d->frame[1]; pthread_mutex_unlock(&d->mutex); return(frame); } static void _frame_dbuffer_ready(_frame_dbuffer_t *d, int repeat) { pthread_mutex_lock(&d->mutex); /* Wait for the ready flag to be unset */ while(d->ready != 0 && d->abort == 0) { pthread_cond_wait(&d->cond, &d->mutex); } d->ready = 1; d->repeat = repeat; pthread_cond_signal(&d->cond); pthread_mutex_unlock(&d->mutex); } static AVFrame *_frame_dbuffer_flip(_frame_dbuffer_t *d) { AVFrame *frame; pthread_mutex_lock(&d->mutex); /* Wait for a flag to be set */ while(d->ready == 0 && d->abort == 0) { pthread_cond_wait(&d->cond, &d->mutex); } /* Die if it was the abort flag */ if(d->abort != 0) { pthread_mutex_unlock(&d->mutex); return(NULL); } /* Swap the frames if we're not repeating */ if(d->repeat == 0) { frame = d->frame[1]; d->frame[1] = d->frame[0]; d->frame[0] = frame; } frame = d->frame[0]; d->ready = 0; /* Signal we're finished and release the mutex */ pthread_cond_signal(&d->cond); pthread_mutex_unlock(&d->mutex); return(frame); } static void *_input_thread(void *arg) { av_ffmpeg_t *av = (av_ffmpeg_t *) arg; AVPacket pkt; int r; //fprintf(stderr, "_input_thread(): Starting\n"); /* Fetch packets from the source */ while(av->thread_abort == 0) { r = av_read_frame(av->format_ctx, &pkt); if(r == AVERROR(EAGAIN)) { av_usleep(10000); continue; } else if(r < 0) { /* FFmpeg input EOF or error. Break out */ break; } if(av->video_stream && pkt.stream_index == av->video_stream->index) { _packet_queue_write(&av->video_queue, &pkt); } else if(av->audio_stream && pkt.stream_index == av->audio_stream->index) { _packet_queue_write(&av->audio_queue, &pkt); } else { av_packet_unref(&pkt); } } /* Set the EOF flag in the queues */ _packet_queue_write(&av->video_queue, NULL); _packet_queue_write(&av->audio_queue, NULL); //fprintf(stderr, "_input_thread(): Ending\n"); return(NULL); } static void *_video_decode_thread(void *arg) { av_ffmpeg_t *av = (av_ffmpeg_t *) arg; AVPacket pkt, *ppkt = NULL; AVFrame *frame; int r; //fprintf(stderr, "_video_decode_thread(): Starting\n"); frame = av_frame_alloc(); /* Fetch video packets from the queue and decode */ while(av->thread_abort == 0) { if(ppkt == NULL) { r = _packet_queue_read(&av->video_queue, &pkt); if(r == -2) { /* Thread is aborting */ break; } ppkt = (r >= 0 ? &pkt : NULL); } r = avcodec_send_packet(av->video_codec_ctx, ppkt); if(ppkt != NULL && r != AVERROR(EAGAIN)) { av_packet_unref(ppkt); ppkt = NULL; } if(r < 0 && r != AVERROR(EAGAIN)) { /* avcodec_send_packet() has failed, abort thread */ break; } r = avcodec_receive_frame(av->video_codec_ctx, frame); if(r == 0) { /* We have received a frame! */ av_frame_ref(_frame_dbuffer_back_buffer(&av->in_video_buffer), frame); _frame_dbuffer_ready(&av->in_video_buffer, 0); } else if(r != AVERROR(EAGAIN)) { /* avcodec_receive_frame returned an EOF or error, abort thread */ break; } } _frame_dbuffer_abort(&av->in_video_buffer); av_frame_free(&frame); //fprintf(stderr, "_video_decode_thread(): Ending\n"); return(NULL); } static void *_video_scaler_thread(void *arg) { av_ffmpeg_t *av = (av_ffmpeg_t *) arg; AVFrame *frame, *oframe; AVRational ratio; int64_t pts; //fprintf(stderr, "_video_scaler_thread(): Starting\n"); /* Fetch video frames and pass them through the scaler */ while((frame = _frame_dbuffer_flip(&av->in_video_buffer)) != NULL) { pts = frame->best_effort_timestamp; if(pts != AV_NOPTS_VALUE) { pts = av_rescale_q(pts, av->video_stream->time_base, av->video_time_base); pts -= av->video_start_time; if(pts < 0) { /* This frame is in the past. Skip it */ av_frame_unref(frame); continue; } while(pts > 0) { /* This frame is in the future. Repeat the previous one */ _frame_dbuffer_ready(&av->out_video_buffer, 1); av->video_start_time++; pts--; } } oframe = _frame_dbuffer_back_buffer(&av->out_video_buffer); sws_scale( av->sws_ctx, (uint8_t const * const *) frame->data, frame->linesize, 0, av->video_codec_ctx->height, oframe->data, oframe->linesize ); ratio = frame->sample_aspect_ratio; if(ratio.num == 0 || ratio.den == 0) { /* Default to square pixels if the ratio looks odd */ ratio = (AVRational) { 1, 1 }; } /* Adjust the pixel ratio for the scaled image */ av_reduce( &oframe->sample_aspect_ratio.num, &oframe->sample_aspect_ratio.den, frame->width * ratio.num * oframe->height, frame->height * ratio.den * oframe->width, INT_MAX ); av_frame_unref(frame); _frame_dbuffer_ready(&av->out_video_buffer, 0); av->video_start_time++; } _frame_dbuffer_abort(&av->out_video_buffer); //fprintf(stderr, "_video_scaler_thread(): Ending\n"); return(NULL); } static uint32_t *_av_ffmpeg_read_video(void *private, float *ratio) { av_ffmpeg_t *av = private; AVFrame *frame; if(av->video_stream == NULL) { return(NULL); } frame = _frame_dbuffer_flip(&av->out_video_buffer); if(!frame) { /* EOF or abort */ av->video_eof = 1; return(NULL); } if(ratio) { /* Default to 4:3 ratio if it can't be calculated */ *ratio = 4.0 / 3.0; if(frame->sample_aspect_ratio.den > 0 && frame->height > 0) { *ratio = (float) frame->sample_aspect_ratio.num / frame->sample_aspect_ratio.den; *ratio *= (float) frame->width / frame->height; } } return((uint32_t *) frame->data[0]); } static void *_audio_decode_thread(void *arg) { /* TODO: This function is virtually identical to _video_decode_thread(), * they should probably be combined */ av_ffmpeg_t *av = (av_ffmpeg_t *) arg; AVPacket pkt, *ppkt = NULL; AVFrame *frame; int r; //fprintf(stderr, "_audio_decode_thread(): Starting\n"); frame = av_frame_alloc(); /* Fetch audio packets from the queue and decode */ while(av->thread_abort == 0) { if(ppkt == NULL) { r = _packet_queue_read(&av->audio_queue, &pkt); if(r == -2) { /* Thread is aborting */ break; } ppkt = (r >= 0 ? &pkt : NULL); } r = avcodec_send_packet(av->audio_codec_ctx, ppkt); if(ppkt != NULL && r != AVERROR(EAGAIN)) { av_packet_unref(ppkt); ppkt = NULL; } r = avcodec_receive_frame(av->audio_codec_ctx, frame); if(r == 0) { /* We have received a frame! */ av_frame_ref(_frame_dbuffer_back_buffer(&av->in_audio_buffer), frame); _frame_dbuffer_ready(&av->in_audio_buffer, 0); } else if(r != AVERROR(EAGAIN)) { /* avcodec_receive_frame returned an EOF or error, abort thread */ break; } } _frame_dbuffer_abort(&av->in_audio_buffer); av_frame_free(&frame); //fprintf(stderr, "_audio_decode_thread(): Ending\n"); return(NULL); } static void *_audio_scaler_thread(void *arg) { av_ffmpeg_t *av = (av_ffmpeg_t *) arg; AVFrame *frame, *oframe; int64_t pts, next_pts; uint8_t const *data[AV_NUM_DATA_POINTERS]; int r, count, drop; //fprintf(stderr, "_audio_scaler_thread(): Starting\n"); /* Fetch audio frames and pass them through the resampler */ while((frame = _frame_dbuffer_flip(&av->in_audio_buffer)) != NULL) { pts = frame->best_effort_timestamp; drop = 0; if(pts != AV_NOPTS_VALUE) { pts = av_rescale_q(pts, av->audio_stream->time_base, av->audio_time_base); pts -= av->audio_start_time; next_pts = pts + frame->nb_samples; if(next_pts <= 0) { /* This frame is in the past. Skip it */ av_frame_unref(frame); continue; } if(pts < -av->allowed_error) { /* Trim this frame */ drop = -pts; //swr_drop_input(av->swr_ctx, -pts); /* It would be nice if this existed */ } else if(pts > av->allowed_error) { /* This frame is in the future. Send silence to fill the gap */ r = swr_inject_silence(av->swr_ctx, pts); av->audio_start_time += pts; } } count = frame->nb_samples; count -= drop; _audio_offset( data, (const uint8_t **) frame->data, drop, av->audio_codec_ctx->channels, av->audio_codec_ctx->sample_fmt ); do { oframe = _frame_dbuffer_back_buffer(&av->out_audio_buffer); r = swr_convert( av->swr_ctx, oframe->data, av->out_frame_size, count ? data : NULL, count ); if(r == 0) break; oframe->nb_samples = r; _frame_dbuffer_ready(&av->out_audio_buffer, 0); av->audio_start_time += count; count = 0; } while(r > 0); av_frame_unref(frame); } _frame_dbuffer_abort(&av->out_audio_buffer); //fprintf(stderr, "_audio_scaler_thread(): Ending\n"); return(NULL); } static int16_t *_av_ffmpeg_read_audio(void *private, size_t *samples) { av_ffmpeg_t *av = private; AVFrame *frame; if(av->audio_stream == NULL) { return(NULL); } frame = _frame_dbuffer_flip(&av->out_audio_buffer); if(!frame) { /* EOF or abort */ av->audio_eof = 1; return(NULL); } *samples = frame->nb_samples; return((int16_t *) frame->data[0]); } static int _av_ffmpeg_eof(void *private) { av_ffmpeg_t *av = private; if((av->video_stream && !av->video_eof) || (av->audio_stream && !av->audio_eof)) { return(0); } return(1); } static int _av_ffmpeg_close(void *private) { av_ffmpeg_t *av = private; av->thread_abort = 1; _packet_queue_abort(&av->video_queue); _packet_queue_abort(&av->audio_queue); pthread_join(av->input_thread, NULL); if(av->video_stream != NULL) { _frame_dbuffer_abort(&av->in_video_buffer); _frame_dbuffer_abort(&av->out_video_buffer); pthread_join(av->video_decode_thread, NULL); pthread_join(av->video_scaler_thread, NULL); _packet_queue_free(&av->video_queue); _frame_dbuffer_free(&av->in_video_buffer); av_freep(&av->out_video_buffer.frame[0]->data[0]); av_freep(&av->out_video_buffer.frame[1]->data[0]); _frame_dbuffer_free(&av->out_video_buffer); avcodec_free_context(&av->video_codec_ctx); sws_freeContext(av->sws_ctx); } if(av->audio_stream != NULL) { _frame_dbuffer_abort(&av->in_audio_buffer); _frame_dbuffer_abort(&av->out_audio_buffer); pthread_join(av->audio_decode_thread, NULL); pthread_join(av->audio_scaler_thread, NULL); _packet_queue_free(&av->audio_queue); _frame_dbuffer_free(&av->in_audio_buffer); //av_freep(&av->out_audio_buffer.frame[0]->data[0]); //av_freep(&av->out_audio_buffer.frame[1]->data[0]); _frame_dbuffer_free(&av->out_audio_buffer); avcodec_free_context(&av->audio_codec_ctx); swr_free(&av->swr_ctx); } avformat_close_input(&av->format_ctx); free(av); return(HACKTV_OK); } int av_ffmpeg_open(vid_t *s, char *input_url) { av_ffmpeg_t *av; AVCodec *codec; AVRational time_base; int64_t start_time = 0; int r; int i; av = calloc(1, sizeof(av_ffmpeg_t)); if(!av) { return(HACKTV_OUT_OF_MEMORY); } /* Use 'pipe:' for stdin */ if(strcmp(input_url, "-") == 0) { input_url = "pipe:"; } /* Open the video */ if((r = avformat_open_input(&av->format_ctx, input_url, NULL, NULL)) < 0) { fprintf(stderr, "Error opening file '%s'\n", input_url); _print_ffmpeg_error(r); return(HACKTV_ERROR); } /* Read stream info from the file */ if(avformat_find_stream_info(av->format_ctx, NULL) < 0) { fprintf(stderr, "Error reading stream information from file\n"); return(HACKTV_ERROR); } /* Dump some useful information to stderr */ fprintf(stderr, "Opening '%s'...\n", input_url); av_dump_format(av->format_ctx, 0, input_url, 0); /* Find the first video and audio streams */ /* TODO: Allow the user to select streams by number or name */ av->video_stream = NULL; av->audio_stream = NULL; for(i = 0; i < av->format_ctx->nb_streams; i++) { if(av->video_stream == NULL && av->format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { av->video_stream = av->format_ctx->streams[i]; } if(s->audio && av->audio_stream == NULL && av->format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { if(av->format_ctx->streams[i]->codecpar->channels <= 0) continue; av->audio_stream = av->format_ctx->streams[i]; } } /* At minimum we need either a video or audio stream */ if(av->video_stream == NULL && av->audio_stream == NULL) { fprintf(stderr, "No video or audio streams found\n"); return(HACKTV_ERROR); } if(av->video_stream != NULL) { fprintf(stderr, "Using video stream %d.\n", av->video_stream->index); /* Create the video's time_base using the current TV mode's frames per second. * Numerator and denominator are swapped as ffmpeg uses seconds per frame. */ av->video_time_base.num = s->conf.frame_rate_den; av->video_time_base.den = s->conf.frame_rate_num; /* Use the video's start time as the reference */ time_base = av->video_stream->time_base; start_time = av->video_stream->start_time; /* Get a pointer to the codec context for the video stream */ av->video_codec_ctx = avcodec_alloc_context3(NULL); if(!av->video_codec_ctx) { return(HACKTV_OUT_OF_MEMORY); } if(avcodec_parameters_to_context(av->video_codec_ctx, av->video_stream->codecpar) < 0) { return(HACKTV_ERROR); } av->video_codec_ctx->thread_count = 0; /* Let ffmpeg decide number of threads */ /* Find the decoder for the video stream */ codec = avcodec_find_decoder(av->video_codec_ctx->codec_id); if(codec == NULL) { fprintf(stderr, "Unsupported video codec\n"); return(HACKTV_ERROR); } /* Open video codec */ if(avcodec_open2(av->video_codec_ctx, codec, NULL) < 0) { fprintf(stderr, "Error opening video codec\n"); return(HACKTV_ERROR); } /* Initialise SWS context for software scaling */ av->sws_ctx = sws_getContext( av->video_codec_ctx->width, av->video_codec_ctx->height, av->video_codec_ctx->pix_fmt, s->active_width, s->conf.active_lines, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL ); if(!av->sws_ctx) { return(HACKTV_OUT_OF_MEMORY); } av->video_eof = 0; } else { fprintf(stderr, "No video streams found.\n"); } if(av->audio_stream != NULL) { fprintf(stderr, "Using audio stream %d.\n", av->audio_stream->index); /* Get a pointer to the codec context for the video stream */ av->audio_codec_ctx = avcodec_alloc_context3(NULL); if(!av->audio_codec_ctx) { return(HACKTV_ERROR); } if(avcodec_parameters_to_context(av->audio_codec_ctx, av->audio_stream->codecpar) < 0) { return(HACKTV_ERROR); } av->audio_codec_ctx->thread_count = 0; /* Let ffmpeg decide number of threads */ /* Find the decoder for the audio stream */ codec = avcodec_find_decoder(av->audio_codec_ctx->codec_id); if(codec == NULL) { fprintf(stderr, "Unsupported audio codec\n"); return(HACKTV_ERROR); } /* Open audio codec */ if(avcodec_open2(av->audio_codec_ctx, codec, NULL) < 0) { fprintf(stderr, "Error opening audio codec\n"); return(HACKTV_ERROR); } /* Create the audio time_base using the source sample rate */ av->audio_time_base.num = 1; av->audio_time_base.den = av->audio_codec_ctx->sample_rate; /* Use the audio's start time as the reference if no video was detected */ if(av->video_stream == NULL) { time_base = av->audio_stream->time_base; start_time = av->audio_stream->start_time; } /* Prepare the resampler */ av->swr_ctx = swr_alloc(); if(!av->swr_ctx) { return(HACKTV_OUT_OF_MEMORY); } if(!av->audio_codec_ctx->channel_layout) { /* Set the default layout for codecs that don't specify any */ av->audio_codec_ctx->channel_layout = av_get_default_channel_layout(av->audio_codec_ctx->channels); } av_opt_set_int(av->swr_ctx, "in_channel_layout", av->audio_codec_ctx->channel_layout, 0); av_opt_set_int(av->swr_ctx, "in_sample_rate", av->audio_codec_ctx->sample_rate, 0); av_opt_set_sample_fmt(av->swr_ctx, "in_sample_fmt", av->audio_codec_ctx->sample_fmt, 0); av_opt_set_int(av->swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); av_opt_set_int(av->swr_ctx, "out_sample_rate", HACKTV_AUDIO_SAMPLE_RATE, 0); av_opt_set_sample_fmt(av->swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); if(swr_init(av->swr_ctx) < 0) { fprintf(stderr, "Failed to initialise the resampling context\n"); return(HACKTV_ERROR); } av->audio_eof = 0; } else { fprintf(stderr, "No audio streams found.\n"); } if(start_time == AV_NOPTS_VALUE) { start_time = 0; } /* Calculate the start time for each stream */ if(av->video_stream != NULL) { av->video_start_time = av_rescale_q(start_time, time_base, av->video_time_base); } if(av->audio_stream != NULL) { av->audio_start_time = av_rescale_q(start_time, time_base, av->audio_time_base); } /* Register the callback functions */ s->av_private = av; s->av_read_video = _av_ffmpeg_read_video; s->av_read_audio = _av_ffmpeg_read_audio; s->av_eof = _av_ffmpeg_eof; s->av_close = _av_ffmpeg_close; /* Start the threads */ av->thread_abort = 0; _packet_queue_init(&av->video_queue); _packet_queue_init(&av->audio_queue); if(av->video_stream != NULL) { _frame_dbuffer_init(&av->in_video_buffer); _frame_dbuffer_init(&av->out_video_buffer); /* Allocate memory for the output frame buffers */ for(i = 0; i < 2; i++) { av->out_video_buffer.frame[i]->width = s->active_width; av->out_video_buffer.frame[i]->height = s->conf.active_lines; r = av_image_alloc( av->out_video_buffer.frame[i]->data, av->out_video_buffer.frame[i]->linesize, s->active_width, s->conf.active_lines, AV_PIX_FMT_RGB32, 1 ); } r = pthread_create(&av->video_decode_thread, NULL, &_video_decode_thread, (void *) av); if(r != 0) { fprintf(stderr, "Error starting video decoder thread.\n"); return(HACKTV_ERROR); } r = pthread_create(&av->video_scaler_thread, NULL, &_video_scaler_thread, (void *) av); if(r != 0) { fprintf(stderr, "Error starting video scaler thread.\n"); return(HACKTV_ERROR); } } if(av->audio_stream != NULL) { _frame_dbuffer_init(&av->in_audio_buffer); _frame_dbuffer_init(&av->out_audio_buffer); /* Calculate the number of samples needed for output */ av->out_frame_size = av_rescale_rnd( av->audio_codec_ctx->frame_size, /* Can this be trusted? */ HACKTV_AUDIO_SAMPLE_RATE, av->audio_codec_ctx->sample_rate, AV_ROUND_UP ); if(av->out_frame_size <= 0) { av->out_frame_size = HACKTV_AUDIO_SAMPLE_RATE; } /* Calculate the allowed error in input samples, +/- 20ms */ av->allowed_error = av_rescale_q(AV_TIME_BASE * 0.020, AV_TIME_BASE_Q, av->audio_time_base); for(i = 0; i < 2; i++) { av->out_audio_buffer.frame[i]->format = AV_SAMPLE_FMT_S16; av->out_audio_buffer.frame[i]->channel_layout = AV_CH_LAYOUT_STEREO; av->out_audio_buffer.frame[i]->sample_rate = HACKTV_AUDIO_SAMPLE_RATE; av->out_audio_buffer.frame[i]->nb_samples = av->out_frame_size; r = av_frame_get_buffer(av->out_audio_buffer.frame[i], 0); if(r < 0) { fprintf(stderr, "Error allocating output audio buffer %d\n", i); return(HACKTV_OUT_OF_MEMORY); } } r = pthread_create(&av->audio_decode_thread, NULL, &_audio_decode_thread, (void *) av); if(r != 0) { fprintf(stderr, "Error starting audio decoder thread.\n"); return(HACKTV_ERROR); } r = pthread_create(&av->audio_scaler_thread, NULL, &_audio_scaler_thread, (void *) av); if(r != 0) { fprintf(stderr, "Error starting audio resampler thread.\n"); return(HACKTV_ERROR); } } r = pthread_create(&av->input_thread, NULL, &_input_thread, (void *) av); if(r != 0) { fprintf(stderr, "Error starting input thread.\n"); return(HACKTV_ERROR); } return(HACKTV_OK); } void av_ffmpeg_init(void) { #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) av_register_all(); #endif avdevice_register_all(); avformat_network_init(); } void av_ffmpeg_deinit(void) { avformat_network_deinit(); } hacktv-master/ffmpeg.h000066400000000000000000000025531357376541300152670ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _FFMPEG_H #define _FFMPEG_H extern int av_ffmpeg_open(vid_t *s, char *input_url); extern void av_ffmpeg_init(void); extern void av_ffmpeg_deinit(void); #endif hacktv-master/file.c000066400000000000000000000207011357376541300147300ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include "hacktv.h" /* File sink */ typedef struct { FILE *f; void *data; size_t data_size; size_t samples; int complex; int type; } rf_file_t; static int _rf_file_write_uint8_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; uint8_t *u8 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { u8[i] = (iq_data[0] - INT16_MIN) >> 8; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_int8_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; int8_t *i8 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { i8[i] = iq_data[0] >> 8; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_uint16_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; uint16_t *u16 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { u16[i] = (iq_data[0] - INT16_MIN); } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_int16_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; int16_t *i16 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { i16[i] = iq_data[0]; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_int32_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; int32_t *i32 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { i32[i] = (iq_data[0] << 16) + iq_data[0]; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_float_real(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; float *f32 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { f32[i] = (float) iq_data[0] * (1.0 / 32767.0); } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_uint8_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; uint8_t *u8 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { u8[i * 2 + 0] = (iq_data[0] - INT16_MIN) >> 8; u8[i * 2 + 1] = (iq_data[1] - INT16_MIN) >> 8; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_int8_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; int8_t *i8 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { i8[i * 2 + 0] = iq_data[0] >> 8; i8[i * 2 + 1] = iq_data[1] >> 8; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_uint16_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; uint16_t *u16 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { u16[i * 2 + 0] = (iq_data[0] - INT16_MIN); u16[i * 2 + 1] = (iq_data[1] - INT16_MIN); } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_int16_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; fwrite(iq_data, sizeof(int16_t) * 2, samples, rf->f); return(HACKTV_OK); } static int _rf_file_write_int32_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; int32_t *i32 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { i32[i * 2 + 0] = (iq_data[0] << 16) + iq_data[0]; i32[i * 2 + 1] = (iq_data[1] << 16) + iq_data[1]; } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_write_float_complex(void *private, int16_t *iq_data, size_t samples) { rf_file_t *rf = private; float *f32 = rf->data; int i; while(samples) { for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) { f32[i * 2 + 0] = (float) iq_data[0] * (1.0 / 32767.0); f32[i * 2 + 1] = (float) iq_data[1] * (1.0 / 32767.0); } fwrite(rf->data, rf->data_size, i, rf->f); samples -= i; } return(HACKTV_OK); } static int _rf_file_close(void *private) { rf_file_t *rf = private; if(rf->f && rf->f != stdout) fclose(rf->f); if(rf->data) free(rf->data); free(rf); return(HACKTV_OK); } int rf_file_open(hacktv_t *s, char *filename, int type) { rf_file_t *rf = calloc(1, sizeof(rf_file_t)); if(!rf) { perror("calloc"); return(HACKTV_ERROR); } rf->complex = s->vid.conf.output_type == HACKTV_INT16_COMPLEX; rf->type = type; if(filename == NULL) { fprintf(stderr, "No output filename provided.\n"); _rf_file_close(rf); return(HACKTV_ERROR); } else if(strcmp(filename, "-") == 0) { rf->f = stdout; } else { rf->f = fopen(filename, "wb"); if(!rf->f) { perror("fopen"); _rf_file_close(rf); return(HACKTV_ERROR); } } /* Find the size of the output data type */ switch(type) { case HACKTV_UINT8: rf->data_size = sizeof(uint8_t); break; case HACKTV_INT8: rf->data_size = sizeof(int8_t); break; case HACKTV_UINT16: rf->data_size = sizeof(uint16_t); break; case HACKTV_INT16: rf->data_size = sizeof(int16_t); break; case HACKTV_INT32: rf->data_size = sizeof(int32_t); break; case HACKTV_FLOAT: rf->data_size = sizeof(float); break; default: fprintf(stderr, "%s: Unrecognised data type %d\n", __func__, type); _rf_file_close(rf); return(HACKTV_ERROR); } /* Double the size for complex types */ if(rf->complex) rf->data_size *= 2; /* Allocate enough memory for one TV line */ rf->samples = s->vid.width; /* Allocate the memory, unless the output is int16 complex */ if(rf->type != HACKTV_INT16 || !rf->complex) { rf->data = malloc(rf->data_size * rf->samples); if(!rf->data) { perror("malloc"); _rf_file_close(rf); return(HACKTV_ERROR); } } /* Register the callback functions */ s->rf_private = rf; s->rf_close = _rf_file_close; switch(type) { case HACKTV_UINT8: s->rf_write = rf->complex ? _rf_file_write_uint8_complex : _rf_file_write_uint8_real; break; case HACKTV_INT8: s->rf_write = rf->complex ? _rf_file_write_int8_complex : _rf_file_write_int8_real; break; case HACKTV_UINT16: s->rf_write = rf->complex ? _rf_file_write_uint16_complex : _rf_file_write_uint16_real; break; case HACKTV_INT16: s->rf_write = rf->complex ? _rf_file_write_int16_complex : _rf_file_write_int16_real; break; case HACKTV_INT32: s->rf_write = rf->complex ? _rf_file_write_int32_complex : _rf_file_write_int32_real; break; case HACKTV_FLOAT: s->rf_write = rf->complex ? _rf_file_write_float_complex : _rf_file_write_float_real; break; } return(HACKTV_OK); } hacktv-master/file.h000066400000000000000000000024531357376541300147410ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _FILE_H #define _FILE_H extern int rf_file_open(hacktv_t *s, char *filename, int type); #endif hacktv-master/fir.c000066400000000000000000000207061357376541300145760ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include "fir.h" /* Some of the filter design functions contained within here where taken from or are based on those within gnuradio's gr-filter/lib/firdes.cc */ static double i_zero(double x) { double sum, u, halfx, temp; int n; sum = u = n = 1; halfx = x / 2.0; do { temp = halfx / (double) n; n += 1; temp *= temp; u *= temp; sum += u; } while(u >= 1e-21 * sum); return(sum); } static void kaiser(double *taps, size_t ntaps, double beta) { double i_beta = 1.0 / i_zero(beta); double inm1 = 1.0 / ((double) (ntaps - 1)); double temp; int i; taps[0] = i_beta; for(i = 1; i < ntaps - 1; i++) { temp = 2 * i * inm1 - 1; taps[i] = i_zero(beta * sqrt(1.0 - temp * temp)) * i_beta; } taps[ntaps - 1] = i_beta; } void fir_low_pass(double *taps, size_t ntaps, double sample_rate, double cutoff, double width, double gain) { int n; int M = (ntaps - 1) / 2; double fmax; double fwT0 = 2 * M_PI * cutoff / sample_rate; /* Create the window */ kaiser(taps, ntaps, 7.0); /* Generate the filter taps */ for(n = -M; n <= M; n++) { if(n == 0) { taps[n + M] *= fwT0 / M_PI; } else { taps[n + M] *= sin(n * fwT0) / (n * M_PI); } } /* find the factor to normalize the gain, fmax. * For low-pass, gain @ zero freq = 1.0 */ fmax = taps[0 + M]; for(n = 1; n <= M; n++) { fmax += 2 * taps[n + M]; } /* Normalise */ gain /= fmax; for(n = 0; n < ntaps; n++) { taps[n] *= gain; } } void fir_complex_band_pass(double *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain) { double *lptaps; double freq = M_PI * (high_cutoff + low_cutoff) / sample_rate; double phase; int i; lptaps = &taps[ntaps]; fir_low_pass(lptaps, ntaps, sample_rate, (high_cutoff - low_cutoff) / 2, width, gain); if(ntaps & 1) { phase = -freq * (ntaps >> 1); } else { phase = -freq / 2.0 * ((1 + 2 * ntaps) >> 1); } for(i = 0; i < ntaps; i++, phase += freq) { taps[i * 2 + 0] = lptaps[i] * cos(phase); taps[i * 2 + 1] = lptaps[i] * sin(phase); } } /* int16_t */ void fir_int16_low_pass(int16_t *taps, size_t ntaps, double sample_rate, double cutoff, double width, double gain) { double *dtaps; int i; int a; dtaps = calloc(ntaps, sizeof(double)); fir_low_pass(dtaps, ntaps, sample_rate, cutoff, width, gain); for(a = i = 0; i < ntaps; i++) { taps[i] = round(dtaps[i] * 32767.0); a += taps[i]; } free(dtaps); } int fir_int16_init(fir_int16_t *s, const int16_t *taps, unsigned int ntaps, unsigned int interpolation, unsigned int decimation) { s->ntaps = ntaps; s->taps = taps; s->interpolation = interpolation; s->decimation = decimation; s->ds = 0; s->lwin = (ntaps + s->interpolation - 1) / s->interpolation; s->owin = 0; s->win = calloc(s->lwin, sizeof(int16_t)); return(0); } size_t fir_int16_process(fir_int16_t *s, int16_t *output, size_t ostep, const int16_t *input, size_t samples, size_t istep) { int32_t a; int i; int x; int y; int p; int osamples; osamples = 0; for(x = 0; x < samples; x++) { /* Append the next input sample to the round buffer */ s->win[s->owin++] = *input; input += istep; if(s->owin == s->lwin) s->owin = 0; for(i = 0; i < s->interpolation; i++) { /* Calculate the next output sample */ if(s->ds == 0) { a = 0; p = s->owin - 1; if(p == -1) p = s->lwin - 1; for(y = i; y < s->ntaps; y += s->interpolation) { a += s->win[p--] * s->taps[y]; if(p == -1) p = s->lwin - 1; } *output = a >> 15; output += ostep; osamples++; s->ds = s->decimation; } s->ds--; } } return(osamples); } size_t fir_int16_process_simple(fir_int16_t *s, int16_t *signal, size_t samples) { int a; int x; int y; int p; for(x = 0; x < samples; x++) { /* Append the next input sample to the round buffer */ s->win[s->owin++] = *signal; if(s->owin == s->lwin) s->owin = 0; /* Calculate the next output sample */ a = 0; p = s->owin; for(y = 0; y < s->lwin - s->owin; y++) { a += s->win[p++] * s->taps[y]; } for(p = 0; y < s->ntaps; y++) { a += s->win[p++] * s->taps[y]; } *signal = a >> 15; signal += 2; } return(samples); } void fir_int16_free(fir_int16_t *s) { if(s->win) free(s->win); } /* complex int16_t */ void fir_int16_complex_band_pass(int16_t *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain) { double *dtaps; int i; int a; dtaps = calloc(ntaps, sizeof(double) * 2); fir_complex_band_pass(dtaps, ntaps, sample_rate, low_cutoff, high_cutoff, width, gain); for(a = i = 0; i < ntaps * 2; i++) { taps[i] = round(dtaps[i] * 32767.0); a += taps[i]; } free(dtaps); } int fir_int16_complex_init(fir_int16_t *s, const int16_t *taps, unsigned int ntaps, unsigned int interpolation, unsigned int decimation) { s->ntaps = ntaps; s->taps = taps; s->interpolation = interpolation; s->decimation = decimation; s->ds = 0; s->lwin = (ntaps + s->interpolation - 1) / s->interpolation; s->owin = 0; s->win = calloc(s->lwin, sizeof(int16_t) * 2); return(0); } size_t fir_int16_complex_process(fir_int16_t *s, int16_t *output, size_t ostep, const int16_t *input, size_t samples, size_t istep) { int32_t ai, aq; int i; int x; int y; int p; int osamples; osamples = 0; for(x = 0; x < samples; x++) { /* Append the next input sample to the round buffer */ s->win[s->owin * 2 + 0] = input[0]; s->win[s->owin * 2 + 1] = input[1]; if(++s->owin == s->lwin) s->owin = 0; input += istep * 2; for(i = 0; i < s->interpolation; i++) { /* Calculate the next output sample */ if(s->ds == 0) { ai = 0; aq = 0; p = s->owin - 1; if(p == -1) p = s->lwin - 1; for(y = i; y < s->ntaps; y += s->interpolation) { ai += s->win[p * 2 + 0] * s->taps[y * 2 + 0] - s->win[p * 2 + 1] * s->taps[y * 2 + 1]; aq += s->win[p * 2 + 1] * s->taps[y * 2 + 0] + s->win[p * 2 + 0] * s->taps[y * 2 + 1]; if(--p == -1) p = s->lwin - 1; } output[0] = ai >> 16; output[1] = aq >> 16; output += ostep * 2; osamples++; s->ds = s->decimation; } s->ds--; } } return(osamples); } size_t fir_int16_complex_process_simple(fir_int16_t *s, int16_t *signal, size_t samples) { int32_t ai, aq; int x; int y; int p; for(x = 0; x < samples; x++) { /* Append the next input sample to the round buffer */ s->win[s->owin * 2 + 0] = signal[0]; s->win[s->owin * 2 + 1] = signal[1]; if(++s->owin == s->lwin) s->owin = 0; /* Calculate the next output sample */ ai = 0; aq = 0; p = s->owin; for(y = 0; y < s->lwin - s->owin; y++) { ai += s->win[p * 2 + 0] * s->taps[y * 2 + 0] - s->win[p * 2 + 1] * s->taps[y * 2 + 1]; aq += s->win[p * 2 + 1] * s->taps[y * 2 + 0] + s->win[p * 2 + 0] * s->taps[y * 2 + 1]; p++; } for(p = 0; y < s->ntaps; y++) { ai += s->win[p * 2 + 0] * s->taps[y * 2 + 0] - s->win[p * 2 + 1] * s->taps[y * 2 + 1]; aq += s->win[p * 2 + 1] * s->taps[y * 2 + 0] + s->win[p * 2 + 0] * s->taps[y * 2 + 1]; p++; } signal[0] = aq >> 15; signal[1] = ai >> 15; signal += 2; } return(samples); } void fir_int16_complex_free(fir_int16_t *s) { free(s->win); } hacktv-master/fir.h000066400000000000000000000053771357376541300146120ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ typedef struct { unsigned int ntaps; const int16_t *taps; unsigned int interpolation; unsigned int decimation; unsigned int ds; unsigned int lwin; unsigned int owin; int16_t *win; } fir_int16_t; extern void fir_low_pass(double *taps, size_t ntaps, double sample_rate, double cutoff, double width, double gain); extern void fir_complex_band_pass(double *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain); extern void fir_int16_low_pass(int16_t *taps, size_t ntaps, double sample_rate, double cutoff, double width, double gain); extern void fir_int16_complex_band_pass(int16_t *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain); extern int fir_int16_init(fir_int16_t *s, const int16_t *taps, unsigned int ntaps, unsigned int interpolation, unsigned int decimation); extern size_t fir_int16_process(fir_int16_t *s, int16_t *output, size_t ostep, const int16_t *input, size_t samples, size_t istep); extern size_t fir_int16_process_simple(fir_int16_t *s, int16_t *signal, size_t samples); extern void fir_int16_free(fir_int16_t *s); extern int fir_int16_complex_init(fir_int16_t *s, const int16_t *taps, unsigned int ntaps, unsigned int interpolation, unsigned int decimation); extern size_t fir_int16_complex_process(fir_int16_t *s, int16_t *output, size_t ostep, const int16_t *input, size_t samples, size_t istep); extern size_t fir_int16_complex_process_simple(fir_int16_t *s, int16_t *signal, size_t samples); extern void fir_int16_complex_free(fir_int16_t *s); hacktv-master/fl2k.c000066400000000000000000000106331357376541300146520ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include "hacktv.h" #define BUFFERS 4 typedef struct { fl2k_dev_t *d; int abort; uint8_t buffer_r[BUFFERS][FL2K_BUF_LEN]; uint8_t buffer_g[BUFFERS][FL2K_BUF_LEN]; pthread_mutex_t mutex[BUFFERS]; int len; int in; int out; } fl2k_t; static void _callback(fl2k_data_info_t *data_info) { fl2k_t *rf = data_info->ctx; int i; if(data_info->device_error) { rf->abort = 1; return; } /* Try to get a lock on the next output buffer */ i = (rf->out + 1) % BUFFERS; if(pthread_mutex_trylock(&rf->mutex[i]) != 0) { /* No luck, the writer must have it */ fprintf(stderr, "U"); return; } /* Got a lock on the next buffer, clear and release the previous */ pthread_mutex_unlock(&rf->mutex[rf->out]); rf->out = i; data_info->sampletype_signed = 0; data_info->r_buf = (char *) rf->buffer_r[rf->out]; data_info->g_buf = (char *) rf->buffer_g[rf->out]; data_info->b_buf = NULL; } static int _rf_write(void *private, int16_t *iq_data, size_t samples) { fl2k_t *rf = private; int i; if(rf->abort) { return(HACKTV_ERROR); } while(samples > 0) { for(; rf->len < FL2K_BUF_LEN && samples > 0; rf->len++, samples--) { rf->buffer_r[rf->in][rf->len] = 128 + (*(iq_data++) / 256); rf->buffer_g[rf->in][rf->len] = 128 + (*(iq_data++) / 256); } if(rf->len == FL2K_BUF_LEN) { /* This buffer is full. Move on to the next. */ i = (rf->in + 1) % BUFFERS; pthread_mutex_lock(&rf->mutex[i]); pthread_mutex_unlock(&rf->mutex[rf->in]); rf->in = i; rf->len = 0; } } return(HACKTV_OK); } static int _rf_close(void *private) { fl2k_t *rf = private; int r; rf->abort = 1; fl2k_stop_tx(rf->d); fl2k_close(rf->d); for(r = 0; r < BUFFERS; r++) { pthread_mutex_destroy(&rf->mutex[r]); } free(rf); return(HACKTV_OK); } int rf_fl2k_open(hacktv_t *s, const char *device) { fl2k_t *rf; int r; rf = calloc(1, sizeof(fl2k_t)); if(!rf) { return(HACKTV_OUT_OF_MEMORY); } rf->abort = 0; r = device ? atoi(device) : 0; fl2k_open(&rf->d, r); if(rf->d == NULL) { fprintf(stderr, "fl2k_open() failed to open device #%d.\n", r); _rf_close(rf); return(HACKTV_ERROR); } for(r = 0; r < BUFFERS; r++) { pthread_mutex_init(&rf->mutex[r], NULL); } /* Lock the initial buffer for the provider */ rf->in = 0; pthread_mutex_lock(&rf->mutex[rf->in]); /* Lock the last empty buffer for the consumer */ rf->out = BUFFERS - 1; pthread_mutex_lock(&rf->mutex[rf->out]); rf->len = 0; r = fl2k_start_tx(rf->d, _callback, rf, 0); if(r < 0) { fprintf(stderr, "fl2k_start_tx() failed: %d\n", r); _rf_close(rf); return(HACKTV_ERROR); } r = fl2k_set_sample_rate(rf->d, s->vid.sample_rate); if(r < 0) { fprintf(stderr, "fl2k_set_sample_rate() failed: %d\n", r); _rf_close(rf); return(HACKTV_ERROR); } /* Read back the actual frequency */ r = fl2k_get_sample_rate(rf->d); if(r != s->vid.sample_rate) { //fprintf(stderr, "fl2k sample rate changed from %d > %d\n", s->vid.sample_rate, r); //_rf_close(rf); //return(HACKTV_ERROR); } /* Register the callback functions */ s->rf_private = rf; s->rf_write = _rf_write; s->rf_close = _rf_close; return(HACKTV_OK); }; hacktv-master/fl2k.h000066400000000000000000000024451357376541300146610ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ #ifndef _FL2K_H #define _FL2K_H extern int rf_fl2k_open(hacktv_t *s, const char *device); #endif hacktv-master/hackrf.c000066400000000000000000000174701357376541300152600ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include #include #include "hacktv.h" #define BUFFERS 32 typedef struct { /* Buffers are locked while reading/writing */ pthread_mutex_t mutex; /* Pointer to the start of the buffer */ int8_t *data; /* Offset to start of data */ size_t start; /* Length of data ready */ size_t length; } buffer_t; typedef struct { buffer_t *buffers; int length; int count; int in; int out; } buffers_t; typedef struct { /* HackRF device */ hackrf_device *d; /* Buffers */ buffers_t buffers; } hackrf_t; static int _buffer_init(buffers_t *buffers, size_t count, size_t length) { int i; buffers->count = count; buffers->length = length; buffers->buffers = calloc(count, sizeof(buffer_t)); for(i = 0; i < count; i++) { pthread_mutex_init(&buffers->buffers[i].mutex, NULL); buffers->buffers[i].data = malloc(length); buffers->buffers[i].start = 0; buffers->buffers[i].length = 0; } /* Lock the initial buffer for the provider */ buffers->in = 0; pthread_mutex_lock(&buffers->buffers[buffers->in].mutex); /* Lock the last empty buffer for the consumer */ buffers->out = count - 1; pthread_mutex_lock(&buffers->buffers[buffers->out].mutex); return(0); } static int _buffer_free(buffers_t *buffers) { int i; for(i = 0; i < buffers->count; i++) { free(buffers->buffers[i].data); pthread_mutex_destroy(&buffers->buffers[i].mutex); } free(buffers->buffers); memset(buffers, 0, sizeof(buffers_t)); return(0); } static int _buffer_read(buffers_t *buffers, void *dst, size_t length) { buffer_t *buf = &buffers->buffers[buffers->out]; if(buf->length == 0) { buffer_t *next; int i; /* This buffer is empty, try to move the read lock onto the next one */ i = (buffers->out + 1) % buffers->count; next = &buffers->buffers[i]; if(pthread_mutex_trylock(&next->mutex) != 0) { /* No luck, the writer must have it */ fprintf(stderr, "U"); return(0); } /* Got a lock on the next buffer, clear and release the previous */ buf->start = 0; pthread_mutex_unlock(&buf->mutex); buf = next; buffers->out = i; } if(length > buf->length) { length = buf->length; } memcpy(dst, buf->data + buf->start, length); buf->start += length; buf->length -= length; return(length); } static int _buffer_write(buffers_t *buffers, void *src, size_t length) { buffer_t *buf = &buffers->buffers[buffers->in]; int i; if(buf->length == buffers->length) { buffer_t *next; /* This buffer is full, move the write lock onto the next one */ i = (buffers->in + 1) % buffers->count; next = &buffers->buffers[i]; pthread_mutex_lock(&next->mutex); pthread_mutex_unlock(&buf->mutex); buf = next; buffers->in = i; } i = buf->start + buf->length; if(length > buffers->length - i) { length = buffers->length - i; } memcpy(buf->data + i, src, length); buf->length += length; return(length); } static int _tx_callback(hackrf_transfer *transfer) { hackrf_t *rf = transfer->tx_ctx; size_t l = transfer->valid_length; uint8_t *buf = transfer->buffer; int r; while(l) { r = _buffer_read(&rf->buffers, buf, l); if(r == 0) { /* Buffer underrun, fill with zero */ memset(buf, 0, l); l = 0; } else { l -= r; buf += r; } } return(0); } static int _rf_write(void *private, int16_t *iq_data, size_t samples) { hackrf_t *rf = private; int8_t iq8[1024 * 4]; int i, r; samples *= 2; for(i = 0; i < samples; i++) { iq8[i] = iq_data[i] >> 8; } i = 0; while(samples) { r = _buffer_write(&rf->buffers, &iq8[i], samples); samples -= r; i += r; } return(HACKTV_OK); } static int _rf_close(void *private) { hackrf_t *rf = private; int r; r = hackrf_stop_tx(rf->d); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_stop_tx() failed: %s (%d)\n", hackrf_error_name(r), r); return(HACKTV_ERROR); } /* Wait until streaming has stopped */ while(hackrf_is_streaming(rf->d) == HACKRF_TRUE) { usleep(100); } r = hackrf_close(rf->d); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_close() failed: %s (%d)\n", hackrf_error_name(r), r); } hackrf_exit(); _buffer_free(&rf->buffers); free(rf); return(HACKTV_OK); } int rf_hackrf_open(hacktv_t *s, const char *serial, uint64_t frequency_hz, unsigned int txvga_gain, unsigned char amp_enable) { hackrf_t *rf; int r; if(s->vid.conf.output_type != HACKTV_INT16_COMPLEX) { fprintf(stderr, "rf_hackrf_open(): Unsupported mode output type for this device.\n"); return(HACKTV_ERROR); } rf = calloc(1, sizeof(hackrf_t)); if(!rf) { return(HACKTV_OUT_OF_MEMORY); } /* Allocate memory for output buffers, each one large enough to hold a single frame */ _buffer_init(&rf->buffers, BUFFERS, s->vid.width * s->vid.conf.lines * sizeof(int8_t) * 2); /* Prepare the HackRF for output */ r = hackrf_init(); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_init() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_open_by_serial(serial, &rf->d); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_open() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_set_sample_rate_manual(rf->d, s->vid.sample_rate, 1); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_sample_rate_set() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_set_baseband_filter_bandwidth(rf->d, hackrf_compute_baseband_filter_bw(s->vid.sample_rate)); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_baseband_filter_bandwidth_set() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_set_freq(rf->d, frequency_hz); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_freq() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_set_txvga_gain(rf->d, txvga_gain); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_txvga_gain() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_set_amp_enable(rf->d, amp_enable); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_set_amp_enable() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } r = hackrf_start_tx(rf->d, _tx_callback, rf); if(r != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_start_tx() failed: %s (%d)\n", hackrf_error_name(r), r); free(rf); return(HACKTV_ERROR); } /* Register the callback functions */ s->rf_private = rf; s->rf_write = _rf_write; s->rf_close = _rf_close; return(HACKTV_OK); }; hacktv-master/hackrf.h000066400000000000000000000025651357376541300152640ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _HACKRF_H #define _HACKRF_H extern int rf_hackrf_open(hacktv_t *s, const char *serial, uint64_t frequency_hz, unsigned int txvga_gain, unsigned char amp_enable); #endif hacktv-master/hacktv.1000066400000000000000000000354031357376541300152140ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11. .TH HACKTV "1" "October 2019" "hacktv " "User Commands" .SH NAME hacktv \- manual page for hacktv .SH SYNOPSIS .B hacktv [\fI\,options\/\fR] \fI\,input \/\fR[\fI\,input\/\fR...] .SH DESCRIPTION .TP \fB\-o\fR, \fB\-\-output\fR Set the output device or file, Default: hackrf .TP \fB\-m\fR, \fB\-\-mode\fR Set the television mode. Default: i .TP \fB\-s\fR, \fB\-\-samplerate\fR Set the sample rate in Hz. Default: 16MHz .TP \fB\-l\fR, \fB\-\-level\fR Set the output level. Default: 1.0 .TP \fB\-D\fR, \fB\-\-deviation\fR Override the mode's FM deviation. (Hz) .TP \fB\-G\fR, \fB\-\-gamma\fR Override the mode's gamma correction value. .TP \fB\-r\fR, \fB\-\-repeat\fR Repeat the inputs forever. .TP \fB\-v\fR, \fB\-\-verbose\fR Enable verbose output. .TP \fB\-\-teletext\fR Enable teletext output. (625 line modes only) .TP \fB\-\-wss\fR Enable WSS output. (625 line modes only) .TP \fB\-\-videocrypt\fR Enable Videocrypt I scrambling. (PAL only) .TP \fB\-\-videocrypt2\fR Enable Videocrypt II scrambling. (PAL only) .TP \fB\-\-videocrypts\fR Enable Videocrypt S scrambling. (PAL only) .TP \fB\-\-syster\fR Enable Nagravision Syster scambling. (PAL only) .TP \fB\-\-systeraudio\fR Invert the audio spectrum when using Syster. .TP \fB\-\-acp\fR Enable Analogue Copy Protection signal. .TP \fB\-\-filter\fR Enable experimental VSB modulation filter. .TP \fB\-\-noaudio\fR Suppress all audio subcarriers. .TP \fB\-\-nonicam\fR Disable the NICAM subcarrier if present. .PP Input options .TP test:colourbars Generate and transmit a test pattern. .TP ffmpeg: Decode and transmit a video file with ffmpeg. .IP If no valid input prefix is provided, ffmpeg: is assumed. .PP HackRF output options .HP \fB\-o\fR, \fB\-\-output\fR hackrf[:] Open a HackRF for output. .TP \fB\-f\fR, \fB\-\-frequency\fR Set the RF frequency in Hz, 0MHz to 7250MHz. .TP \fB\-a\fR, \fB\-\-amp\fR Enable the TX RF amplifier. .TP \fB\-g\fR, \fB\-\-gain\fR Set the TX VGA (IF) gain, 0\-47dB. Default: 0dB .IP Only modes with a complex output are supported by the HackRF. .PP SoapySDR output options .HP \fB\-o\fR, \fB\-\-output\fR soapysdr[:] Open a SoapySDR device for output. .TP \fB\-f\fR, \fB\-\-frequency\fR Set the RF frequency in Hz. .TP \fB\-g\fR, \fB\-\-gain\fR Set the TX level. Default: 0dB .TP \fB\-A\fR, \fB\-\-antenna\fR Set the antenna. .PP fl2k output options .HP \fB\-o\fR, \fB\-\-output\fR fl2k[:] Open an fl2k device for output. Real signals are output on the Red channel. Complex signals are output on the Red (I) and Green (Q) channels. The 0.7v p-p voltage level of the FL2K is too low to create a correct composite video signal, it will appear too dark without amplification. .PP File output options .TP \fB\-o\fR, \fB\-\-output\fR file: Open a file for output. Use \- for stdout. .TP \fB\-t\fR, \fB\-\-type\fR Set the file data type. .PP Supported file types: .IP uint8 int8 uint16 int16 int32 float .IP The default output is int16. The TV mode will determine if the output is real or complex. .IP If no valid output prefix is provided, file: is assumed. .PP Supported television modes: .TP i = PAL colour, 25 fps, 625 lines, AM (complex), 6.0 MHz FM audio .TP b, g = PAL colour, 25 fps, 625 lines, AM (complex), 5.5 MHz FM audio .TP pal\-fm = PAL colour, 25 fps, 625 lines, FM (complex), 6.5 MHz FM audio .TP pal = PAL colour, 25 fps, 625 lines, unmodulated (real) .TP m = NTSC colour, 30/1.001 fps, 525 lines, AM (complex) .TP ntsc = NTSC colour, 30/1.001 fps, 525 lines, unmodulated (real) .TP l = SECAM colour, 25 fps, 625 lines, AM (complex), 6.5 MHz AM .IP audio .TP secam = SECAM colour, 25 fps, 625 lines, unmodulated (real) .TP d2mac-fm = D2-MAC, 25 fps, 625 lines, FM (complex) .TP d2mac-am = D2-MAC, 25 fps, 625 lines, AM (complex) .TP d2mac = D2-MAC, 25 fps, 625 lines, unmodulated (real) .TP dmac-fm = D-MAC, 25 fps, 625 lines, FM (complex) .TP dmac-am = D-MAC, 25 fps, 625 lines, AM (complex) .TP dmac = D-MAC, 25 fps, 625 lines, unmodulated (real) .TP e = No colour, 25 fps, 819 lines, AM (complex) .TP 819 = No colour, 25 fps, 819 lines, unmodulated (real) .TP a = No colour, 25 fps, 405 lines, AM (complex) .TP 405 = No colour, 25 fps, 405 lines, unmodulated (real) .TP 240\-am = No colour, 25 fps, 240 lines, AM (complex) .TP 240 = No colour, 25 fps, 240 lines, unmodulated (real) .TP 30\-am = No colour, 12.5 fps, 30 lines, AM (complex) .TP 30 = No colour, 12.5 fps, 30 lines, unmodulated (real) .IP apollo\-fsc\-fm = Field sequential colour, 30/1.001 fps, 525 lines, FM (complex) .IP 1.25 MHz FM audio .TP apollo\-fsc = Field sequential colour, 30/1.001 fps, 525 lines, unmodulated .IP (real) .TP apollo\-fm = No colour, 10 fps, 320 lines, FM (complex), 1.25 MHz FM audio .TP apollo = No colour, 10 fps, 320 lines, unmodulated (real) .PP NOTE: The number of samples per line is rounded to the nearest integer, which may result in a slight frame rate error. .PP For modes which include audio you also need to ensure the sample rate is adequate to contain both the video signal and audio subcarriers. .PP 16MHz works well with PAL modes, and 13.5MHz for NTSC modes. .PP Teletext .PP Teletext is a digital information service transmitted within the VBI lines of the video signal. Developed in the UK in the 1970s, it was used throughout much of Europe until the end of analogue TV in the 2010s. .PP hacktv supports TTI files. The path can be either a single file or a directory. All files in the directory will be loaded. .PP Raw packet sources are also supported with the raw: path name. The input is expected to be 42 byte teletext packets. Use \- for stdin. .PP Lines 7\-22 and 320\-335 are used, 16 lines per field. .PP Teletext support in hacktv is only compatible with 625 line PAL modes. NTSC and SECAM variations exist and may be supported in the future. .PP WSS (Widescreen Signaling) .PP WSS provides a method to signal to a TV the intended aspect ratio of the video. The following modes are supported: .TP 4:3 = Video is 4:3. .TP 16:9 = Video is 16:9 (Anamorphic). .IP 14:9\-letterbox = Crop a 4:3 video to 14:9. 16:9\-letterbox = Crop a 4:3 video to 16:9. auto = Automatically switch between 4:3 and 16:9. .PP Currently only supported in 625 line modes. A 525 line variant exists and may be supported in future. .PP Videocrypt I .PP A video scrambling system used by the Sky TV analogue satellite service in the UK in the 1990s. Each line of the image is cut at a point determined by a pseudorandom number generator, then the two parts are swapped. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .IP conditional = A valid Sky card is required to decode. Sample data from MTV. .PP Videocrypt is only compatiable with 625 line PAL modes. This version works best when used with samples rates at multiples of 14MHz. .PP Videocrypt II .PP A variation of Videocrypt I used throughout Europe. The scrambling method is identical to VC1, but has a higher VBI data rate. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .PP Both VC1 and VC2 cannot be used together except if both are in free\-access mode. .PP Videocrypt S (Simulation) .PP A variation of Videocrypt II used on the short lived BBC Select service. This mode uses line\-shuffling rather than line cut\-and\-rotate. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .PP This is a simulation and will not work with real hardware. Audio inversion is not yet supported. .PP Nagravision Syster .PP Another video scrambling system used in the 1990s in Europe. The video lines are vertically shuffled within a field. .PP Syster is only compatible with 625 line PAL modes and does not currently work with most hardware. .PP Some decoders will invert the audio around 12.8 kHz. For these devices you need to use the --systeraudio option. .PP Usage: hacktv [options] input [input...] .TP \fB\-o\fR, \fB\-\-output\fR Set the output device or file, Default: hackrf .TP \fB\-m\fR, \fB\-\-mode\fR Set the television mode. Default: i .TP \fB\-s\fR, \fB\-\-samplerate\fR Set the sample rate in Hz. Default: 16MHz .TP \fB\-l\fR, \fB\-\-level\fR Set the output level. Default: 1.0 .TP \fB\-D\fR, \fB\-\-deviation\fR Override the mode's FM deviation. (Hz) .TP \fB\-G\fR, \fB\-\-gamma\fR Override the mode's gamma correction value. .TP \fB\-r\fR, \fB\-\-repeat\fR Repeat the inputs forever. .TP \fB\-v\fR, \fB\-\-verbose\fR Enable verbose output. .TP \fB\-\-teletext\fR Enable teletext output. (625 line modes only) .TP \fB\-\-wss\fR Enable WSS output. (625 line modes only) .TP \fB\-\-videocrypt\fR Enable Videocrypt I scrambling. (PAL only) .TP \fB\-\-videocrypt2\fR Enable Videocrypt II scrambling. (PAL only) .TP \fB\-\-videocrypts\fR Enable Videocrypt S scrambling. (PAL only) .TP \fB\-\-syster\fR Enable Nagravision Syster scambling. (PAL only) .TP \fB\-\-filter\fR Enable experimental VSB modulation filter. .TP \fB\-\-noaudio\fR Suppress all audio subcarriers. .PP Input options .TP test:colourbars Generate and transmit a test pattern. .TP ffmpeg: Decode and transmit a video file with ffmpeg. .IP If no valid input prefix is provided, ffmpeg: is assumed. .PP HackRF output options .HP \fB\-o\fR, \fB\-\-output\fR hackrf[:] Open a HackRF for output. .TP \fB\-f\fR, \fB\-\-frequency\fR Set the RF frequency in Hz, 0MHz to 7250MHz. .TP \fB\-a\fR, \fB\-\-amp\fR Enable the TX RF amplifier. .TP \fB\-g\fR, \fB\-\-gain\fR Set the TX VGA (IF) gain, 0\-47dB. Default: 0dB .IP Only modes with a complex output are supported by the HackRF. .PP SoapySDR output options .HP \fB\-o\fR, \fB\-\-output\fR soapysdr[:] Open a SoapySDR device for output. .TP \fB\-f\fR, \fB\-\-frequency\fR Set the RF frequency in Hz. .TP \fB\-g\fR, \fB\-\-gain\fR Set the TX level. Default: 0dB .TP \fB\-A\fR, \fB\-\-antenna\fR Set the antenna. .PP File output options .TP \fB\-o\fR, \fB\-\-output\fR file: Open a file for output. Use \- for stdout. .TP \fB\-t\fR, \fB\-\-type\fR Set the file data type. .PP Supported file types: .IP uint8 int8 uint16 int16 int32 float .IP The default output is int16. The TV mode will determine if the output is real or complex. .IP If no valid output prefix is provided, file: is assumed. .PP Supported television modes: .TP i = PAL colour, 25 fps, 625 lines, AM (complex), 6.0 MHz FM audio .TP b, g = PAL colour, 25 fps, 625 lines, AM (complex), 5.5 MHz FM audio .TP pal\-fm = PAL colour, 25 fps, 625 lines, FM (complex), 6.5 MHz FM audio .TP pal = PAL colour, 25 fps, 625 lines, unmodulated (real) .TP m = NTSC colour, 30/1.001 fps, 525 lines, AM (complex) .TP ntsc = NTSC colour, 30/1.001 fps, 525 lines, unmodulated (real) .TP l = SECAM colour, 25 fps, 625 lines, AM (complex), 6.5 MHz AM .IP audio .TP secam = SECAM colour, 25 fps, 625 lines, unmodulated (real) .TP e = No colour, 25 fps, 819 lines, AM (complex) .TP 819 = No colour, 25 fps, 819 lines, unmodulated (real) .TP a = No colour, 25 fps, 405 lines, AM (complex) .TP 405 = No colour, 25 fps, 405 lines, unmodulated (real) .TP 240\-am = No colour, 25 fps, 240 lines, AM (complex) .TP 240 = No colour, 25 fps, 240 lines, unmodulated (real) .TP 30\-am = No colour, 12.5 fps, 30 lines, AM (complex) .TP 30 = No colour, 12.5 fps, 30 lines, unmodulated (real) .IP apollo\-fsc\-fm = Field sequential colour, 30/1.001 fps, 525 lines, FM (complex) .IP 1.25 MHz FM audio .TP apollo\-fsc = Field sequential colour, 30/1.001 fps, 525 lines, unmodulated .IP (real) .TP apollo\-fm = No colour, 10 fps, 320 lines, FM (complex), 1.25 MHz FM audio .TP apollo = No colour, 10 fps, 320 lines, unmodulated (real) .PP NOTE: The number of samples per line is rounded to the nearest integer, which may result in a slight frame rate error. .PP For modes which include audio you also need to ensure the sample rate is adequate to contain both the video signal and audio subcarriers. .PP 16MHz works well with PAL modes, and 13.5MHz for NTSC modes. .PP Teletext .PP Teletext is a digital information service transmitted within the VBI lines of the video signal. Developed in the UK in the 1970s, it was used throughout much of Europe until the end of analogue TV in the 2010s. .PP hacktv supports TTI files. The path can be either a single file or a directory. All files in the directory will be loaded. .PP Raw packet sources are also supported with the raw: path name. The input is expected to be 42 byte teletext packets. Use \- for stdin. .PP Lines 7\-22 and 320\-335 are used, 16 lines per field. .PP Teletext support in hacktv is only compatible with 625 line PAL modes. NTSC and SECAM variations exist and may be supported in the future. .PP WSS (Widescreen Signaling) .PP WSS provides a method to signal to a TV the intended aspect ratio of the video. The following modes are supported: .TP 4:3 = Video is 4:3. .TP 16:9 = Video is 16:9 (Anamorphic). .IP 14:9\-letterbox = Crop a 4:3 video to 14:9. 16:9\-letterbox = Crop a 4:3 video to 16:9. auto = Automatically switch between 4:3 and 16:9. .PP Currently only supported in 625 line modes. A 525 line variant exists and may be supported in future. .PP Videocrypt I .PP A video scrambling system used by the Sky TV analogue satellite service in the UK in the 1990s. Each line of the image is cut at a point determined by a pseudorandom number generator, then the two parts are swapped. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .IP conditional = A valid Sky card is required to decode. Sample data from MTV. .PP Videocrypt is only compatiable with 625 line PAL modes. This version works best when used with samples rates at multiples of 14MHz. .PP Videocrypt II .PP A variation of Videocrypt I used throughout Europe. The scrambling method is identical to VC1, but has a higher VBI data rate. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .PP Both VC1 and VC2 cannot be used together except if both are in free\-access mode. .PP Videocrypt S (Simulation) .PP A variation of Videocrypt II used on the short lived BBC Select service. This mode uses line\-shuffling rather than line cut\-and\-rotate. .PP hacktv supports the following modes: .TP free = Free\-access, no subscription card is required to decode. .PP This is a simulation and will not work with real hardware. Audio inversion is not yet supported. .PP Nagravision Syster .PP Another video scrambling system used in the 1990s in Europe. The video lines are vertically shuffled within a field. .PP Syster is only compatible with 625 line PAL modes and does not currently work with most hardware. .PP Audio inversion is not yet supported. hacktv-master/hacktv.c000066400000000000000000000547011357376541300153000ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include #include "hacktv.h" #include "test.h" #include "ffmpeg.h" #include "file.h" #include "hackrf.h" #ifdef HAVE_SOAPYSDR #include "soapysdr.h" #endif #ifdef HAVE_FL2K #include "fl2k.h" #endif volatile int _abort = 0; static void _sigint_callback_handler(int signum) { fprintf(stderr, "Caught signal %d\n", signum); if(_abort > 0) { exit(-1); } _abort++; } /* RF sink callback handlers */ static int _hacktv_rf_write(hacktv_t *s, int16_t *iq_data, size_t samples) { if(s->rf_write) { return(s->rf_write(s->rf_private, iq_data, samples)); } return(HACKTV_ERROR); } static int _hacktv_rf_close(hacktv_t *s) { if(s->rf_close) { return(s->rf_close(s->rf_private)); } return(HACKTV_OK); } static void print_usage(void) { printf( "\n" "Usage: hacktv [options] input [input...]\n" "\n" " -o, --output Set the output device or file, Default: hackrf\n" " -m, --mode Set the television mode. Default: i\n" " -s, --samplerate Set the sample rate in Hz. Default: 16MHz\n" " -l, --level Set the output level. Default: 1.0\n" " -D, --deviation Override the mode's FM deviation. (Hz)\n" " -G, --gamma Override the mode's gamma correction value.\n" " -r, --repeat Repeat the inputs forever.\n" " -v, --verbose Enable verbose output.\n" " --teletext Enable teletext output. (625 line modes only)\n" " --wss Enable WSS output. (625 line modes only)\n" " --videocrypt Enable Videocrypt I scrambling. (PAL only)\n" " --videocrypt2 Enable Videocrypt II scrambling. (PAL only)\n" " --videocrypts Enable Videocrypt S scrambling. (PAL only)\n" " --syster Enable Nagravision Syster scambling. (PAL only)\n" " --systeraudio Invert the audio spectrum when using Syster.\n" " --acp Enable Analogue Copy Protection signal.\n" " --filter Enable experimental VSB modulation filter.\n" " --noaudio Suppress all audio subcarriers.\n" " --nonicam Disable the NICAM subcarrier if present.\n" "\n" "Input options\n" "\n" " test:colourbars Generate and transmit a test pattern.\n" " ffmpeg: Decode and transmit a video file with ffmpeg.\n" "\n" " If no valid input prefix is provided, ffmpeg: is assumed.\n" "\n" "HackRF output options\n" "\n" " -o, --output hackrf[:] Open a HackRF for output.\n" " -f, --frequency Set the RF frequency in Hz, 0MHz to 7250MHz.\n" " -a, --amp Enable the TX RF amplifier.\n" " -g, --gain Set the TX VGA (IF) gain, 0-47dB. Default: 0dB\n" "\n" " Only modes with a complex output are supported by the HackRF.\n" "\n" #ifdef HAVE_SOAPYSDR "SoapySDR output options\n" "\n" " -o, --output soapysdr[:] Open a SoapySDR device for output.\n" " -f, --frequency Set the RF frequency in Hz.\n" " -g, --gain Set the TX level. Default: 0dB\n" " -A, --antenna Set the antenna.\n" "\n" #endif #ifdef HAVE_FL2K "fl2k output options\n" "\n" " -o, --output fl2k[:] Open an fl2k device for output.\n" "\n" " Real signals are output on the Red channel. Complex signals are output\n" " on the Red (I) and Green (Q) channels.\n" "\n" " The 0.7v p-p voltage level of the FL2K is too low to create a correct\n" " composite video signal, it will appear too dark without amplification.\n" "\n" #endif "File output options\n" "\n" " -o, --output file: Open a file for output. Use - for stdout.\n" " -t, --type Set the file data type.\n" "\n" "Supported file types:\n" "\n" " uint8\n" " int8\n" " uint16\n" " int16\n" " int32\n" " float\n" "\n" " The default output is int16. The TV mode will determine if the output\n" " is real or complex.\n" "\n" " If no valid output prefix is provided, file: is assumed.\n" "\n" "Supported television modes:\n" "\n" " i = PAL colour, 25 fps, 625 lines, AM (complex), 6.0 MHz FM audio\n" " b, g = PAL colour, 25 fps, 625 lines, AM (complex), 5.5 MHz FM audio\n" " pal-fm = PAL colour, 25 fps, 625 lines, FM (complex), 6.5 MHz FM audio\n" " pal = PAL colour, 25 fps, 625 lines, unmodulated (real)\n" " m = NTSC colour, 30/1.001 fps, 525 lines, AM (complex)\n" " ntsc = NTSC colour, 30/1.001 fps, 525 lines, unmodulated (real)\n" " l = SECAM colour, 25 fps, 625 lines, AM (complex), 6.5 MHz AM\n" " audio\n" " secam = SECAM colour, 25 fps, 625 lines, unmodulated (real)\n" " d2mac-fm = D2-MAC, 25 fps, 625 lines, FM (complex)\n" " d2mac-am = D2-MAC, 25 fps, 625 lines, AM (complex)\n" " d2mac = D2-MAC, 25 fps, 625 lines, unmodulated (real)\n" " dmac-fm = D-MAC, 25 fps, 625 lines, FM (complex)\n" " dmac-am = D-MAC, 25 fps, 625 lines, AM (complex)\n" " dmac = D-MAC, 25 fps, 625 lines, unmodulated (real)\n" " e = No colour, 25 fps, 819 lines, AM (complex)\n" " 819 = No colour, 25 fps, 819 lines, unmodulated (real)\n" " a = No colour, 25 fps, 405 lines, AM (complex)\n" " 405 = No colour, 25 fps, 405 lines, unmodulated (real)\n" " 240-am = No colour, 25 fps, 240 lines, AM (complex)\n" " 240 = No colour, 25 fps, 240 lines, unmodulated (real)\n" " 30-am = No colour, 12.5 fps, 30 lines, AM (complex)\n" " 30 = No colour, 12.5 fps, 30 lines, unmodulated (real)\n" " apollo-fsc-fm = Field sequential colour, 30/1.001 fps, 525 lines, FM (complex)\n" " 1.25 MHz FM audio\n" " apollo-fsc = Field sequential colour, 30/1.001 fps, 525 lines, unmodulated\n" " (real)\n" " apollo-fm = No colour, 10 fps, 320 lines, FM (complex), 1.25 MHz FM audio\n" " apollo = No colour, 10 fps, 320 lines, unmodulated (real)\n" "\n" "NOTE: The number of samples per line is rounded to the nearest integer,\n" "which may result in a slight frame rate error.\n" "\n" "For modes which include audio you also need to ensure the sample rate\n" "is adequate to contain both the video signal and audio subcarriers.\n" "\n" "16MHz works well with PAL modes, and 13.5MHz for NTSC modes.\n" "\n" "20.25MHz is ideal for the D/D2-MAC modes, but may not work with all hackrfs.\n" "\n" "Teletext\n" "\n" "Teletext is a digital information service transmitted within the VBI lines of\n" "the video signal. Developed in the UK in the 1970s, it was used throughout\n" "much of Europe until the end of analogue TV in the 2010s.\n" "\n" "hacktv supports TTI files. The path can be either a single file or a\n" "directory. All files in the directory will be loaded.\n" "\n" "Raw packet sources are also supported with the raw: path name.\n" "The input is expected to be 42 byte teletext packets. Use - for stdin.\n" "\n" "Lines 7-22 and 320-335 are used, 16 lines per field.\n" "\n" "Teletext support in hacktv is only compatible with 625 line PAL modes.\n" "NTSC and SECAM variations exist and may be supported in the future.\n" "\n" "WSS (Widescreen Signaling)\n" "\n" "WSS provides a method to signal to a TV the intended aspect ratio of\n" "the video. The following modes are supported:\n" "\n" " 4:3 = Video is 4:3.\n" " 16:9 = Video is 16:9 (Anamorphic).\n" " 14:9-letterbox = Crop a 4:3 video to 14:9.\n" " 16:9-letterbox = Crop a 4:3 video to 16:9.\n" " auto = Automatically switch between 4:3 and 16:9.\n" "\n" "Currently only supported in 625 line modes. A 525 line variant exists and\n" "may be supported in future.\n" "\n" "Videocrypt I\n" "\n" "A video scrambling system used by the Sky TV analogue satellite service in\n" "the UK in the 1990s. Each line of the image is cut at a point determined by\n" "a pseudorandom number generator, then the two parts are swapped.\n" "\n" "hacktv supports the following modes:\n" "\n" " free = Free-access, no subscription card is required to decode.\n" " conditional = A valid Sky card is required to decode. Sample data from MTV.\n" "\n" "Videocrypt is only compatiable with 625 line PAL modes. This version\n" "works best when used with samples rates at multiples of 14MHz.\n" "\n" "Videocrypt II\n" "\n" "A variation of Videocrypt I used throughout Europe. The scrambling method is\n" "identical to VC1, but has a higher VBI data rate.\n" "\n" "hacktv supports the following modes:\n" "\n" " free = Free-access, no subscription card is required to decode.\n" "\n" "Both VC1 and VC2 cannot be used together except if both are in free-access mode.\n" "\n" "Videocrypt S (Simulation)\n" "\n" "A variation of Videocrypt II used on the short lived BBC Select service. This\n" "mode uses line-shuffling rather than line cut-and-rotate.\n" "\n" "hacktv supports the following modes:\n" "\n" " free = Free-access, no subscription card is required to decode.\n" "\n" "This is a simulation and will not work with real hardware.\n" "Audio inversion is not yet supported.\n" "\n" "Nagravision Syster\n" "\n" "Another video scrambling system used in the 1990s in Europe. The video lines\n" "are vertically shuffled within a field.\n" "\n" "Syster is only compatible with 625 line PAL modes and does not currently work\n" "with most hardware.\n" "\n" "Some decoders will invert the audio around 12.8 kHz. For these devices you need\n" "to use the --systeraudio option.\n" "\n" ); } #define _OPT_TELETEXT 1000 #define _OPT_WSS 1001 #define _OPT_VIDEOCRYPT 1002 #define _OPT_VIDEOCRYPT2 1003 #define _OPT_VIDEOCRYPTS 1004 #define _OPT_SYSTER 1005 #define _OPT_SYSTERAUDIO 1006 #define _OPT_ACP 1007 #define _OPT_FILTER 1008 #define _OPT_NOAUDIO 1009 #define _OPT_NONICAM 1010 int main(int argc, char *argv[]) { int c; int option_index; static struct option long_options[] = { { "output", required_argument, 0, '0' }, { "mode", required_argument, 0, 'm' }, { "samplerate", required_argument, 0, 's' }, { "level", required_argument, 0, 'l' }, { "deviation", required_argument, 0, 'D' }, { "gamma", required_argument, 0, 'G' }, { "repeat", no_argument, 0, 'r' }, { "verbose", no_argument, 0, 'v' }, { "teletext", required_argument, 0, _OPT_TELETEXT }, { "wss", required_argument, 0, _OPT_WSS }, { "videocrypt", required_argument, 0, _OPT_VIDEOCRYPT }, { "videocrypt2", required_argument, 0, _OPT_VIDEOCRYPT2 }, { "videocrypts", required_argument, 0, _OPT_VIDEOCRYPTS }, { "syster", no_argument, 0, _OPT_SYSTER }, { "systeraudio", no_argument, 0, _OPT_SYSTERAUDIO }, { "acp", no_argument, 0, _OPT_ACP }, { "filter", no_argument, 0, _OPT_FILTER }, { "noaudio", no_argument, 0, _OPT_NOAUDIO }, { "nonicam", no_argument, 0, _OPT_NONICAM }, { "frequency", required_argument, 0, 'f' }, { "amp", no_argument, 0, 'a' }, { "gain", required_argument, 0, 'x' }, { "antenna", required_argument, 0, 'A' }, { "type", required_argument, 0, 't' }, { 0, 0, 0, 0 } }; static hacktv_t s; const vid_configs_t *vid_confs; vid_config_t vid_conf; char *pre, *sub; int l; int r; /* Initialise the state */ memset(&s, 0, sizeof(hacktv_t)); /* Default configuration */ s.output_type = "hackrf"; s.output = NULL; s.mode = "i"; s.samplerate = 16000000; s.level = 1.0; s.deviation = -1; s.gamma = -1; s.repeat = 0; s.verbose = 0; s.teletext = NULL; s.wss = NULL; s.videocrypt = NULL; s.videocrypt2 = NULL; s.videocrypts = NULL; s.syster = 0; s.systeraudio = 0; s.acp = 0; s.filter = 0; s.noaudio = 0; s.nonicam = 0; s.frequency = 0; s.amp = 0; s.gain = 0; s.antenna = NULL; s.file_type = HACKTV_INT16; opterr = 0; while((c = getopt_long(argc, argv, "o:m:s:D:G:rvf:al:g:A:t:", long_options, &option_index)) != -1) { switch(c) { case 'o': /* -o, --output <[type:]target> */ /* Get a pointer to the output prefix and target */ pre = optarg; sub = strchr(pre, ':'); if(sub != NULL) { /* Split the optarg into two */ *sub = '\0'; sub++; } /* Try to match the prefix with a known type */ if(strcmp(pre, "file") == 0) { s.output_type = "file"; s.output = sub; } else if(strcmp(pre, "hackrf") == 0) { s.output_type = "hackrf"; s.output = sub; } #ifdef HAVE_SOAPYSDR else if(strcmp(pre, "soapysdr") == 0) { s.output_type = "soapysdr"; s.output = sub; } #endif #ifdef HAVE_FL2K else if(strcmp(pre, "fl2k") == 0) { s.output_type = "fl2k"; s.output = sub; } #endif else { /* Unrecognised output type, default to file */ if(sub != NULL) { /* Recolonise */ sub--; *sub = ':'; } s.output_type = "file"; s.output = pre; } break; case 'm': /* -m, --mode */ s.mode = optarg; break; case 's': /* -s, --samplerate */ s.samplerate = atoi(optarg); break; case 'l': /* -l, --level */ s.level = atof(optarg); break; case 'D': /* -D, --deviation */ s.deviation = atof(optarg); break; case 'G': /* -G, --gamma */ s.gamma = atof(optarg); break; case 'r': /* -r, --repeat */ s.repeat = 1; break; case 'v': /* -v, --verbose */ s.verbose = 1; break; case _OPT_TELETEXT: /* --teletext */ free(s.teletext); s.teletext = strdup(optarg); break; case _OPT_WSS: /* --wss */ free(s.wss); s.wss = strdup(optarg); break; case _OPT_VIDEOCRYPT: /* --videocrypt */ free(s.videocrypt); s.videocrypt = strdup(optarg); break; case _OPT_VIDEOCRYPT2: /* --videocrypt2 */ free(s.videocrypt2); s.videocrypt2 = strdup(optarg); break; case _OPT_VIDEOCRYPTS: /* --videocrypts */ free(s.videocrypts); s.videocrypts = strdup(optarg); break; case _OPT_SYSTER: /* --syster */ s.syster = 1; break; case _OPT_SYSTERAUDIO: /* --systeraudio */ s.systeraudio = 1; break; case _OPT_ACP: /* --acp */ s.acp = 1; break; case _OPT_FILTER: /* --filter */ s.filter = 1; break; case _OPT_NOAUDIO: /* --noaudio */ s.noaudio = 1; break; case _OPT_NONICAM: /* --nonicam */ s.nonicam = 1; break; case 'f': /* -f, --frequency */ s.frequency = atol(optarg); break; case 'a': /* -a, --amp */ s.amp = 1; break; case 'g': /* -g, --gain */ s.gain = atoi(optarg); break; case 'A': /* -A, --antenna */ free(s.antenna); s.antenna = strdup(optarg); break; case 't': /* -t, --type */ if(strcmp(optarg, "uint8") == 0) { s.file_type = HACKTV_UINT8; } else if(strcmp(optarg, "int8") == 0) { s.file_type = HACKTV_INT8; } else if(strcmp(optarg, "uint16") == 0) { s.file_type = HACKTV_UINT16; } else if(strcmp(optarg, "int16") == 0) { s.file_type = HACKTV_INT16; } else if(strcmp(optarg, "int32") == 0) { s.file_type = HACKTV_INT32; } else if(strcmp(optarg, "float") == 0) { s.file_type = HACKTV_FLOAT; } else { fprintf(stderr, "Unrecognised file data type.\n"); return(-1); } break; case '?': print_usage(); return(0); } } if(optind >= argc) { fprintf(stderr, "No input specified.\n"); return(-1); } /* Load the mode configuration */ for(vid_confs = vid_configs; vid_confs->id != NULL; vid_confs++) { if(strcmp(s.mode, vid_confs->id) == 0) break; } if(vid_confs->id == NULL) { fprintf(stderr, "Unrecognised TV mode.\n"); return(-1); } /* Catch all the signals */ signal(SIGINT, &_sigint_callback_handler); signal(SIGILL, &_sigint_callback_handler); signal(SIGFPE, &_sigint_callback_handler); signal(SIGSEGV, &_sigint_callback_handler); signal(SIGTERM, &_sigint_callback_handler); signal(SIGABRT, &_sigint_callback_handler); memcpy(&vid_conf, vid_confs->conf, sizeof(vid_config_t)); if(s.deviation > 0) { /* Override the FM deviation value */ vid_conf.fm_deviation = s.deviation; } if(s.gamma > 0) { /* Override the gamma value */ vid_conf.gamma = s.gamma; } if(s.noaudio > 0) { /* Disable all audio sub-carriers */ vid_conf.fm_audio_level = 0; vid_conf.am_audio_level = 0; vid_conf.nicam_level = 0; vid_conf.fm_mono_carrier = 0; vid_conf.fm_left_carrier = 0; vid_conf.fm_right_carrier = 0; vid_conf.nicam_carrier = 0; vid_conf.am_mono_carrier = 0; } if(s.nonicam > 0) { /* Disable the NICAM sub-carrier */ vid_conf.nicam_level = 0; vid_conf.nicam_carrier = 0; } vid_conf.level *= s.level; if(s.teletext) { if(vid_conf.lines != 625) { fprintf(stderr, "Teletext is only available with 625 line modes.\n"); return(-1); } vid_conf.teletext = s.teletext; } if(s.wss) { if(vid_conf.lines != 625) { fprintf(stderr, "WSS is only available with 625 line modes.\n"); return(-1); } vid_conf.wss = s.wss; } if(s.videocrypt) { if(vid_conf.lines != 625 && vid_conf.colour_mode != VID_PAL) { fprintf(stderr, "Videocrypt I is only compatible with 625 line PAL modes.\n"); return(-1); } vid_conf.videocrypt = s.videocrypt; } if(s.videocrypt2) { if(vid_conf.lines != 625 && vid_conf.colour_mode != VID_PAL) { fprintf(stderr, "Videocrypt II is only compatible with 625 line PAL modes.\n"); return(-1); } /* Only allow both VC1 and VC2 if both are in free-access mode */ if(s.videocrypt && !(strcmp(s.videocrypt, "free") == 0 && strcmp(s.videocrypt2, "free") == 0)) { fprintf(stderr, "Videocrypt I and II cannot be used together except in free-access mode.\n"); return(-1); } vid_conf.videocrypt2 = s.videocrypt2; } if(s.videocrypts) { if(vid_conf.lines != 625 && vid_conf.colour_mode != VID_PAL) { fprintf(stderr, "Videocrypt S is only compatible with 625 line PAL modes.\n"); return(-1); } if(s.videocrypt || s.videocrypt2) { fprintf(stderr, "Using multiple scrambling modes is not supported.\n"); return(-1); } vid_conf.videocrypts = s.videocrypts; } if(s.syster) { if(vid_conf.lines != 625 && vid_conf.colour_mode != VID_PAL) { fprintf(stderr, "Nagravision Syster is only compatible with 625 line PAL modes.\n"); return(-1); } if(vid_conf.videocrypt || vid_conf.videocrypt2 || vid_conf.videocrypts) { fprintf(stderr, "Using multiple scrambling modes is not supported.\n"); return(-1); } vid_conf.syster = 1; vid_conf.systeraudio = s.systeraudio; } if(s.acp) { if(vid_conf.lines != 625 && vid_conf.lines != 525) { fprintf(stderr, "Analogue Copy Protection is only compatible with 525 and 625 line modes.\n"); return(-1); } if(vid_conf.videocrypt || vid_conf.videocrypt2 || vid_conf.videocrypts || vid_conf.syster) { fprintf(stderr, "Analogue Copy Protection cannot be used with video scrambling enabled.\n"); return(-1); } vid_conf.acp = 1; } /* Setup video encoder */ r = vid_init(&s.vid, s.samplerate, &vid_conf); if(r != VID_OK) { fprintf(stderr, "Unable to initialise video encoder.\n"); return(-1); } vid_info(&s.vid); if(s.filter) { vid_init_filter(&s.vid); } if(strcmp(s.output_type, "hackrf") == 0) { if(rf_hackrf_open(&s, s.output, s.frequency, s.gain, s.amp) != HACKTV_OK) { vid_free(&s.vid); return(-1); } } #ifdef HAVE_SOAPYSDR else if(strcmp(s.output_type, "soapysdr") == 0) { if(rf_soapysdr_open(&s, s.output, s.frequency, s.gain, s.antenna) != HACKTV_OK) { vid_free(&s.vid); return(-1); } } #endif #ifdef HAVE_FL2K else if(strcmp(s.output_type, "fl2k") == 0) { if(rf_fl2k_open(&s, s.output) != HACKTV_OK) { vid_free(&s.vid); return(-1); } } #endif else if(strcmp(s.output_type, "file") == 0) { if(rf_file_open(&s, s.output, s.file_type) != HACKTV_OK) { vid_free(&s.vid); return(-1); } } av_ffmpeg_init(); do { for(c = optind; c < argc && !_abort; c++) { /* Get a pointer to the output prefix and target */ pre = argv[c]; sub = strchr(pre, ':'); if(sub != NULL) { l = sub - pre; sub++; } else { l = strlen(pre); } if(strncmp(pre, "test", l) == 0) { r = av_test_open(&s.vid); } else if(strncmp(pre, "ffmpeg", l) == 0) { r = av_ffmpeg_open(&s.vid, sub); } else { r = av_ffmpeg_open(&s.vid, pre); } if(r != HACKTV_OK) { vid_free(&s.vid); return(-1); } while(!_abort) { size_t samples; int16_t *data = vid_next_line(&s.vid, &samples); if(data == NULL) break; if(_hacktv_rf_write(&s, data, samples) != HACKTV_OK) break; } vid_av_close(&s.vid); } } while(s.repeat && !_abort); _hacktv_rf_close(&s); vid_free(&s.vid); av_ffmpeg_deinit(); fprintf(stderr, "\n"); return(0); } hacktv-master/hacktv.h000066400000000000000000000053211357376541300152770ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _HACKTV_H #define _HACKTV_H #include #include "video.h" /* Return codes */ #define HACKTV_OK 0 #define HACKTV_ERROR -1 #define HACKTV_OUT_OF_MEMORY -2 /* Integer types */ #define HACKTV_INT16_COMPLEX 0 #define HACKTV_INT16_REAL 1 /* File output types */ #define HACKTV_UINT8 0 #define HACKTV_INT8 1 #define HACKTV_UINT16 2 #define HACKTV_INT16 3 #define HACKTV_INT32 4 #define HACKTV_FLOAT 5 /* 32-bit float */ /* Standard audio sample rate */ #define HACKTV_AUDIO_SAMPLE_RATE 32000 /* AV source function prototypes */ typedef uint32_t *(*hacktv_av_read_video_t)(void *private, float *ratio); typedef int16_t *(*hacktv_av_read_audio_t)(void *private, size_t *samples); typedef int (*hacktv_av_close_t)(void *private); /* RF output function prototypes */ typedef int (*hacktv_rf_write_t)(void *private, int16_t *iq_data, size_t samples); typedef int (*hacktv_rf_close_t)(void *private); /* Program state */ typedef struct { /* Configuration */ char *output_type; char *output; char *mode; int samplerate; float level; float deviation; float gamma; int repeat; int verbose; char *teletext; char *wss; char *videocrypt; char *videocrypt2; char *videocrypts; int syster; int systeraudio; int acp; int filter; int noaudio; int nonicam; uint64_t frequency; int amp; int gain; char *antenna; int file_type; /* Video encoder state */ vid_t vid; /* RF sink interface */ void *rf_private; hacktv_rf_write_t rf_write; hacktv_rf_close_t rf_close; } hacktv_t; #endif hacktv-master/mac.c000066400000000000000000001007611357376541300145560ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* -=== D/D2-MAC encoder ===- */ #include #include #include #include "video.h" #include "nicam728.h" #include "mac.h" /* MAC sync codes */ #define MAC_CLAMP 0xEAF3927FUL #define MAC_LSW 0x0BUL #define MAC_CRI 0x55555555UL #define MAC_FSW 0x65AEF3153F41C246ULL /* Polynomial for PRBS generator */ #define _PRBS_POLY 0x7FFF /* Hamming codes */ static const uint8_t _hamming[0x10] = { 0x15, 0x02, 0x49, 0x5E, 0x64, 0x73, 0x38, 0x2F, 0xD0, 0xC7, 0x8C, 0x9B, 0xA1, 0xB6, 0xFD, 0xEA }; /* Network origin and name */ static const char *_nwo = "hacktv"; static const char *_nwname = "hacktv"; /* Service Reference */ static const char *_sname = "hacktv"; /* Service Name (max 32 characters) */ /* RDF sequence */ typedef struct { int tdmcid; int fln1; int lln1; int fln2; int lln2; int fcp; int lcp; int links; } _rdf_t; static _rdf_t _rdf_d2[] = { /* CID, FL1, LL1, FL2, LL2, FCP, LCP */ { 0x01, 0, 622, 1023, 1023, 9, 205, 0 }, /* MPX 01 data burst (99 bits) */ { 0x10, 22, 309, 334, 621, 235, 583, 0 }, /* CDIFF colour difference signal */ { 0x11, 22, 309, 334, 621, 589, 1285, 0 }, /* LUM luminance signal */ { 0x20, 0, 21, 312, 333, 229, 1292, 0 }, /* FF Fixed Format teletext */ { 0x00, }, /* End of sequence */ }; static _rdf_t _rdf_d[] = { /* CID, FL1, LL1, FL2, LL2, FCP, LCP */ { 0x01, 0, 622, 1023, 1023, 6, 104, 0 }, /* MPX 01 data burst (99 bits) */ { 0x02, 0, 622, 1023, 1023, 105, 203, 0 }, /* MPX 02 data burst (99 bits) */ { 0x10, 22, 309, 334, 621, 235, 583, 0 }, /* CDIFF colour difference signal */ { 0x11, 22, 309, 334, 621, 589, 1285, 0 }, /* LUM luminance signal */ { 0x20, 0, 21, 312, 333, 229, 1292, 0 }, /* FF Fixed Format teletext */ { 0x00, }, /* End of sequence */ }; static double _rrc(double x) { return(x == 0 ? 1 : sin(M_PI * x) / (M_PI * x)); } static int16_t *_duobinary_lut(int mode, int width, double level) { double samples_per_symbol; double offset; int i, x, bits; double err; int ntaps, htaps; int16_t *lut, *p; bits = (mode == MAC_MODE_D2 ? 648 : 1296); samples_per_symbol = (double) width / bits; offset = width / 1296 * (mode == MAC_MODE_D2 ? -3 : -1); ntaps = (int) (samples_per_symbol * 16) | 1; htaps = ntaps / 2; lut = malloc(sizeof(int16_t) * ((ntaps + 1) * bits + 1)); if(!lut) { return(NULL); } p = lut; *(p++) = ntaps; for(i = 0; i < bits; i++) { /* Calculate the error */ *p = lround(offset + samples_per_symbol * i); err = offset + samples_per_symbol * i - *p; *(p++) -= htaps; for(x = 0; x < ntaps; x++) { *(p++) = lround(_rrc((double) (x - htaps - err) / samples_per_symbol) * level); } } return(lut); } static int _duobinary(vid_t *s, int bit) { if(bit) { return(s->mac.polarity); } s->mac.polarity = -s->mac.polarity; return(0); } static void _render_duobinary(vid_t *s, uint8_t *data, int nbits) { const int16_t *taps; int symbol; int ntaps; int x, xo; int l; int i; taps = s->mac.lut; ntaps = *(taps++); for(i = 0; i < nbits; i++, taps += ntaps + 1) { /* Read the next symbol */ symbol = _duobinary(s, (data[i >> 3] >> (i & 7)) & 1); /* 0 bits don't need to be rendered */ if(!symbol) continue; l = 0; xo = *taps; if(xo < 0) { l = -1; xo += s->width; } for(x = 1; x <= ntaps; x++, xo++) { int t; if(xo >= s->width) { xo -= s->width; l++; } t = s->oline[s->odelay + l][xo * 2] + (symbol == 1 ? taps[x] : -taps[x]); /* Don't let the duobinary signal clip */ if(t < INT16_MIN) t = INT16_MIN; else if(t > INT16_MAX) t = INT16_MAX; s->oline[s->odelay + l][xo * 2] = t; } } } /* Pseudo-random binary sequence (PRBS) generator for spectrum shaping */ static int _prbs(uint16_t *x) { int b; b = (*x ^ (*x >> 14)) & 1; *x = (*x >> 1) | (b << 14); return(b); } /* Generate IW for CA PRBS2 for video scrambling */ static uint64_t _prbs2_generate_iw(uint64_t cw, uint8_t fcnt) { uint64_t iw; /* FCNT is repeated 8 times, each time inverted */ iw = ((fcnt ^ 0xFF) << 8) | fcnt; iw |= (iw << 16) | (iw << 32) | (iw << 48); return((iw ^ cw) & MAC_PRBS2_CW_MASK); } /* Reset CA PRBS2 */ static void _prbs2_reset(mac_t *s, uint8_t fcnt) { uint64_t iw = _prbs2_generate_iw(s->cw, fcnt); s->sr1 = iw & MAC_PRBS2_SR1_MASK; s->sr2 = (iw >> 31) & MAC_PRBS2_SR2_MASK; } /* Return first x LSBs in b in reversed order. TODO: Remove this */ static uint64_t _rev(uint64_t b, int x) { uint64_t r = 0; while(x--) { r = (r << 1) | (b & 1); b >>= 1; } return(r); } /* Update CA PRBS2 */ static uint16_t _prbs2_update(mac_t *s) { uint16_t code = 0; int i; for(i = 0; i < 16; i++) { int a; /* Load the multiplexer address */ a = _rev(s->sr2, 29) & 0x1F; if(a == 31) a = 30; /* Shift into result register */ code = (code >> 1) | (((_rev(s->sr1, 31) >> a) & 1) << 15); /* Update shift registers */ s->sr1 = (s->sr1 >> 1) ^ (s->sr1 & 1 ? 0x7BB88888UL : 0); s->sr2 = (s->sr2 >> 1) ^ (s->sr2 & 1 ? 0x17A2C100UL : 0); } return(code); } /* Pack bits into buffer LSB first */ static size_t _bits(uint8_t *data, size_t offset, uint64_t bits, size_t nbits) { uint8_t b; for(; nbits; nbits--, offset++, bits >>= 1) { b = 1 << (offset & 7); if(bits & 1) data[offset >> 3] |= b; else data[offset >> 3] &= ~b; } return(offset); } /* Pack bits into buffer MSB first */ static size_t _rbits(uint8_t *data, size_t offset, uint64_t bits, size_t nbits) { uint64_t m = (uint64_t) 1 << (nbits - 1); uint8_t b; for(; nbits; nbits--, offset++, bits <<= 1) { b = 1 << (offset & 7); if(bits & m) data[offset >> 3] |= b; else data[offset >> 3] &= ~b; } return(offset); } /* Pack bits from a byte array into buffer LSB first */ static size_t _bits_buf(uint8_t *data, size_t offset, const uint8_t *src, size_t nbits) { for(; nbits >= 8; nbits -= 8) { offset = _bits(data, offset, *(src++), 8); } if(nbits) { offset = _bits(data, offset, *src, nbits); } return(offset); } /* Pack bits from a byte array into buffer LSB first, interleaved with PRNG bits */ static size_t _bits_buf_il(uint8_t *data, size_t offset, const uint8_t *src, size_t nbits, uint16_t *poly) { int x; for(x = 0; x < nbits; x++) { _prbs(poly); offset = _bits(data, offset, (src[x >> 3] >> (x & 7)) & 1, 1); offset = _bits(data, offset, _prbs(poly), 1); } return(offset); } static inline uint8_t _parity(unsigned int value) { uint8_t p = 0; while(value) { p ^= value & 1; value >>= 1; } return(p); } /* Reversed version of the CCITT CRC */ static uint16_t _crc16(const uint8_t *data, size_t length) { uint16_t crc = 0x0000; const uint16_t poly = 0x8408; int b; while(length--) { crc ^= *(data++); for(b = 0; b < 8; b++) { crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1); } } return(crc); } /* Calculate and append bits in *data with BCH codes. * * data = pointer to bits, LSB first * n = Length of final code in bits (data + BCH codes) * k = Length of data in bits */ static void _bch_encode(uint8_t *data, int n, int k) { unsigned int code = 0x0000; unsigned int g; int i, b; g = (n == 23 ? 0x0571 : 0x3BB0); for(i = 0; i < k; i++) { b = (data[i >> 3] >> (i & 7)) & 1; b = (b ^ code) & 1; code >>= 1; if(b) code ^= g; } _bits(data, k, code, n - k); } static void _update_udt(uint8_t udt[25], time_t timestamp) { struct tm tm; int i, mjd; /* Get the timezone offset */ localtime_r(×tamp, &tm); i = tm.tm_gmtoff / 1800; if(i < 0) i = -i | (1 << 5); /* Calculate Modified Julian Date */ gmtime_r(×tamp, &tm); mjd = 367.0 * (1900 + tm.tm_year) - (int) (7.0 * (1900 + tm.tm_year + (int) ((1 + tm.tm_mon + 9.0) / 12.0)) / 4.0) + (int) (275.0 * (1 + tm.tm_mon) / 9.0) + tm.tm_mday - 678987.0; /* Set the Unified Date and Time sequence */ memset(udt, 0, 25); udt[ 0] = mjd / 10000 % 10; /* MJD digit weight 10^4 */ udt[ 1] = mjd / 1000 % 10; /* MJD digit weight 10^3 */ udt[ 2] = mjd / 100 % 10; /* MJD digit weight 10^2 */ udt[ 3] = mjd / 10 % 10; /* MJD digit weight 10^1 */ udt[ 4] = mjd / 1 % 10; /* MJD digit weight 10^0 */ udt[ 5] = tm.tm_hour / 10 % 10; /* UTC hours (tens) */ udt[ 6] = tm.tm_hour / 1 % 10; /* UTC hours (units) */ udt[ 7] = tm.tm_min / 10 % 10; /* UTC minutes (tens) */ udt[ 8] = tm.tm_min / 1 % 10; /* UTC minutes (units) */ udt[ 9] = tm.tm_sec / 10 % 10; /* UTC seconds (tens) */ udt[10] = tm.tm_sec / 1 % 10; /* UTC seconds (units) */ udt[15] = (i >> 4) & 15; /* Local Offset */ udt[16] = i & 15; /* Local Offset */ /* Apply the chain code sequence */ /* 0000101011101100011111001 */ for(i = 0; i < 25; i++) { udt[i] |= ((0x13E3750 >> i) & 1) << 4; } } static void _interleave(uint8_t pkt[94]) { uint8_t tmp[94]; int c, d, i; memcpy(tmp, pkt, 94); /* + 1 bit to ensure final byte is shifted correctly */ for(d = i = 0; i < 751 + 1; i++) { c = i >> 3; pkt[d] = (pkt[d] >> 1) | (tmp[c] << 7); tmp[c] >>= 1; if(++d == 94) d = 0; } } static void _encode_packet(uint8_t *pkt, int address, int continuity, const uint8_t *data) { int x; /* Generate packet header (address and continuity, MSB first) */ x = _bits(pkt, 0, address & 0x3FF, 10); x = _bits(pkt, x, continuity & 3, 2); _bch_encode(pkt, 23, 12); /* Write the packet contents, or zero */ for(x = 23; x < 751; x += 8) { _bits(pkt, x, data ? *(data++) : 0x00, 8); } /* Interleave the packet */ _interleave(pkt); } /* Packet reader. Returns a dummy packet if the queue is empty */ static void _read_packet(mac_t *s, uint8_t *pkt, int subframe) { mac_subframe_t *sf = &s->subframes[subframe]; int x; if(sf->queue.len == 0) { /* The packet queue is empty, generate a dummy packet */ _encode_packet(pkt, 0x3FF, sf->dummy_continuity++, NULL); return; } x = sf->queue.p - sf->queue.len; if(x < 0) x += MAC_QUEUE_LEN; memcpy(pkt, &sf->queue.pkts[x * MAC_PACKET_BYTES], MAC_PACKET_BYTES); sf->queue.len--; } static void _create_si_dg0_packet(mac_t *s, uint8_t pkt[MAC_PAYLOAD_BYTES]) { int x; uint16_t b; memset(pkt, 0, MAC_PAYLOAD_BYTES); /* PT Packet Type */ pkt[0] = 0xF8; /* DGH (Data Group Header) */ pkt[1] = _hamming[0]; /* TG data group type */ pkt[2] = _hamming[0]; /* C data group continuity */ pkt[3] = _hamming[15]; /* R data group repetition */ pkt[4] = _hamming[0]; /* S1 MSB number of packets carrying the data group */ pkt[5] = _hamming[1]; /* S2 LSB number of packets carrying the data group */ pkt[6] = _hamming[0]; /* F1 MSB number of data group bytes in the last packet */ pkt[7] = _hamming[0]; /* F2 LSB number of data group bytes in the last packet */ pkt[8] = _hamming[1]; /* N data group suffix indicator */ pkt[9] = 0x10; /* CI Network Command (Medium Priority) */ pkt[10] = 11; /* LI Length (bytes, everything following up until the DGS) */ x = 11; /* Parameter NWO */ pkt[x++] = 0x10; /* PI NWO (Network Origin) */ pkt[x++] = 3 + strlen(_nwo); /* LI Length (bytes, 3 + string length) */ pkt[x++] = 0x00; /* Channel number, BCD */ pkt[x++] = 0x01; /* First and second digit of satellite orbital position, BCD */ pkt[x++] = 0x91; /* Third digit of satellite orbital position, BCD and Polarisation */ strcpy((char *) &pkt[x], _nwo); /* Network Origin string */ x += strlen(_nwo); /* Parameter NWNAME */ pkt[x++] = 0x14; /* PI NWNAME (Network Name) */ pkt[x++] = strlen(_nwname); /* LI Length (bytes, string length) */ strcpy((char *) &pkt[x], _nwname); /* Network Name string */ x += strlen(_nwname); /* Parameter LISTX (TV) */ pkt[x++] = 0x18; /* PI LISTX (List of index values) */ pkt[x++] = 0x04; /* LI Length (4 bytes) */ pkt[x++] = 0x01; /* TV service */ pkt[x++] = 0x01; /* Index value 1 */ b = 3 << 12; /* TV, detailed description = DG3 */ b |= 1 << 10; /* Subframe identification, TDMCID = 01 */ b |= s->audio_channel; /* Packet address of the main TV sound */ pkt[x++] = (b & 0x00FF) >> 0; /* TV config LSB */ pkt[x++] = (b & 0xFF00) >> 8; /* TV config MSB */ /* Update the CI command length */ pkt[10] = x - pkt[10]; /* Generate the DGS CRC */ b = _crc16(&pkt[9], pkt[10] + 2); pkt[x++] = (b & 0x00FF) >> 0; pkt[x++] = (b & 0xFF00) >> 8; /* Update the DGH length */ x -= 1; pkt[6] = _hamming[(x & 0xF0) >> 4]; pkt[7] = _hamming[(x & 0x0F) >> 0]; /* Test if the data is too large for a single packet */ if(x > 45 - 2) { fprintf(stderr, "SI DG0 packet overflow (%d/43 bytes)\n", x); } /* Generate the overall packet CRC (excludes PT and CRC) */ x = MAC_PAYLOAD_BYTES; b = _crc16(&pkt[1], x - 3); pkt[x - 2] = (b & 0x00FF) >> 0; pkt[x - 1] = (b & 0xFF00) >> 8; } static void _create_si_dg3_packet(mac_t *s, uint8_t *pkt) { int x; uint16_t b; memset(pkt, 0, MAC_PAYLOAD_BYTES); /* PT Packet Type */ pkt[0] = 0xF8; /* DGH (Data Group Header) */ pkt[1] = _hamming[3]; /* TG data group type */ pkt[2] = _hamming[0]; /* C data group continuity */ pkt[3] = _hamming[15]; /* R data group repetition */ pkt[4] = _hamming[0]; /* S1 MSB number of packets carrying the data group */ pkt[5] = _hamming[1]; /* S2 LSB number of packets carrying the data group */ pkt[6] = _hamming[0]; /* F1 MSB number of data group bytes in the last packet */ pkt[7] = _hamming[0]; /* F2 LSB number of data group bytes in the last packet */ pkt[8] = _hamming[1]; /* N data group suffix indicator */ pkt[9] = 0x90; /* CI TV Command (Medium Priority) */ pkt[10] = 11; /* LI Length (bytes, everything following up until the DGS) */ x = 11; /* Parameter SREF */ pkt[x++] = 0x40; /* PI Service Reference */ pkt[x++] = 1 + strlen(_sname); /* LI Length */ pkt[x++] = 1; /* Index value 1 */ strcpy((char *) &pkt[x], _sname); x += strlen(_sname); /* Parameter VCONF */ pkt[x++] = 0x90; /* PI Analogue TV picture (VCONF) */ pkt[x++] = 1; /* LI Length (1 byte) */ b = 1 << 5; /* Always 001 */ //b |= 0 << 4; /* Aspect ratio: 0: 4:3, 1: 16:9 -- note: inverse of line 625 flag */ b |= s->ratio << 4; b |= 0 << 3; /* Compression ratio: always 0 for Cy = 3:2, Cu = 3:1 */ /* VSAM Vision scrambling and access mode (3 bits) */ //b |= 0 << 2; /* Access type: 0: Free access, 1: Controlled access */ //b |= 0 << 1; /* Scramble type: 0: Double-cut rotation, 1: single-cut line rotation */ //b |= 1 << 0; /* Scrambled: 0: Scrambled, 1: Unscrambled */ b |= s->vsam << 0; pkt[x++] = b; /* Parameter DCINF A4 */ pkt[x++] = 0xA4; /* PI TV original sound (DCINF A4) */ pkt[x++] = 3; /* LI Length (3 bytes */ pkt[x++] = 0x09; /* Language (see page 188) = English */ b = 0x0400 | s->audio_channel; /* 0 0 0 0 01 audio_channel */ pkt[x++] = (b & 0x00FF) >> 0; pkt[x++] = (b & 0xFF00) >> 8; if(s->teletext) { /* Parameter DCINF F0 */ pkt[x++] = 0xF0; /* PI CCIR system B cyclic teletext */ pkt[x++] = 3; /* LI Length (3 bytes) */ pkt[x++] = 0x09; /* Language = English */ b = 0; /* Not a part of the packet multiplex */ pkt[x++] = (b & 0x00FF) >> 0; pkt[x++] = (b & 0xFF00) >> 8; } /* Update the CI command length */ pkt[10] = x - pkt[10]; /* Generate the DGS CRC */ b = _crc16(&pkt[9], pkt[10] + 2); pkt[x++] = (b & 0x00FF) >> 0; pkt[x++] = (b & 0xFF00) >> 8; /* Update the DGH length */ x -= 1; pkt[6] = _hamming[(x & 0xF0) >> 4]; pkt[7] = _hamming[(x & 0x0F) >> 0]; /* Test if the data is too large for a single packet */ if(x > 45 - 2) { fprintf(stderr, "SI DG3 packet overflow (%d/43 bytes)\n", x); } /* Generate the overall packet CRC (excludes PT and CRC) */ x = MAC_PAYLOAD_BYTES; b = _crc16(&pkt[1], x - 3); pkt[x - 2] = (b & 0x00FF) >> 0; pkt[x - 1] = (b & 0xFF00) >> 8; } static void _create_audio_si_packet(mac_t *s, uint8_t *pkt) { uint16_t b; int x; memset(pkt, 0, MAC_PAYLOAD_BYTES); pkt[0] = 0x00; /* PT == BI1 */ pkt[1] = _hamming[0]; /* S1 Number of packets MSB */ pkt[2] = _hamming[1]; /* S2 Number of packets LSB */ pkt[3] = _hamming[0]; /* F1 Number of bytes in last packet MSB */ pkt[4] = _hamming[12]; /* F2 Number of bytes in last packet LSB */ pkt[5] = _hamming[1]; /* CI */ pkt[6] = _hamming[10]; /* LI Length (10 bytes) */ b = 0 << 15; /* State (0: Signal Present, 1: interrupted) */ b |= 0 << 13; /* CIB (0: music/speech ON, 1: cross-fade sound ON, 2+3 undefined) */ b |= 0 << 12; /* Timing (0: Continuous, 1: intermittent) */ b |= 1 << 11; /* ID of sound coding blocks (0: BC2, 1: BC1) */ b |= 0 << 10; /* News flash (0: no, 1: yes) */ b |= 0 << 9; /* SDFSCR flag (0: store, 1: don't store) */ b |= 0 << 7; /* Level of error protection (0: first level, 1: second level) */ b |= 1 << 6; /* Coding law (0: linear, 1: companded) */ b |= 0 << 5; /* Controlled access (0: no, 1: yes) */ b |= 0 << 4; /* Scrambling (0: no, 1: yes) */ b |= 0 << 3; /* Automatic mixing (0: mixing not intended, 1: mixing intended) */ b |= 4 << 0; /* Audio config (0: 15 kHz mono, 2: 7 kHz mono, 4: 15 kHz stereo) */ b |= _parity(b) << 8; /* Parity bit */ for(x = 0; x < 5; x++) { pkt[7 + x * 2] = (b & 0xFF00) >> 8; pkt[8 + x * 2] = (b & 0x00FF) >> 0; } } static int _calculate_audio_address(int channels, int quality, int protection, int mode, int index) { int addr; /* Audio channel address calculation: * * 001 ? ? ? ? ???_: Index (0-7) * \ \ \ \____: 0: 10-bit-NICAM, 1: Linear * \ \ \_____: 0: First level, 1: Second level protection * \ \______: 0: 7 kHz, 1: 15 kHz * \_______: 0: Mono, 1: Stereo * * 224 = 001 1 1 0 0 000 = Stereo, 15 kHz, First level, Companded * 129 = 001 0 0 0 0 001 = Mono, 7 kHz, First level, Companded * 170 = 001 0 1 0 1 010 = Mono, 15 kHz, First level, Linear */ addr = 1 << 7; addr |= (channels & 1) << 6; addr |= (quality & 1) << 5; addr |= (protection & 1) << 4; addr |= (mode & 1) << 3; addr |= index & 7; return(addr); } int mac_init(vid_t *s) { mac_t *mac = &s->mac; int i, x; s->olines += 2; /* Increase the output buffer to three lines */ s->audio = 1; /* MAC always has audio */ memset(mac, 0, sizeof(mac_t)); mac->vsam = MAC_VSAM_FREE_ACCESS_UNSCRAMBLED; /* <-- check the mac.h file for the options here */ if(s->conf.mac_mode == MAC_MODE_D) { /* BSB receivers are ignoring the SI packets, * and expect audio at packet address 128. */ mac->audio_channel = 128; /* Stereo NICAM 32kHz, level 1 protection */ } else { mac->audio_channel = _calculate_audio_address( MAC_STEREO, MAC_HIGH_QUALITY, MAC_FIRST_LEVEL_PROTECTION, MAC_COMPANDED, 0); } mac->teletext = (s->conf.teletext ? 1 : 0); _update_udt(s->mac.udt, time(NULL)); mac->rdf = 0; /* Generate the per-line PRBS seeds */ mac->prbs[0] = _PRBS_POLY; for(i = 1; i < MAC_LINES; i++) { mac->prbs[i] = mac->prbs[i - 1]; for(x = 0; x < (s->conf.mac_mode == MAC_MODE_D ? 1296 : 648); x++) { _prbs(&mac->prbs[i]); } } /* Init NICAM encoder */ nicam_encode_init(&mac->nicam, NICAM_MODE_STEREO, 0); mac->subframes[0].pkt_bits = MAC_PACKET_BITS; mac->subframes[1].pkt_bits = MAC_PACKET_BITS; mac->polarity = -1; mac->lut = _duobinary_lut(s->conf.mac_mode, s->width, (s->white_level - s->black_level) * 0.4); /* Set the video properties */ s->active_width &= ~1; /* Ensure the active width is an even number */ mac->chrominance_width = s->active_width / 2; mac->chrominance_left = round(s->sample_rate * (232.0 / MAC_CLOCK_RATE)); mac->white_ref_left = round(s->sample_rate * (371.0 / MAC_CLOCK_RATE)); mac->black_ref_left = round(s->sample_rate * (533.0 / MAC_CLOCK_RATE)); mac->black_ref_right = round(s->sample_rate * (695.0 / MAC_CLOCK_RATE)); /* Setup PRBS */ mac->cw = MAC_PRBS2_CW_FA; /* Quick and dirty sample rate conversion array */ for(x = 0; x < MAC_WIDTH; x++) { mac->video_scale[x] = round((double) x * s->width / MAC_WIDTH); } return(0); } void mac_free(vid_t *s) { mac_t *mac = &s->mac; free(mac->lut); } int mac_write_packet(vid_t *s, int subframe, int address, int continuity, const uint8_t *data) { mac_subframe_t *sf = &s->mac.subframes[subframe]; if(sf->queue.len == MAC_QUEUE_LEN) { /* The packet queue is full */ return(-1); } _encode_packet(&sf->queue.pkts[sf->queue.p++ * MAC_PACKET_BYTES], address, continuity, data); if(sf->queue.p == MAC_QUEUE_LEN) { sf->queue.p = 0; } sf->queue.len++; return(0); } int mac_write_audio(vid_t *s, const int16_t *audio) { uint8_t data[MAC_PAYLOAD_BYTES]; if(s->mac.subframes[0].audio_continuity % 80 == 0) { /* Write out an SI Sound Interpretation * packet at least once per two frames */ _create_audio_si_packet(&s->mac, data); mac_write_packet(s, 0, s->mac.audio_channel, s->mac.subframes[0].audio_continuity - 2, data); } /* Encode and write the audio packet */ nicam_encode_mac_packet(&s->mac.nicam, data, audio); mac_write_packet(s, 0, s->mac.audio_channel, s->mac.subframes[0].audio_continuity++, data); return(0); } static uint8_t _hsync_word(int frame, int line) { int hsync = (frame + line) & 1; if(line == 623 || line == 624) { hsync ^= 1; } return(hsync ? MAC_LSW : ~MAC_LSW); } static int _line(vid_t *s, uint8_t *data, int x) { uint16_t poly = s->mac.prbs[s->line - 1]; int i, c; /* A regular line, contains a short data burst of 105/205 bits for D2/D */ for(c = 0; c < (s->conf.mac_mode == MAC_MODE_D2 ? 1 : 2); c++) { mac_subframe_t *sf = &s->mac.subframes[c]; for(i = 0; i < 99; i++) { if(sf->pkt_bits == MAC_PACKET_BITS) { if(s->line == 623) { /* Line 623 contains only the last 4 bits * of packet 82. The remainder is empty */ break; } /* Fetch the next packet */ _read_packet(&s->mac, sf->pkt, c); sf->pkt_bits = 0; } /* Feed in the bits, LSB first */ x = _bits(data, x, (sf->pkt[sf->pkt_bits >> 3] & 1) ^ _prbs(&poly), 1); sf->pkt[sf->pkt_bits >> 3] >>= 1; sf->pkt_bits++; } /* For line 623, fill out remainder of the line with zeros */ for(; i < 99; i++) { x = _bits(data, x, _prbs(&poly), 1); } } if(s->conf.mac_mode == MAC_MODE_D) { /* D-MAC always ends the packet burst with a spare bit */ x = _rbits(data, x, 1, 1); } return(x); } static int _line_624(vid_t *s, uint8_t *data, int x) { if(s->conf.mac_mode == MAC_MODE_D2) { /* D2-MAC line 624 contains 67 spare bits (transmitted MSB first) */ x = _rbits(data, x, 0xAAAAAAAAAAAAAAAAUL, 64); x = _rbits(data, x, 0x5, 3); } else { /* D-MAC line 624 contains 166 spare bits (transmitted MSB first) */ x = _rbits(data, x, 0xAAAAAAAAAAAAAAAAUL, 64); x = _rbits(data, x, 0xAAAAAAAAAAAAAAAAUL, 64); x = _rbits(data, x, 0x2AAAAAAAAAUL, 38); } /* 32-bit clamp marker (transmitted MSB first) */ x = _rbits(data, x, MAC_CLAMP, 32); return(x); } static int _line_625(vid_t *s, uint8_t *data, int x) { uint16_t poly = s->mac.prbs[s->line - 1]; uint16_t b; uint8_t df[16]; uint8_t il[69]; _rdf_t *rdf; int dx, ix; int i; /* The clock run-in and frame sync word (transmitted MSB first) */ x = _rbits(data, x, s->frame & 1 ? MAC_CRI : ~MAC_CRI, 32); x = _rbits(data, x, s->frame & 1 ? MAC_FSW : ~MAC_FSW, 64); /* UDT (transmitted MSB first) */ ix = _rbits(il, 0, s->mac.udt[s->frame % 25], 5); /* SDF */ dx = _bits(df, 0, 0x00B5, 16); /* CHID Satellite channel identification */ dx = _bits(df, dx, 0x00, 8); /* SDFSCR Services configuration reference */ /* MVSCG Multiplex and video scrambling control group */ /* VSAM Vision scrambling and access mode (3 bits) */ //b = 0 << 7; /* Access type: 0: Free access, 1: Controlled access */ //b |= 0 << 6; /* Scramble type: 0: Double-cut rotation, 1: single-cut line rotation */ //b |= 1 << 5; /* Scrambled: 0: Scrambled, 1: Unscrambled */ b = s->mac.vsam << 5; b |= 1 << 4; /* Reserved, always 1 */ //b |= 1 << 3; /* Aspect ratio: 0: 16:9, 1: 4:3 */ b |= (s->ratio <= (14.0 / 9.0) ? 1 : 0) << 3; b |= 1 << 2; /* For satellite broadcast, this bit has no significance */ b |= 1 << 1; /* Sound/data multiplex format: 0: no or incompatible sound, 1: compatible sound */ b |= 1 << 0; /* Video configuration: 0: no or incompatible video, 1: compatible video */ dx = _bits(df, dx, b, 8); dx = _bits(df, dx, (s->frame >> 8) & 0xFFFFF, 20); /* CAFCNT Conditional access frame count */ dx = _bits(df, dx, 1, 1); /* Rp Repacement */ dx = _bits(df, dx, 1, 1); /* Fp Fingerprint */ dx = _bits(df, dx, 3, 2); /* Unallocated, both bits set to 1 */ dx = _bits(df, dx, 0, 1); /* SIFT Service identification channel format */ _bch_encode(df, 71, 57); ix = _bits_buf(il, ix, df, 71); /* RDF */ rdf = (s->conf.mac_mode == MAC_MODE_D2 ? _rdf_d2 : _rdf_d); dx = _bits(df, 0, s->frame & 0xFF, 8); /* FCNT (8 bits) */ dx = _bits(df, dx, 0, 1); /* UDF (1 bit) */ dx = _bits(df, dx, rdf[s->mac.rdf].tdmcid, 8); /* TDMCID (8 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].fln1, 10); /* FLN1 (10 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].lln1, 10); /* LLN1 (10 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].fln2, 10); /* FLN2 (10 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].lln2, 10); /* LLN2 (10 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].fcp, 11); /* FCP (11 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].lcp, 11); /* LCP (11 bits) */ dx = _bits(df, dx, rdf[s->mac.rdf].links ^= 1, 1); /* LINKS (1 bit) */ _bch_encode(df, 94, 80); s->mac.rdf++; if(rdf[s->mac.rdf].tdmcid == 0x00) { s->mac.rdf = 0; } for(dx = 0; dx < 5; dx++) { ix = _bits_buf(il, ix, df, 94); } if(s->conf.mac_mode == MAC_MODE_D2) { x = _bits_buf(data, x, il, ix); } else { /* Skip the poly bits for the CRI and FSW */ for(i = 0; i < 96; i++) { _prbs(&poly); } x = _bits_buf_il(data, x, il, ix, &poly); /* The remainder of line 625 is filled with PRBS output */ while(x < MAC_WIDTH) { x = _bits(data, x, _prbs(&poly), 1); } } return(x); } static int _vbi_teletext(vid_t *s, uint8_t *data) { uint16_t poly = s->mac.prbs[s->line - 1]; uint8_t vbi[45]; int x, i; if(!((s->line >= 1 && s->line <= 22) || (s->line >= 313 && s->line <= 334))) { /* Not a teletext line */ return(-1); } /* Fetch the next teletext packet */ i = tt_next_packet(&s->tt, vbi); if(i != TT_OK) { /* No packet ready */ return(-1); } x = s->conf.mac_mode == MAC_MODE_D2 ? 116 : 230; for(i = 0; i < 360; i++) { x = _bits(data, x, vbi[i >> 3] >> (i & 7), 1); /* In D-MAC the teletext bits are interleaved with PRBS */ if(s->conf.mac_mode == MAC_MODE_D) { x = _bits(data, x, _prbs(&poly), 1); } } /* The remainder of the line in D-MAC is filled with PRBS output */ if(s->conf.mac_mode == MAC_MODE_D) { for(i = 0; i < 172; i++) { x = _bits(data, x, _prbs(&poly), 1); x = _bits(data, x, _prbs(&poly), 1); } } return(0); } static void _rotate(vid_t *s, int x1, int x2, int xc) { int x; xc -= 4; x1 = s->mac.video_scale[x1]; x2 = s->mac.video_scale[x2]; xc = s->mac.video_scale[xc]; for(x = x1 - 4; x <= x2 + 4; x++) { s->output[x * 2 + 1] = s->output[xc++ * 2]; if(xc > x2) xc = x1; } for(x = x1; x <= x2; x++) { s->output[x * 2] = s->output[x * 2 + 1]; } } void mac_next_line(vid_t *s) { uint8_t data[MAC_LINE_BYTES]; int x, y; /* Blank the +1 line */ for(x = 0; x < s->width; x++) { s->output[x * 2] = s->blanking_level; } /* Move to the 0 line */ vid_adj_delay(s, 1); if(s->line == 1) { uint8_t pkt[MAC_PACKET_BYTES]; /* Update the aspect ratio flag */ s->mac.ratio = (s->ratio <= (14.0 / 9.0) ? 0 : 1); /* Push a service information packet at the start of each new * frame. Alternates between DG0 and DG3 each frame. DG0 is * added to both subframes for D-MAC */ switch(s->frame & 1) { case 0: /* Write DG0 to 1st and 2nd subframes */ _create_si_dg0_packet(&s->mac, pkt); mac_write_packet(s, 0, 0x000, 0, pkt); if(s->conf.mac_mode == MAC_MODE_D) { mac_write_packet(s, 1, 0x000, 0, pkt); } break; case 1: /* Write DG3 to 1st subframe */ _create_si_dg3_packet(&s->mac, pkt); mac_write_packet(s, 0, 0x000, 0, pkt); break; } /* Update the UDT date and time every 25 frames */ if(s->frame % 25 == 0) { _update_udt(s->mac.udt, time(NULL)); } } /* Clear the data buffer. This is where the duobinary data is packed */ memset(data, 0, MAC_LINE_BYTES); x = 0; if(s->conf.mac_mode == MAC_MODE_D) { /* D-MAC always begins with a single run-in bit */ x = _rbits(data, x, 1, 1); } /* Apply the line sync word (transmitted MSB first) */ x = _rbits(data, x, _hsync_word(s->frame, s->line), 6); /* Apply the remainder of the line */ if(s->line == 625) { x = _line_625(s, data, x); } else if(s->line == 624) { x = _line_624(s, data, x); } else { x = _line(s, data, x); } /* Generate the teletext data, if enabled */ if(s->conf.teletext) { _vbi_teletext(s, data); } /* Render the duobinary into the line */ _render_duobinary(s, data, (s->conf.mac_mode == MAC_MODE_D2 ? 648 : 1296)); /* Flatten the clamping areas */ /*if(s->line <= 624) { for(x = s->mac.video_scale[207]; x < s->mac.video_scale[207 + 20]; x++) { s->output[x * 2] = s->blanking_level; } }*/ /* Lines 23 and 335 have a black luminance reference area */ if(s->line == 23 || s->line == 335) { for(x = s->active_left; x < s->active_left + s->active_width; x++) { s->output[x * 2] = s->black_level; } } /* Line 624 has grey, black and white reference areas */ if(s->line == 624) { /* White */ for(x = s->mac.white_ref_left; x < s->mac.black_ref_left; x++) { s->output[x * 2] = s->white_level; } /* Black */ for(; x < s->mac.black_ref_right; x++) { s->output[x * 2] = s->black_level; } } /* Render the luminance */ y = -1; if(s->line >= 24 && s->line <= 310) { /* Top field */ y = (s->line - 24) * 2 + 2; } else if(s->line >= 336 && s->line <= 622) { /* Bottom field */ y = (s->line - 336) * 2 + 1; } if(y >= 0) { uint32_t *px = (s->framebuffer != NULL ? &s->framebuffer[y * s->active_width] : NULL); for(x = s->active_left; x < s->active_left + s->active_width; x++) { uint32_t rgb = (px != NULL ? *(px++) & 0xFFFFFF : 0x000000); s->output[x * 2] = s->y_level_lookup[rgb]; } } /* Render the chrominance (one line ahead of the luminance) */ vid_adj_delay(s, 1); if(y >= 0) { uint32_t *px = (s->framebuffer != NULL ? &s->framebuffer[y * s->active_width] : NULL); for(x = s->mac.chrominance_left; x < s->mac.chrominance_left + s->mac.chrominance_width; x++) { uint32_t rgb = (px != NULL ? *(px++) & 0xFFFFFF : 0x000000); s->output[x * 2] += (s->line & 1 ? s->q_level_lookup[rgb] : s->i_level_lookup[rgb]); if(px != NULL) px++; } } /* Scramble the line if enabled */ if((s->mac.vsam & 1) == 0) { uint16_t prbs; /* Reset CA PRBS2 at the beginning of each frame */ if(s->line == 1) { /* TODO: Update the CW for Controlled Access here */ _prbs2_reset(&s->mac, s->frame - 1); } /* Fetch the next CA PRBS2 code */ prbs = _prbs2_update(&s->mac); if(y >= 0) { if((s->mac.vsam & 2) == 0) { /* Double Cut rotation */ _rotate(s, 230 + 2 - 4, 587 - 3 - 4, 282 + ((prbs & 0xFF00) >> 8)); /* Colour-diff */ _rotate(s, 585 + 2 - 2, 1289 - 3 - 2, 682 + ((prbs & 0x00FF) << 1)); /* Luminance */ } else { /* Single Cut rotation */ _rotate(s, 230 + 2 - 3, 1289 - 3 - 2, 282 + ((prbs & 0xFF00) >> 8)); } } } } hacktv-master/mac.h000066400000000000000000000111161357376541300145560ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* -=== D/D2-MAC encoder ===- */ #ifndef _MAC_H #define _MAC_H #define MAC_CLOCK_RATE 20250000 #define MAC_WIDTH 1296 #define MAC_LINES 625 /* The two data modes */ #define MAC_MODE_D 0 #define MAC_MODE_D2 1 /* MAC VSAM modes */ #define MAC_VSAM_FREE_ACCESS_DOUBLE_CUT 0 /* 000: free access, double-cut component rotation scrambling */ #define MAC_VSAM_FREE_ACCESS_UNSCRAMBLED 1 /* 001: free access, unscrambled */ #define MAC_VSAM_FREE_ACCESS_SINGLE_CUT 2 /* 010: free access, single-cut line rotation scrambling */ #define MAC_VSAM_CONTROLLED_ACCESS_DOUBLE_CUT 4 /* 100: controlled access, double-cut component rotation scrambling */ #define MAC_VSAM_CONTROLLED_ACCESS_SINGLE_CUT 6 /* 110: controlled access, single-cut line rotation scrambling */ /* Video aspect ratios */ #define MAC_RATIO_4_3 0 #define MAC_RATIO_16_9 1 /* Number of bits and bytes in a packet, bytes rounded up */ #define MAC_PACKET_BITS 751 #define MAC_PACKET_BYTES 94 /* Number of bits and bytes in a packet payload */ #define MAC_PAYLOAD_BITS 728 #define MAC_PAYLOAD_BYTES 91 /* Number of packets in the transmit queue */ #define MAC_QUEUE_LEN 12 /* Maximum number of bytes per line (for D-MAC, D2 is half) */ #define MAC_LINE_BYTES (MAC_WIDTH / 8) /* Audio defines */ #define MAC_MEDIUM_QUALITY 0 #define MAC_HIGH_QUALITY 1 #define MAC_MONO 0 #define MAC_STEREO 1 #define MAC_COMPANDED 0 #define MAC_LINEAR 1 #define MAC_FIRST_LEVEL_PROTECTION 0 #define MAC_SECOND_LEVEL_PROTECTION 1 /* CA PRBS2 defines */ #define MAC_PRBS2_CW_FA (((uint64_t) 1 << 60) - 1) #define MAC_PRBS2_CW_MASK (((uint64_t) 1 << 60) - 1) #define MAC_PRBS2_SR1_MASK (((uint32_t) 1 << 31) - 1) #define MAC_PRBS2_SR2_MASK (((uint32_t) 1 << 29) - 1) typedef struct { /* The packet queue */ uint8_t pkts[MAC_QUEUE_LEN * MAC_PACKET_BYTES]; /* Copy of the packets */ int len; /* Number of packets in the queue */ int p; /* Index of the next free slot */ } mac_packet_queue_t; typedef struct { mac_packet_queue_t queue; /* Packet queue for this subframe */ uint8_t pkt[MAC_PACKET_BYTES]; /* The current packet */ int pkt_bits; /* Bits sent of the current packet */ /* Channel continuity counters */ int service_continuity; /* Channel 0 -- Service information */ int audio_continuity; /* Channel 224 -- Audio packets */ int dummy_continuity; /* Channel 1023 -- Dummy packets */ } mac_subframe_t; typedef struct { uint8_t vsam; /* VSAM Vision scrambling and access mode */ uint8_t ratio; /* 0: 4:3, 1: 16:9 */ uint16_t audio_channel; /* 1 = Teletext enabled */ int teletext; /* UDT (Unified Date and Time) sequence */ uint8_t udt[25]; /* RDF sequence index */ int rdf; /* The data subframes */ mac_subframe_t subframes[2]; /* PRBS seed, per-line */ uint16_t prbs[MAC_LINES]; /* Duobinary state */ int polarity; int16_t *lut; int width; /* NICAM encoder */ nicam_enc_t nicam; /* Video properties */ int chrominance_width; int chrominance_left; int white_ref_left; int black_ref_left; int black_ref_right; /* PRBS generator */ uint64_t cw; uint64_t sr1; uint64_t sr2; int video_scale[MAC_WIDTH]; } mac_t; extern int mac_init(vid_t *s); extern void mac_free(vid_t *s); extern int mac_write_packet(vid_t *s, int subframe, int address, int continuity, const uint8_t *data); extern int mac_write_audio(vid_t *s, const int16_t *audio); extern void mac_next_line(vid_t *s); #endif hacktv-master/nicam728.c000066400000000000000000000271141357376541300153460ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ /* NICAM-728 stereo encoder * * Based on the BBC RD document "NICAM 728 - DIGITAL * TWO-CHANNEL STEREO FOR TERRESTRIAL TELEVISION"; * http://downloads.bbc.co.uk/rd/pubs/reports/1990-06.pdf * * http://www.etsi.org/deliver/etsi_en/300100_300199/300163/01.02.01_60/en_300163v010201p.pdf * * NICAM was designed for 14-bit PCM samples, but for * simplicity this encoder expects 16-bit samples. */ #include #include #include #include #include "nicam728.h" /* Pre-calculated J.17 pre-emphasis filter taps, 32kHz sample rate */ static const int32_t _j17_taps[_J17_NTAPS] = { -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -3, -3, -3, -3, -5, -5, -6, -7, -9, -10, -13, -14, -18, -21, -27, -32, -42, -51, -69, -86, -120, -159, -233, -332, -524, -814, -1402, -2372, -4502, 25590, -4502, -2372, -1402, -814, -524, -332, -233, -159, -120, -86, -69, -51, -42, -32, -27, -21, -18, -14, -13, -10, -9, -7, -6, -5, -5, -3, -3, -3, -3, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1 }; /* RF symbols */ static const int _step[4] = { 0, 3, 1, 2 }; static const int _syms[4] = { 0, 1, 3, 2 }; /* NICAM scaling factors */ typedef struct { int factor; int shift; int coding_range; int protection_range; } _scale_factor_t; static const _scale_factor_t _scale_factors[8] = { { 0, 2, 5, 7 }, /* 0b000 */ { 1, 2, 5, 7 }, /* 0b001 */ { 2, 2, 5, 6 }, /* 0b010 */ { 4, 2, 5, 5 }, /* 0b100 */ { 3, 3, 4, 4 }, /* 0b011 */ { 5, 4, 3, 3 }, /* 0b101 */ { 6, 5, 2, 2 }, /* 0b110 */ { 7, 6, 1, 1 }, /* 0b111 */ }; static const _scale_factor_t *_scale_factor(int16_t *pcm, int step) { int i, b; int16_t s; /* Calculate the optimal scale factor for this audio block */ b = 1; /* Test each sample if it requires a larger range */ for(i = 0; b < 7 && i < NICAM_AUDIO_LEN; i++) { /* Negative values use the same scales */ s = (*pcm < 0) ? ~*pcm : *pcm; /* Test if the scale factor needs to be increased */ while(b < 7 && s >> (b + 8)) { b++; } pcm += step; } return(&_scale_factors[b]); } static void _prn(uint8_t prn[NICAM_FRAME_BYTES - 1]) { /* Generate the full PRN sequence for a NICAM-728 packet * First 20 bits of the sequence should be: * 0000 0111 1011 1110 0010 .... * 07 BE 2. ... */ int poly = 0x1FF; int x, i; for(x = 0; x < NICAM_FRAME_BYTES - 1; x++) { prn[x] = 0x00; for(i = 0; i < 8; i++) { uint8_t b; b = poly & 1; b ^= (poly >> 4) & 1; poly >>= 1; poly |= b << 8; prn[x] <<= 1; prn[x] |= b; } } } static uint8_t _parity(unsigned int value) { uint8_t p = 0; while(value) { p ^= value & 1; value >>= 1; } return(p); } void _process_audio(nicam_enc_t *s, int16_t dst[NICAM_AUDIO_LEN * 2], const int16_t src[NICAM_AUDIO_LEN * 2]) { const _scale_factor_t *scale[2]; int32_t l, r; int x, xi; /* Apply J.17 pre-emphasis filter */ for(x = 0; x < NICAM_AUDIO_LEN; x++) { s->fir_l[s->fir_p] = src ? src[x * 2 + 0] : 0; s->fir_r[s->fir_p] = src ? src[x * 2 + 1] : 0; if(++s->fir_p == _J17_NTAPS) s->fir_p = 0; for(l = r = xi = 0; xi < _J17_NTAPS; xi++) { l += (int32_t) s->fir_l[s->fir_p] * _j17_taps[xi]; r += (int32_t) s->fir_r[s->fir_p] * _j17_taps[xi]; if(++s->fir_p == _J17_NTAPS) s->fir_p = 0; } dst[x * 2 + 0] = l >> 15; dst[x * 2 + 1] = r >> 15; } /* Calculate the scale factors for each channel */ scale[0] = _scale_factor(dst + 0, 2); scale[1] = _scale_factor(dst + 1, 2); /* Scale and append each sample to the frame */ for(xi = x = 0; x < NICAM_AUDIO_LEN * 2; x++) { /* Shift down the selected range */ dst[x] = (dst[x] >> scale[x & 1]->shift) & 0x3FF; /* Add the parity bit (6 MSBs only) */ dst[x] |= _parity(dst[x] >> 4) << 10; /* Add scale-factor code if necessary */ if(x < 54) { dst[x] ^= ((scale[x & 1]->factor >> (2 - (x / 2 % 3))) & 1) << 10; } } } void nicam_encode_init(nicam_enc_t *s, uint8_t mode, uint8_t reserve) { memset(s, 0, sizeof(nicam_enc_t)); s->mode = mode; s->reserve = reserve; _prn(s->prn); } void nicam_encode_frame(nicam_enc_t *s, uint8_t frame[NICAM_FRAME_BYTES], const int16_t audio[NICAM_AUDIO_LEN * 2]) { int16_t j17_audio[NICAM_AUDIO_LEN * 2]; int x, xi; /* Encode the audio */ _process_audio(s, j17_audio, audio); /* Initialise the NICAM frame header with the FAW (Frame Alignment Word) */ frame[0] = NICAM_FAW; /* Set the application control bits */ frame[1] = (((~s->frame) >> 3) & 1) << 7; /* C0 frame flag-bit. Toggled every 8 frames */ frame[1] |= ((s->mode >> 2) & 1) << 6; /* C1 */ frame[1] |= ((s->mode >> 1) & 1) << 5; /* C2 */ frame[1] |= ((s->mode >> 0) & 1) << 4; /* C3 */ frame[1] |= (s->reserve & 1) << 3; /* C4 reserve sound switching flag */ /* The additional bits AD0-AD10 and audio are all zero */ for(x = 2; x < NICAM_FRAME_BYTES; x++) { frame[x] = 0; } /* Pack the encoded audio into the frame */ for(xi = x = 0; x < NICAM_AUDIO_LEN * 2; x++) { int b; for(b = 0; b < 11; b++, j17_audio[x] >>= 1) { /* Apply the bit to the interleaved location */ if(j17_audio[x] & 1) { frame[3 + (xi / 8)] |= 1 << (7 - (xi % 8)); } /* Calculate next interleaved bit location */ xi += 16; if(xi >= NICAM_FRAME_BITS - 24) { xi -= NICAM_FRAME_BITS - 24 - 1; } } } /* Apply the PRN */ for(x = 0; x < NICAM_FRAME_BYTES - 1; x++) { frame[x + 1] ^= s->prn[x]; } /* Increment the frame counter */ s->frame++; } void nicam_encode_mac_packet(nicam_enc_t *s, uint8_t pkt[91], const int16_t audio[NICAM_AUDIO_LEN * 2]) { /* Creates a 90 byte companded sound coding block, first level protection */ int16_t j17[NICAM_AUDIO_LEN * 2]; int i, x; /* Encode the audio */ _process_audio(s, j17, audio); /* PT Packet Type */ pkt[0] = 0xC7; /* Unallocated */ pkt[1] = 0x00; pkt[2] = 0x00; /* Pack the 11-bit compressed samples into the packet */ for(x = 3, i = 0; i < NICAM_AUDIO_LEN * 2; i += 8, x += 11) { pkt[x + 0] = (j17[i + 0] >> 0); pkt[x + 1] = (j17[i + 1] << 3) | (j17[i + 0] >> 8); pkt[x + 2] = (j17[i + 2] << 6) | (j17[i + 1] >> 5); pkt[x + 3] = (j17[i + 2] >> 2); pkt[x + 4] = (j17[i + 3] << 1) | (j17[i + 2] >> 10); pkt[x + 5] = (j17[i + 4] << 4) | (j17[i + 3] >> 7); pkt[x + 6] = (j17[i + 5] << 7) | (j17[i + 4] >> 4); pkt[x + 7] = (j17[i + 5] >> 1); pkt[x + 8] = (j17[i + 6] << 2) | (j17[i + 5] >> 9); pkt[x + 9] = (j17[i + 7] << 5) | (j17[i + 6] >> 6); pkt[x + 10] = (j17[i + 7] >> 3); } /* Increment the frame counter (not used for MAC) */ s->frame++; } static double _hamming(double x) { if(x < -1 || x > 1) return(0); return(0.54 - 0.46 * cos((M_PI * (1.0 + x)))); } static double _rrc(double x, double b, double t) { double r; /* Based on the Wikipedia page, https://en.wikipedia.org/w/index.php?title=Root-raised-cosine_filter&oldid=787851747 */ if(x == 0) { r = (1.0 / t) * (1.0 + b * (4.0 / M_PI - 1)); } else if(fabs(x) == t / (4.0 * b)) { r = b / (t * sqrt(2.0)) * ((1.0 + 2.0 / M_PI) * sin(M_PI / (4.0 * b)) + (1.0 - 2.0 / M_PI) * cos(M_PI / (4.0 * b))); } else { double t1 = (4.0 * b * (x / t)); double t2 = (sin(M_PI * (x / t) * (1.0 - b)) + 4.0 * b * (x / t) * cos(M_PI * (x / t) * (1.0 + b))); double t3 = (M_PI * (x / t) * (1.0 - t1 * t1)); r = (1.0 / t) * (t2 / t3); } return(r); } int nicam_mod_init(nicam_mod_t *s, uint8_t mode, uint8_t reserve, unsigned int sample_rate, unsigned int frequency, double beta, double level) { double sps; double t; double r; int x, n; memset(s, 0, sizeof(nicam_mod_t)); /* Samples per symbol */ sps = (double) sample_rate / 364000.0; /* Calculate the number of taps needed to cover 5 symbols, rounded up to odd number */ s->ntaps = ((unsigned int) (sps * 5) + 1) | 1; s->taps = malloc(sizeof(int16_t) * s->ntaps); if(!s->taps) { return(-1); } /* Generate the filter taps */ n = s->ntaps / 2; for(x = -n; x <= n; x++) { t = ((double) x) / sps; r = _rrc(t, beta, 1.0) * _hamming((double) x / n); r *= M_SQRT1_2 * INT16_MAX * level; s->taps[x + n] = lround(r); } /* Allocate memory for the baseband buffer */ s->bb_start = calloc(s->ntaps, sizeof(cint16_t)); s->bb_end = s->bb_start + s->ntaps; s->bb = s->bb_start; s->bb_len = 0; if(!s->bb_start) { return(-1); } /* Setup values for the sample rate error correction */ n = gcd(sample_rate, NICAM_SYMBOL_RATE); s->decimation = NICAM_SYMBOL_RATE / n; s->sps = (sample_rate + NICAM_SYMBOL_RATE - 1) / NICAM_SYMBOL_RATE; s->dsl = (s->sps * s->decimation) % (sample_rate / n); s->ds = 0; /* Setup the mixer signal */ n = gcd(sample_rate, frequency); x = sample_rate / n; s->cc_start = sin_cint16(x, frequency / n, 1.0); s->cc_end = s->cc_start + x; s->cc = s->cc_start; if(!s->cc) { return(-1); } /* Initialise the encoder */ nicam_encode_init(&s->enc, mode, reserve); s->frame_bit = NICAM_FRAME_BITS; return(0); } int nicam_mod_free(nicam_mod_t *s) { free(s->cc_start); free(s->bb_start); free(s->taps); return(0); } void nicam_mod_input(nicam_mod_t *s, const int16_t audio[NICAM_AUDIO_LEN * 2]) { memcpy(s->audio, audio, sizeof(int16_t) * NICAM_AUDIO_LEN * 2); } int nicam_mod_output(nicam_mod_t *s, int16_t *iq, size_t samples) { cint16_t *ciq = (cint16_t *) iq; int x, i; int16_t r; for(x = 0; x < samples;) { /* Output and clear the buffer */ for(; x < samples && s->bb_len; x++, s->bb_len--) { cint16_mula(ciq++, s->bb, s->cc); s->bb->i = 0; s->bb->q = 0; if(++s->bb == s->bb_end) { s->bb = s->bb_start; } if(++s->cc == s->cc_end) { s->cc = s->cc_start; } } if(s->bb_len > 0) { break; } if(s->frame_bit == NICAM_FRAME_BITS) { /* Encode the next frame */ nicam_encode_frame(&s->enc, s->frame, s->audio); s->frame_bit = 0; } /* Read out the next 2-bit symbol, USB first */ s->dsym += _step[(s->frame[s->frame_bit >> 3] >> (6 - (s->frame_bit & 0x07))) & 0x03]; s->dsym &= 0x03; s->frame_bit += 2; /* Encode the symbol */ for(i = 0; i < s->ntaps; i++) { r = s->taps[i]; s->bb->i += (_syms[s->dsym] & 1 ? r : -r); s->bb->q += (_syms[s->dsym] & 2 ? r : -r); if(++s->bb == s->bb_end) { s->bb = s->bb_start; } } /* Calculate length of the next block */ s->bb_len = s->sps; s->ds += s->dsl; if(s->ds >= s->decimation) { s->bb_len--; s->ds -= s->decimation; } } return(0); } hacktv-master/nicam728.h000066400000000000000000000066271357376541300153610ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _NICAM_H #define _NICAM_H #include #include "common.h" /* NICAM bit and symbol rates */ #define NICAM_BIT_RATE 728000 #define NICAM_SYMBOL_RATE (NICAM_BIT_RATE / 2) /* Audio sample rate for NICAM */ #define NICAM_AUDIO_RATE 32000 /* Length of a NICAM frame in bits, bytes and symbols */ #define NICAM_FRAME_BITS 728 #define NICAM_FRAME_BYTES (NICAM_FRAME_BITS / 8) #define NICAM_FRAME_SYMS (NICAM_FRAME_BITS / 2) /* Length of a NICAM frame in audio samples */ #define NICAM_AUDIO_LEN (NICAM_AUDIO_RATE / 1000) /* Frame alignment word (0b01001110) */ #define NICAM_FAW 0x4E /* Modes of operation */ #define NICAM_MODE_STEREO 0x00 /* Single stereo audio signal */ #define NICAM_MODE_DUAL_MONO 0x02 /* Two independent mono audio signals */ #define NICAM_MODE_MONO_DATA 0x04 /* One mono audio signal and one data channel */ #define NICAM_MODE_DATA 0x06 /* One data channel */ /* Taps in J.17 pre-emphasis filter */ #define _J17_NTAPS 83 typedef struct { uint8_t mode; uint8_t reserve; unsigned int frame; uint8_t prn[NICAM_FRAME_BYTES - 1]; int fir_p; int16_t fir_l[_J17_NTAPS]; int16_t fir_r[_J17_NTAPS]; } nicam_enc_t; typedef struct { nicam_enc_t enc; int16_t audio[NICAM_AUDIO_LEN * 2]; int ntaps; int16_t *taps; int16_t *hist; int dsym; /* Differential symbol */ cint16_t *bb; cint16_t *bb_start; cint16_t *bb_end; int bb_len; int sps; int ds; int dsl; int decimation; cint16_t *cc; cint16_t *cc_start; cint16_t *cc_end; uint8_t frame[NICAM_FRAME_BYTES]; int frame_bit; } nicam_mod_t; extern void nicam_encode_init(nicam_enc_t *s, uint8_t mode, uint8_t reserve); extern void nicam_encode_frame(nicam_enc_t *s, uint8_t frame[NICAM_FRAME_BYTES], const int16_t audio[NICAM_AUDIO_LEN * 2]); extern void nicam_encode_mac_packet(nicam_enc_t *s, uint8_t pkt[91], const int16_t audio[NICAM_AUDIO_LEN * 2]); extern int nicam_mod_init(nicam_mod_t *s, uint8_t mode, uint8_t reserve, unsigned int sample_rate, unsigned int frequency, double beta, double level); extern void nicam_mod_input(nicam_mod_t *s, const int16_t audio[NICAM_AUDIO_LEN * 2]); extern int nicam_mod_output(nicam_mod_t *s, int16_t *iq, size_t samples); extern int nicam_mod_free(nicam_mod_t *s); #endif hacktv-master/soapysdr.c000066400000000000000000000104101357376541300156510ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include "hacktv.h" typedef struct { /* SoapySDR device and stream */ SoapySDRDevice *d; SoapySDRStream *s; } soapysdr_t; static int _rf_write(void *private, int16_t *iq_data, size_t samples) { soapysdr_t *rf = private; const void *buffs[] = { iq_data }; int flags = 0; SoapySDRDevice_writeStream(rf->d, rf->s, buffs, samples, &flags, 0, 100000); return(HACKTV_OK); } static int _rf_close(void *private) { soapysdr_t *rf = private; SoapySDRDevice_deactivateStream(rf->d, rf->s, 0, 0); SoapySDRDevice_closeStream(rf->d, rf->s); SoapySDRDevice_unmake(rf->d); return(HACKTV_OK); } int rf_soapysdr_open(hacktv_t *s, const char *device, unsigned int frequency_hz, unsigned int gain, const char *antenna) { soapysdr_t *rf; SoapySDRKwargs *results; size_t length; if(s->vid.conf.output_type != HACKTV_INT16_COMPLEX) { fprintf(stderr, "rf_soapysdr_open(): Unsupported mode output type for this device.\n"); return(HACKTV_ERROR); } rf = calloc(1, sizeof(soapysdr_t)); if(!rf) { return(HACKTV_OUT_OF_MEMORY); } /* Display a list of devices */ results = SoapySDRDevice_enumerate(NULL, &length); if(length <= 0) { fprintf(stderr, "No SoapySDR devices found.\n"); free(rf); return(HACKTV_ERROR); } /*for(i = 0; i < length; i++) { fprintf(stderr, "Found device #%ld: ", i); for(j = 0; j < results[i].size; j++) { fprintf(stderr, "%s=%s, ", results[i].keys[j], results[i].vals[j]); } fprintf(stderr, "\n"); }*/ SoapySDRKwargsList_clear(results, length); /* Prepare the device for output */ rf->d = SoapySDRDevice_makeStrArgs(device); if(rf->d == NULL) { fprintf(stderr, "SoapySDRDevice_make() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } if(SoapySDRDevice_setSampleRate(rf->d, SOAPY_SDR_TX, 0, s->vid.sample_rate) != 0) { fprintf(stderr, "SoapySDRDevice_setSampleRate() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } if(SoapySDRDevice_setFrequency(rf->d, SOAPY_SDR_TX, 0, frequency_hz, NULL) != 0) { fprintf(stderr, "SoapySDRDevice_setFrequency() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } if(SoapySDRDevice_setGain(rf->d, SOAPY_SDR_TX, 0, gain) != 0) { fprintf(stderr, "SoapySDRDevice_setGain() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } if(antenna && SoapySDRDevice_setAntenna(rf->d, SOAPY_SDR_TX, 0, antenna) != 0) { fprintf(stderr, "SoapySDRDevice_setAntenna() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } if(SoapySDRDevice_setupStream(rf->d, &rf->s, SOAPY_SDR_TX, SOAPY_SDR_CS16, NULL, 0, NULL) != 0) { fprintf(stderr, "SoapySDRDevice_setupStream() failed: %s\n", SoapySDRDevice_lastError()); free(rf); return(HACKTV_ERROR); } SoapySDRDevice_activateStream(rf->d, rf->s, 0, 0, 0); /* Register the callback functions */ s->rf_private = rf; s->rf_write = _rf_write; s->rf_close = _rf_close; return(HACKTV_OK); }; hacktv-master/soapysdr.h000066400000000000000000000025641357376541300156710ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _SOAPYSDR_H #define _SOAPYSDR_H extern int rf_soapysdr_open(hacktv_t *s, const char *device, unsigned int frequency_hz, unsigned int gain, const char *antenna); #endif hacktv-master/syster.c000066400000000000000000000651041357376541300153500ustar00rootroot00000000000000/* Nagravision Syster encoder for hacktv */ /*=======================================================================*/ /* Copyright 2018 Alex L. James */ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* -=== Nagravision Syster encoder ===- * * These functions implement the image scrambler for Nagravision Syster. * This system uses line shuffling to obscure the image. * * There is some limited support for real hardware decoders. */ #include #include #include #include #include #include "video.h" #include "vbidata.h" /* 0 - 12.8 kHz complex FIR filter taps, sample rate 32 kHz */ #define NTAPS 771 static const int16_t _firi[NTAPS] = { 0,-2,-1,-1,-2,0,-2,-1,-1,-2,0,-2,-1,-1,-2,0,-2,-1,-1,-2,0,-2,-1,-1,-2,0,-2,-1,-1,-2,0,-3,-1,-1,-3,0,-3,-1,-1,-3,0,-3,-1,-1,-3,0,-3,-1,-1,-3,0,-3,-1,-1,-4,0,-4,-1,-1,-4,0,-4,-2,-2,-4,0,-4,-2,-2,-5,0,-5,-2,-2,-5,0,-5,-2,-2,-5,0,-5,-2,-2,-6,0,-6,-2,-2,-6,0,-6,-3,-3,-7,0,-7,-3,-3,-7,0,-8,-3,-3,-8,0,-8,-3,-3,-9,0,-9,-3,-3,-9,0,-10,-4,-4,-10,0,-10,-4,-4,-11,0,-11,-4,-4,-12,0,-12,-5,-5,-12,0,-13,-5,-5,-13,0,-14,-5,-5,-14,0,-15,-6,-6,-15,0,-16,-6,-6,-16,0,-17,-6,-7,-17,0,-18,-7,-7,-19,0,-19,-7,-7,-20,0,-20,-8,-8,-21,0,-22,-8,-8,-22,0,-23,-9,-9,-24,0,-24,-9,-10,-25,0,-26,-10,-10,-27,0,-28,-11,-11,-29,0,-29,-11,-11,-30,0,-31,-12,-12,-32,0,-33,-13,-13,-34,0,-35,-14,-14,-36,0,-37,-14,-14,-39,0,-39,-15,-15,-41,0,-42,-16,-16,-43,0,-44,-17,-17,-46,0,-47,-18,-18,-49,0,-50,-19,-19,-52,0,-53,-21,-21,-55,0,-56,-22,-22,-58,0,-60,-23,-23,-62,0,-63,-25,-25,-66,0,-67,-26,-26,-70,0,-72,-28,-28,-75,0,-77,-30,-30,-80,0,-82,-32,-32,-85,0,-87,-34,-34,-91,0,-94,-36,-37,-98,0,-101,-39,-39,-105,0,-108,-42,-43,-114,0,-117,-46,-46,-123,0,-127,-50,-50,-134,0,-138,-54,-55,-146,0,-151,-59,-60,-161,0,-167,-65,-66,-178,0,-185,-73,-74,-199,0,-208,-82,-83,-224,0,-236,-93,-95,-257,0,-272,-108,-110,-300,0,-321,-128,-132,-359,0,-389,-156,-162,-447,0,-493,-200,-210,-588,0,-671,-277,-299,-857,0,-1046,-452,-513,-1573,0,-2356,-1205,-1795,-9443,-34,9427,1808,1197,2360,0,1570,516,448,1048,0,855,301,276,672,0,587,212,199,494,0,446,163,155,390,0,359,132,127,321,0,300,111,107,273,0,257,96,92,237,0,224,84,81,208,0,198,74,72,186,0,178,67,65,167,0,160,60,59,152,0,146,55,54,138,0,134,50,49,127,0,123,46,45,117,0,113,43,42,108,0,105,40,39,101,0,98,37,36,94,0,91,34,34,88,0,85,32,32,82,0,80,30,30,77,0,75,28,28,72,0,70,27,26,67,0,66,25,24,63,0,62,23,23,60,0,58,22,22,56,0,55,21,20,53,0,52,20,19,50,0,49,18,18,47,0,46,17,17,44,0,43,16,16,42,0,41,15,15,39,0,38,15,14,37,0,36,14,13,35,0,34,13,13,33,0,32,12,12,31,0,30,12,11,29,0,29,11,11,28,0,27,10,10,26,0,25,10,9,24,0,24,9,9,23,0,22,8,8,22,0,21,8,8,20,0,20,7,7,19,0,19,7,7,18,0,17,7,6,17,0,16,6,6,16,0,15,6,6,15,0,14,5,5,14,0,13,5,5,13,0,12,5,5,12,0,11,4,4,11,0,11,4,4,10,0,10,4,4,10,0,9,3,3,9,0,9,3,3,8,0,8,3,3,8,0,7,3,3,7,0,7,3,2,6,0,6,2,2,6,0,6,2,2,5,0,5,2,2,5,0,5,2,2,5,0,5,2,2,4,0,4,2,2,4,0,4,1,1,4,0,4,1,1,3,0,3,1,1,3,0,3,1,1,3,0,3,1,1,3,0,3,1,1,3,0,2,1,1,2,0,2,1,1,2,0,2,1,1,2,0,2,1,1,2,0,2,1,1,2,0,2,1,1,2,0, }; static const int16_t _firq[NTAPS] = { 0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,1,-1,1,0,-1,2,-2,1,0,-1,2,-2,1,0,-1,2,-2,1,0,-1,2,-2,1,0,-1,2,-2,1,0,-1,2,-2,1,0,-2,2,-3,2,0,-2,3,-3,2,0,-2,3,-3,2,0,-2,3,-3,2,0,-2,3,-4,2,0,-2,4,-4,2,0,-2,4,-4,3,0,-3,4,-4,3,0,-3,5,-5,3,0,-3,5,-5,3,0,-3,5,-6,3,0,-4,6,-6,4,0,-4,6,-6,4,0,-4,7,-7,4,0,-4,7,-7,5,0,-5,8,-8,5,0,-5,8,-8,5,0,-5,9,-9,6,0,-6,9,-10,6,0,-6,10,-10,6,0,-7,11,-11,7,0,-7,11,-12,7,0,-8,12,-12,8,0,-8,13,-13,8,0,-9,14,-14,9,0,-9,15,-15,9,0,-10,16,-16,10,0,-10,17,-17,10,0,-11,18,-18,11,0,-11,19,-19,12,0,-12,20,-20,12,0,-13,21,-21,13,0,-14,22,-23,14,0,-15,24,-24,15,0,-15,25,-25,16,0,-16,26,-27,17,0,-17,28,-29,18,0,-18,30,-30,19,0,-20,32,-32,20,0,-21,34,-34,21,0,-22,36,-36,23,0,-24,38,-39,24,0,-25,41,-41,26,0,-27,43,-44,27,0,-29,47,-47,29,0,-31,50,-51,32,0,-33,54,-55,34,0,-36,58,-59,37,0,-38,62,-64,40,0,-42,68,-69,43,0,-45,74,-76,47,0,-50,81,-83,52,0,-55,89,-92,57,0,-61,100,-102,64,0,-68,112,-115,72,0,-77,127,-132,83,0,-89,148,-153,97,0,-105,175,-182,116,0,-128,214,-224,144,0,-162,274,-291,189,0,-220,380,-413,276,0,-343,618,-709,507,0,-772,1650,-2485,3041,13108,3090,-2475,1656,-760,0,515,-707,621,-338,0,280,-412,381,-217,0,192,-290,275,-159,0,146,-223,214,-126,0,118,-181,175,-104,0,98,-152,148,-88,0,84,-131,128,-76,0,73,-115,112,-67,0,65,-102,100,-60,0,58,-91,90,-54,0,53,-83,81,-49,0,48,-75,74,-45,0,44,-69,68,-41,0,40,-63,63,-38,0,37,-59,58,-35,0,34,-54,54,-32,0,32,-51,50,-30,0,30,-47,47,-28,0,28,-44,44,-26,0,26,-41,41,-25,0,24,-39,38,-23,0,23,-36,36,-22,0,22,-34,34,-20,0,20,-32,32,-19,0,19,-30,30,-18,0,18,-28,28,-17,0,17,-27,27,-16,0,16,-25,25,-15,0,15,-24,24,-14,0,14,-22,22,-13,0,13,-21,21,-13,0,13,-20,20,-12,0,12,-19,19,-11,0,11,-18,18,-11,0,11,-17,17,-10,0,10,-16,16,-9,0,9,-15,15,-9,0,9,-14,14,-8,0,8,-13,13,-8,0,8,-12,12,-7,0,7,-12,12,-7,0,7,-11,11,-7,0,6,-10,10,-6,0,6,-10,10,-6,0,6,-9,9,-5,0,5,-8,8,-5,0,5,-8,8,-5,0,5,-7,7,-4,0,4,-7,7,-4,0,4,-6,6,-4,0,4,-6,6,-4,0,4,-6,5,-3,0,3,-5,5,-3,0,3,-5,5,-3,0,3,-4,4,-3,0,3,-4,4,-2,0,2,-4,4,-2,0,2,-3,3,-2,0,2,-3,3,-2,0,2,-3,3,-2,0,2,-3,3,-2,0,2,-3,2,-1,0,1,-2,2,-1,0,1,-2,2,-1,0,1,-2,2,-1,0,1,-2,2,-1,0,1,-2,2,-1,0,1,-2,2,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0,1,-1,1,-1,0, }; /* 12.8 kHz complex carrier, sample rate 32 kHz */ static const int16_t _mixi[5] = { 16383, -13254, 5063, 5063, -13254 }; static const int16_t _mixq[5] = { 0, 9630, -15581, 15581, -9630 }; /* Masks for the PRBS */ #define _PRBS_SR1_MASK (((uint32_t) 1 << 31) - 1) #define _PRBS_SR2_MASK (((uint32_t) 1 << 29) - 1) /* The standard syster substitution table */ static const uint8_t _key_table1[0x100] = { 10, 11, 12, 13, 16, 17, 18, 19, 13, 14, 15, 16, 0, 1, 2, 3, 21, 22, 23, 24, 18, 19, 20, 21, 23, 24, 25, 26, 26, 27, 28, 29, 19, 20, 21, 22, 11, 12, 13, 14, 28, 29, 30, 31, 4, 5, 6, 7, 22, 23, 24, 25, 5, 6, 7, 8, 31, 0, 1, 2, 27, 28, 29, 30, 3, 4, 5, 6, 8, 9, 10, 11, 14, 15, 16, 17, 25, 26, 27, 28, 15, 16, 17, 18, 7, 8, 9, 10, 17, 18, 19, 20, 29, 30, 31, 0, 24, 25, 26, 27, 20, 21, 22, 23, 1, 2, 3, 4, 6, 7, 8, 9, 12, 13, 14, 15, 9, 10, 11, 12, 2, 3, 4, 5, 30, 31, 0, 1, 24, 25, 26, 27, 2, 3, 4, 5, 31, 0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16, 26, 27, 28, 29, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 5, 6, 7, 8, 19, 20, 21, 22, 12, 13, 14, 15, 17, 18, 19, 20, 27, 28, 29, 30, 10, 11, 12, 13, 11, 12, 13, 14, 6, 7, 8, 9, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 3, 4, 5, 6, 8, 9, 10, 11, 15, 16, 17, 18, 23, 24, 25, 26, 29, 30, 31, 0, 25, 26, 27, 28, 9, 10, 11, 12, 21, 22, 23, 24, 20, 21, 22, 23, 30, 31, 0, 1, 16, 17, 18, 19, 28, 29, 30, 31 }; /* Canal+ FR (Oct 1997) */ /*static const uint8_t _key_table2[0x100] = { 0, 1, 2, 3, 4, 5, 6, 7, 2, 5, 4, 7, 8, 9, 10, 11, 14, 17, 16, 19, 22, 25, 24, 27, 28, 31, 30, 1, 24, 27, 26, 29, 8, 11, 10, 13, 20, 23, 22, 25, 20, 21, 22, 23, 30, 31, 0, 1, 16, 17, 18, 19, 28, 29, 30, 31, 10, 11, 12, 13, 16, 17, 18, 19, 12, 15, 14, 17, 0, 1, 2, 3, 20, 23, 22, 25, 18, 19, 20, 21, 22, 25, 24, 27, 26, 27, 28, 29, 18, 21, 20, 23, 10, 13, 12, 15, 28, 29, 30, 31, 4, 5, 6, 7, 22, 23, 24, 25, 4, 7, 6, 9, 30, 1, 0, 3, 26, 29, 28, 31, 2, 5, 4, 7, 8, 9, 10, 11, 14, 15, 16, 17, 24, 27, 26, 29, 14, 17, 16, 19, 6, 9, 8, 11, 16, 19, 18, 21, 28, 31, 30, 1, 24, 25, 26, 27, 20, 21, 22, 23, 0, 3, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15, 8, 11, 10, 13, 2, 3, 4, 5, 30, 31, 0, 1, 24, 25, 26, 27, 2, 3, 4, 5, 30, 1, 0, 3, 6, 9, 8, 11, 12, 15, 14, 17, 26, 27, 28, 29, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 4, 7, 6, 9, 18, 21, 20, 23, 12, 13, 14, 15, 16, 19, 18, 21, 26, 29, 28, 31, 10, 11, 12, 13, 10, 13, 12, 15, 6, 7, 8, 9, 0, 3, 2, 5 };*/ static const uint8_t _vbi_sequence[10] = { 0x73, 0x9B, 0x5E, 0xB6, 0x49, 0xA1, 0x02, 0xEA, 0x15, 0xFD }; /* ECM messages from Premiere (The Nutty Professor recording) */ static const ng_ecm_t _ecm_table_premiere[0x40] = { { 0x006A1CA059D24162, { 0x15,0x3D,0x1B,0x5A,0xE3,0xD4,0x5D,0xAB,0x0E,0x4C,0xAB,0x9A,0x22,0x44,0x8E,0xAD } }, /* 0x00 */ { 0x0284BC4B4BD4BC31, { 0x67,0x83,0x81,0xB3,0x25,0xB4,0x1D,0xC2,0x33,0x7B,0xC3,0xEC,0x13,0xE9,0xE6,0x2F } }, /* 0x01 */ { 0x05FCFE681804D155, { 0x01,0x7B,0x04,0xB3,0xB4,0xC1,0x8A,0xF3,0x27,0xCD,0x85,0xCB,0xDF,0x0C,0x86,0x11 } }, /* 0x02 */ { 0x1D3855DB2F906ED3, { 0xAB,0x37,0x7D,0xF5,0x4F,0x7B,0x04,0x88,0xE7,0x39,0xCB,0xAE,0xC6,0xA3,0xBD,0x60 } }, /* 0x03 */ { 0x03DA00F9489770F7, { 0xD0,0x2F,0x44,0x9E,0xB6,0xA5,0xB4,0xC1,0xCE,0x3D,0x02,0x02,0x63,0x03,0x6F,0x24 } }, /* 0x04 */ { 0x0EA29E5525195862, { 0xFE,0x29,0x91,0x23,0x64,0x3E,0x60,0x16,0xDE,0xE0,0xF5,0x97,0x5D,0x3E,0xD6,0x6E } }, /* 0x05 */ { 0x0F77221D3104CBEB, { 0x96,0x28,0x6C,0xF6,0x07,0xC8,0x36,0xC2,0x5B,0x85,0x80,0x25,0x29,0xAB,0xE7,0xA6 } }, /* 0x06 */ { 0x08E95E066DEA4FF4, { 0x3B,0x59,0x74,0xAB,0xE3,0x70,0x0D,0x0C,0xC6,0x09,0x42,0x1E,0x80,0xA9,0xBD,0xF5 } }, /* 0x07 */ { 0x0D9E740561008817, { 0xD4,0x96,0xE8,0x82,0x95,0xC0,0x46,0x69,0xB3,0x5D,0xE8,0x3C,0xCB,0xD5,0x37,0x36 } }, /* 0x08 */ { 0x06D507F33743BA07, { 0x0C,0x8A,0x39,0x66,0xF9,0x81,0xB3,0xF3,0x78,0xCC,0x4F,0x20,0xF1,0xFF,0x1A,0x89 } }, /* 0x09 */ { 0x140DA3217EA51F2D, { 0x94,0xE7,0x8D,0x74,0x64,0xE7,0x1C,0x8B,0x29,0x33,0xE0,0x75,0x7C,0x21,0xF4,0x24 } }, /* 0x0A */ { 0x12908508768EE5DD, { 0x7C,0xE3,0xBE,0xA9,0xDB,0x88,0xBF,0xB9,0x88,0x5F,0x78,0x36,0xBB,0xF5,0xA3,0xC8 } }, /* 0x0B */ { 0x0D55A58433000C19, { 0x85,0x9B,0xF0,0x2E,0x90,0x00,0xD2,0xB8,0x7B,0x3A,0x4C,0xBB,0x7B,0xD1,0xE0,0x40 } }, /* 0x0C */ { 0x0127A2FF1B6CA6FE, { 0xD3,0x0F,0xC7,0xD6,0x1B,0xE2,0x15,0x15,0xD8,0xD1,0x82,0x26,0x9A,0xBE,0x93,0xD5 } }, /* 0x0D */ { 0x00C8F42B5D042AC5, { 0xFC,0xDB,0x25,0xD7,0x30,0x49,0x87,0x8F,0x02,0x5A,0x34,0x07,0x17,0x52,0x17,0xEE } }, /* 0x0E */ { 0x16682F0941F1DD68, { 0x01,0x35,0xE5,0xEE,0x4E,0x0D,0xED,0x01,0x25,0xEC,0x43,0xBC,0x20,0x4A,0xE4,0xDA } }, /* 0x0F */ { 0x01086DE174A780FC, { 0xE6,0xE8,0x0D,0xB7,0x4C,0x6F,0xB6,0x96,0xAC,0x87,0xB5,0x7E,0x42,0xE5,0x05,0xB3 } }, /* 0x10 */ { 0x01027E5753DF616D, { 0x0D,0x8D,0xF5,0x74,0x4E,0x89,0x13,0x26,0xC9,0xEF,0x1E,0x9B,0x4D,0xD0,0xB9,0x77 } }, /* 0x11 */ { 0x1B1898CA5ED9CC7B, { 0xB8,0x7D,0xFB,0x2A,0x1C,0x78,0x8E,0x17,0x30,0xBE,0x8E,0xB1,0xAD,0x9F,0x64,0x71 } }, /* 0x12 */ { 0x1B3FF8665DAEFF40, { 0xBA,0x9B,0x7E,0x9A,0x7B,0x9D,0xB7,0xEB,0x12,0x4C,0x6D,0x8D,0x82,0x0A,0x91,0x11 } }, /* 0x13 */ { 0x1B0C240A379F5AFE, { 0x6B,0xFF,0x9D,0xA0,0x73,0x97,0xED,0x43,0xAB,0xDB,0x8E,0x2A,0xAB,0x51,0x60,0xFB } }, /* 0x14 */ { 0x1BE17E730CA94670, { 0x00,0x92,0xF4,0x76,0x69,0x24,0xBD,0xED,0x7C,0x51,0x4D,0xFD,0x93,0x83,0x0F,0x09 } }, /* 0x15 */ { 0x1D383F0C01FC4791, { 0xC6,0xB9,0x82,0x39,0xCF,0x17,0x86,0xE4,0xC4,0x75,0x2D,0x5C,0x10,0xC7,0xB0,0xA6 } }, /* 0x16 */ { 0x001680864ED644AF, { 0xFF,0x66,0x3B,0x89,0xFA,0x5B,0xA8,0x92,0x66,0x74,0xF3,0xBE,0x6B,0xDB,0xA5,0x76 } }, /* 0x17 */ { 0x1F3B26E31A299FDB, { 0xC2,0x0B,0xD8,0x3B,0x31,0x74,0x99,0xA7,0x80,0x59,0x1B,0x5A,0xDC,0xC1,0x7B,0x04 } }, /* 0x18 */ { 0x1A0C7ABB7FA73F45, { 0xDD,0xA6,0xBC,0x69,0x5D,0x35,0x7F,0xE2,0xED,0x2D,0xDA,0xF8,0x85,0x25,0x24,0xAA } }, /* 0x19 */ { 0x09B845E71387757F, { 0xB0,0x40,0xFF,0x71,0xD9,0x12,0xEC,0x2E,0xB2,0x5C,0x14,0xDA,0x57,0x0D,0x34,0x2C } }, /* 0x1A */ { 0x092448372C8B5D92, { 0x52,0xEB,0xD2,0x07,0x0C,0x20,0x92,0xB0,0x12,0x71,0x31,0x4A,0x43,0x24,0x3D,0xEA } }, /* 0x1B */ { 0x192DE0493F15AC9F, { 0x80,0xA5,0x43,0xC1,0xA2,0x68,0xAD,0x18,0x51,0x52,0xCC,0xEE,0x5D,0x84,0x14,0x8A } }, /* 0x1C */ { 0x04CCE92274B67712, { 0x0C,0xB1,0x84,0x21,0x13,0x6C,0x27,0xDC,0xC5,0x2C,0x16,0x5E,0xC0,0x50,0x25,0x76 } }, /* 0x1D */ { 0x0279F58C084AE008, { 0xB1,0x0E,0x0A,0x2E,0x83,0x98,0x5A,0xA6,0xD2,0x7E,0x58,0xF6,0x9E,0xE5,0x56,0xC0 } }, /* 0x1E */ { 0x0E3DABAB10EDF9E1, { 0x09,0xFD,0x39,0x38,0xB7,0x3E,0x44,0x30,0x42,0xDD,0x4E,0x1A,0x0E,0xF9,0x0E,0x9C } }, /* 0x1F */ { 0x156E38BF4E330F1C, { 0xEA,0x39,0x27,0xCC,0x62,0x7E,0x1E,0xC3,0x75,0x3C,0x5F,0x0B,0x22,0xCF,0x53,0xAA } }, /* 0x20 */ { 0x05D6ECD46DF94551, { 0x85,0xC8,0x74,0xE1,0x82,0x68,0xDE,0xDE,0x8D,0x83,0x20,0x3D,0x1E,0xB8,0xAA,0x83 } }, /* 0x21 */ { 0x16FDD8D66B499A6F, { 0x26,0xE8,0x0D,0xE1,0x65,0xAA,0x9C,0xF0,0xFE,0xD9,0xD4,0x64,0x10,0x2F,0x09,0x6D } }, /* 0x22 */ { 0x07D6441441B69D78, { 0x2C,0x55,0x42,0x0F,0x26,0xC9,0x98,0x54,0xB2,0x2F,0x31,0x8D,0x53,0x8B,0xC6,0x55 } }, /* 0x23 */ { 0x102C53F7027794E2, { 0x7D,0xDB,0xAC,0xE7,0xF5,0xE3,0x93,0x74,0xB9,0xFA,0xE6,0x8E,0xAB,0x22,0xCB,0xEA } }, /* 0x24 */ { 0x10EAF127331EA737, { 0x79,0x81,0x15,0x33,0xD0,0xD8,0xC8,0xCC,0x34,0x5D,0xF0,0x48,0xC9,0x6B,0x84,0xF5 } }, /* 0x25 */ { 0x08AAAAA303DDE859, { 0x3C,0xE6,0x91,0x0A,0x97,0xBB,0xB7,0x8A,0xC9,0x85,0x5A,0xC8,0xD0,0x29,0xCE,0xD0 } }, /* 0x26 */ { 0x08FD46CF507733CF, { 0x9D,0xDD,0x31,0x3C,0x12,0x28,0x40,0xC0,0x1F,0x3A,0x38,0xBD,0xC4,0x69,0x45,0x71 } }, /* 0x27 */ { 0x00F4242A092BFA7A, { 0x6B,0x03,0xDC,0x1A,0x20,0x6B,0x17,0xD6,0xD7,0x03,0xDC,0xEC,0xA9,0x7E,0x3E,0xB4 } }, /* 0x28 */ { 0x13D16CC47644C92B, { 0xD0,0x09,0xD2,0x5C,0x9C,0xBB,0xFD,0xE9,0xBB,0x50,0xAC,0xD9,0x78,0x8A,0x1A,0x1C } }, /* 0x29 */ { 0x1176D4954F30821F, { 0xD3,0x58,0xF8,0x69,0x2C,0x40,0xB5,0xA3,0x3D,0xCB,0xF8,0xA0,0x74,0xF7,0xFE,0xAE } }, /* 0x2A */ { 0x044666E400123493, { 0x7B,0x8F,0x4B,0x1A,0x97,0x9A,0xC6,0x9F,0x69,0x8C,0xBA,0x7B,0x89,0xB4,0x40,0x50 } }, /* 0x2B */ { 0x060DDE8B31867CB1, { 0x01,0x8E,0x3A,0xC0,0x3B,0x43,0x22,0xBE,0x51,0x71,0x1F,0xE8,0xAE,0x8A,0x7F,0x33 } }, /* 0x2C */ { 0x01E132CA67F9F8F6, { 0x6B,0xC7,0x2D,0x2B,0xCE,0xB5,0x14,0x63,0x37,0x1F,0x3A,0x2E,0x93,0xAD,0xAC,0x9C } }, /* 0x2D */ { 0x03C0D4FD79579136, { 0xE1,0x67,0x1A,0x60,0x48,0xFC,0xC9,0x2A,0xCE,0x9F,0x6E,0x4E,0xE1,0xF6,0x7A,0x3C } }, /* 0x2E */ { 0x0E00E74C51487441, { 0xB6,0xE5,0xC8,0x69,0xFE,0xC4,0x81,0xA5,0xC5,0x18,0x70,0xE8,0xEA,0xE2,0x18,0x8A } }, /* 0x2F */ { 0x0650FEAD1725C3E7, { 0xE9,0xA3,0xBC,0xCA,0xEF,0xD5,0xE8,0x47,0xBF,0x3B,0x04,0x60,0xA6,0xBC,0xAF,0x68 } }, /* 0x30 */ { 0x1C2D0E7A49F7FFBF, { 0x37,0x5E,0x2A,0x34,0x8C,0x0C,0x9E,0xDC,0xA5,0x63,0xA2,0x32,0xF3,0x3E,0x29,0x5C } }, /* 0x31 */ { 0x1E322D6525A2F285, { 0x03,0xC6,0xAE,0x5A,0x60,0xE1,0x6F,0x82,0xF9,0x46,0xCA,0x81,0xA7,0x9A,0xBE,0x6D } }, /* 0x32 */ { 0x1298985465617D23, { 0xC5,0x33,0xF3,0x14,0xC5,0x94,0xEF,0xD2,0xA0,0xC0,0x3B,0x48,0x55,0x64,0x11,0xE5 } }, /* 0x33 */ { 0x0C735F5F09A38145, { 0x3D,0x17,0xD8,0x98,0x93,0x03,0xF0,0x41,0xE5,0x26,0xC9,0x9B,0x0F,0x61,0xAE,0xC2 } }, /* 0x34 */ { 0x15E9B6C33D0FDC51, { 0x95,0xB5,0xE0,0x56,0x36,0x84,0xAF,0x8A,0x5E,0x02,0xC5,0xA6,0x96,0x8C,0xAD,0x8D } }, /* 0x35 */ { 0x0DEED2DC33F941AE, { 0x9A,0x1A,0x67,0xEC,0xB9,0xC9,0x1F,0xC6,0xAA,0x6E,0xC5,0xB2,0xEC,0x7B,0x15,0x7B } }, /* 0x36 */ { 0x0673EC0423166CFE, { 0x5B,0xF1,0x1D,0x80,0x86,0xE9,0xAF,0x30,0xCB,0xAF,0xA7,0xB7,0x09,0xAE,0xE2,0xF8 } }, /* 0x37 */ { 0x09862A7B4797B9B6, { 0xE4,0x1F,0xF6,0x05,0xCA,0x13,0x17,0x79,0x23,0xE2,0xC9,0x1B,0xDF,0x2C,0x9C,0x92 } }, /* 0x38 */ { 0x1320B6B631CDDD91, { 0x39,0x0D,0x47,0x5C,0x39,0x7B,0xBB,0x24,0x0A,0xBA,0x3F,0x9E,0x4E,0x79,0xB6,0x3A } }, /* 0x39 */ { 0x0893BAD665B0B13D, { 0x11,0xF5,0x03,0x4A,0xA0,0x6D,0x0D,0x18,0x82,0x48,0x31,0x46,0x6E,0x6E,0x92,0x56 } }, /* 0x3A */ { 0x06CD358A0CB82602, { 0xA8,0x3C,0x1D,0x0A,0x5E,0x64,0xF9,0x3D,0xAE,0xCE,0x53,0x6F,0xBC,0xEC,0xBA,0xB4 } }, /* 0x3B */ { 0x03CC15543DEEB44A, { 0xE6,0x40,0x15,0xF8,0xA7,0x60,0xD5,0xDE,0xBE,0xC6,0x4B,0x72,0x9D,0x78,0x4B,0x45 } }, /* 0x3C */ { 0x03B8A8FC032E3529, { 0x60,0x66,0x7E,0xBC,0xA1,0x87,0x0A,0x5C,0x6D,0x8C,0xBD,0x37,0x60,0x3A,0x1A,0x39 } }, /* 0x3D */ { 0x1046F4D86B914ACB, { 0x24,0xB1,0xF3,0x16,0x01,0x7C,0x50,0x5C,0x27,0x32,0x4B,0xFD,0x07,0x55,0x6E,0x93 } }, /* 0x3E */ { 0x10EF953F72C02A66, { 0x35,0x67,0x73,0x86,0xF0,0x9E,0xC5,0x99,0xA0,0x03,0xDC,0xD7,0xEF,0x73,0xDF,0x5B } }, /* 0x3F */ }; static const uint8_t *_dummy_emm = (const uint8_t *) "\xFF\xFF\xFF\xFF" "DUMMYEMMDUMMYEMMDUMMYEMMDUMMYEMMDUMMYEMMDUMMYEMMDUMMYEMMDUMMYEMM" "\x9E\x4D\xDC\xF0"; static const uint8_t _ppua_emm[] = { 0x00,0x40,0x00,0x00,0x43,0x43,0x41,0x80,0x69,0x4A,0x10,0x22,0xE3,0xA9,0x9A,0xF8,0xB9,0x0F,0xD4,0xEF,0x6E,0x8A,0x30,0xCF,0xA4,0xCD,0xAD,0x83,0x4D,0xA3,0x1C,0xB0,0x2F,0x78,0xCE,0xE9,0xA8,0xDE,0xBB,0x4A,0x06,0xF0,0x27,0x4C,0xA6,0xBD,0xAD,0x67,0x9C,0xEB,0xAD,0xAE,0xD2,0xA5,0x31,0xC9,0x51,0x58,0x0D,0x72,0xF5,0x7B,0xF4,0x74,0x2D,0x45,0x3D,0xB1,0x87,0x78,0x21,0x69 }; static void _prbs_reset(ng_t *s, uint64_t cw) { s->sr1 = cw & _PRBS_SR1_MASK; s->sr2 = (cw >> 32) & _PRBS_SR2_MASK; } static uint16_t _prbs_update(ng_t *s) { uint16_t code = 0; int a, i; for(i = 0; i < 16; i++) { /* Shift the registers */ s->sr1 = (s->sr1 >> 1) ^ (s->sr1 & 1 ? 0x7BB88888UL : 0); s->sr2 = (s->sr2 >> 1) ^ (s->sr2 & 1 ? 0x17A2C100UL : 0); /* Load the multiplexer address */ a = (s->sr2 >> 24) & 0x1F; if(a == 31) a = 30; /* Shift into result register */ code = (code << 1) | ((s->sr1 >> a) & 1); } /* Code is: rrrrrrrrsssssssx * x = spare bit * r = 8-bit r value * s = 7-bit s value */ return(code >> 1); } static uint16_t _crc(const uint8_t *data, size_t length) { uint16_t crc = 0x0000; const uint16_t poly = 0xC003; int b; while(length--) { crc ^= *(data++); for(b = 0; b < 8; b++) { crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1); } } return(crc); } static void _pack_vbi_block(uint8_t vbi[10][NG_VBI_BYTES], const uint8_t msg1[NG_MSG_BYTES], const uint8_t msg2[NG_MSG_BYTES]) { int i, x; /* A block covers 10 VBI lines and contains various control bytes, two * EMM messages, a PRBS codeword, some unknown data and two methods of * error detection. */ /* Copy the message data */ memcpy(&vbi[4][5], &msg2[0], 21); memcpy(&vbi[5][5], &msg2[21], 21); memcpy(&vbi[2][5], &msg2[42], 21); memcpy(&vbi[3][5], &msg2[63], 21); memcpy(&vbi[8][5], &msg1[0], 21); memcpy(&vbi[9][5], &msg1[21], 21); memcpy(&vbi[6][5], &msg1[42], 21); memcpy(&vbi[7][5], &msg1[63], 21); /* Calculate the XOR packet data */ for(x = 5; x < 26; x++) { vbi[0][x] = vbi[1][x] = 0x00; for(i = 2; i < 10; i++) { vbi[i & 1][x] ^= vbi[i][x]; } } /* Generate the VBI header and CRC for each line */ for(i = 0; i < 10; i++) { uint16_t crc; vbi[i][0] = 0x55; vbi[i][1] = 0xD0; vbi[i][2] = 0x18; vbi[i][3] = 0x6C; vbi[i][4] = _vbi_sequence[i]; /* Calculate and apply the CRC */ crc = _crc(&vbi[i][4], 22); vbi[i][26] = (crc & 0x00FF) >> 0; vbi[i][27] = (crc & 0xFF00) >> 8; } } void _ecm_part(ng_t *s, uint8_t *dst) { const uint8_t il[20] = { 0x00, 0x01, 0x30, 0x31, 0x40, 0x41, 0x20, 0x21, 0x60, 0x61, 0x00, 0x01, 0x7E, 0x7F, 0x50, 0x51, 0x70, 0x71, 0x10, 0x11, }; const uint8_t ap[20] = { 0x01, 0x0F, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x01, 0x00, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, }; const ng_ecm_t *ecm; const uint8_t *d; uint16_t c; /* Calculate ECM table offset for this block */ c = (s->block_seq / 20 * 2 + il[s->block_seq % 20]) & 0x7F; ecm = &_ecm_table_premiere[c / 2]; /* Get a pointer to the 8 ECM bytes to send */ d = &ecm->ecm[c & 1 ? 8 : 0]; /* Encode the result into the VBI line */ c = (c << 4) | ap[s->block_seq % 20]; dst[0] = c >> 8; dst[1] = c & 0xFF; memcpy(&dst[2], d, 8); if(ap[s->block_seq % 20] == 0x00) { s->cw = ecm->cw; } else if(s->block_seq % 20 == 13) { /* The ECM is updated here */ } } static void _update_field_order(ng_t *s) { int i, j; int b[32]; /* This function generates the scrambled line order for the * next field based on _key_table, s->s and s->r parameters. * * Based on work by Markus G. Kuhn from his publication * 'Analysis of the Nagravision Video Scrambling Method', 1998-07-09 */ for(i = 0; i < 32; i++) { b[i] = -32 + i; } for(i = 0; i < 287; i++) { j = i <= 254 ? _key_table1[(s->r + (2 * s->s + 1) * i) & 0xFF] : i - 255; b[j] = s->order[b[j] + 32] = i; } } int ng_init(ng_t *s, vid_t *vid) { int i; memset(s, 0, sizeof(ng_t)); s->vid = vid; /* Calculate the high level for the VBI data, 66% of the white level */ i = round((vid->white_level - vid->black_level) * 0.66); s->lut = vbidata_init( NG_VBI_WIDTH, s->vid->width, i, VBIDATA_FILTER_RC, 0.7 ); if(!s->lut) { return(VID_OUT_OF_MEMORY); } s->vbi_seq = 0; s->block_seq = 0; /* Initial seeds. Updated every field. */ s->s = 0; s->r = 0; _update_field_order(s); /* Allocate memory for the delay */ s->vid->olines += NG_DELAY_LINES; /* Allocate memory for the audio inversion FIR filters */ s->firli = calloc(NTAPS * 2, sizeof(int16_t)); s->firlq = calloc(NTAPS * 2, sizeof(int16_t)); s->firri = calloc(NTAPS * 2, sizeof(int16_t)); s->firrq = calloc(NTAPS * 2, sizeof(int16_t)); s->mixx = 0; s->firx = 0; if(s->firli == NULL || s->firlq == NULL || s->firri == NULL || s->firrq == NULL) { return(VID_OUT_OF_MEMORY); } return(VID_OK); } void ng_free(ng_t *s) { free(s->firli); free(s->firlq); free(s->firri); free(s->firrq); free(s->lut); } void ng_invert_audio(ng_t *s, int16_t *audio, size_t samples) { int i, x; int a; /* Invert the audio spectrum below 12.8 kHz. * * Each audio channel is mixed with a complex sine wave to create a * DSB-SC signal at +12.8 kHz. A sharp FIR filter is then used to * remove everything but the lower sideband, with the result then * copied back into the audio buffer. * * The mixing and filtering use complex operations to avoid the * upper sideband interfering after mixing. */ if(audio == NULL) return; for(i = 0; i < samples; i++) { /* Left */ s->firli[s->firx + NTAPS] = s->firli[s->firx] = (audio[i * 2 + 0] * _mixi[s->mixx] - audio[i * 2 + 0] * _mixq[s->mixx]) >> 15; s->firlq[s->firx + NTAPS] = s->firlq[s->firx] = (audio[i * 2 + 0] * _mixq[s->mixx] + audio[i * 2 + 0] * _mixi[s->mixx]) >> 15; /* Right */ s->firri[s->firx + NTAPS] = s->firri[s->firx] = (audio[i * 2 + 1] * _mixi[s->mixx] - audio[i * 2 + 1] * _mixq[s->mixx]) >> 15; s->firrq[s->firx + NTAPS] = s->firrq[s->firx] = (audio[i * 2 + 1] * _mixq[s->mixx] + audio[i * 2 + 1] * _mixi[s->mixx]) >> 15; if(++s->firx == NTAPS) s->firx = 0; if(++s->mixx == 5) s->mixx = 0; /* Left */ for(a = x = 0; x < NTAPS; x++) { a += s->firli[s->firx + x] * _firi[x] - s->firlq[s->firx + x] * _firq[x]; } audio[i * 2 + 0] = a >> 15; /* Right */ for(a = x = 0; x < NTAPS; x++) { a += s->firri[s->firx + x] * _firi[x] - s->firrq[s->firx + x] * _firq[x]; } audio[i * 2 + 1] = a >> 15; } } void ng_render_line(ng_t *s) { int j = 0; int x, f, i; int line; /* Calculate which line is about to be transmitted due to the delay */ line = s->vid->line - NG_DELAY_LINES; if(line < 0) line += s->vid->conf.lines; /* Calculate the field and field line */ f = (line < NG_FIELD_2_START ? 1 : 2); i = line - (f == 1 ? NG_FIELD_1_START : NG_FIELD_2_START); if(i >= 0 && i < NG_LINES_PER_FIELD) { /* Adjust for the decoder's 32 line delay */ i += 32; if(i >= NG_LINES_PER_FIELD) { i -= NG_LINES_PER_FIELD; f = (f == 1 ? 2 : 1); } /* Reinitialise the seeds if this is a new field */ if(i == 0) { int sf = s->vid->frame % 50; if((sf == 6 || sf == 31) && f == 2) { _prbs_reset(s, s->cw); } x = _prbs_update(s); s->s = x & 0x7F; s->r = x >> 7; _update_field_order(s); } /* Calculate which line in the delay buffer to copy image data from */ j = (f == 1 ? NG_FIELD_1_START : NG_FIELD_2_START) + s->order[i]; if(j < line) j += s->vid->conf.lines; j -= line; if(j < 0 || j >= NG_DELAY_LINES) { /* We should never get to this point */ fprintf(stderr, "*** Nagravision Syster scrambler is trying to read an invalid line ***\n"); j = 0; } } vid_adj_delay(s->vid, NG_DELAY_LINES); /* Swap the active line with the oldest line in the delay buffer, * with active video offset in j if necessary. */ if(j > 0) { int16_t *dline = s->vid->oline[s->vid->odelay + j]; /* For PAL the colour burst is not moved, just the active * video. For SECAM the entire line is moved. */ x = s->vid->active_left * 2; if(s->vid->conf.colour_mode == VID_SECAM) x = 0; for(; x < s->vid->width * 2; x += 2) { s->vid->output[x] = dline[x]; } } /* Render the VBI data * These lines where used by Premiere */ if(line == 14 || line == 15 || line == 327 || line == 328) { if(s->vbi_seq == 0) { const uint8_t *emm1 = _dummy_emm; const uint8_t *emm2 = _dummy_emm; uint8_t msg1[NG_MSG_BYTES]; uint8_t msg2[NG_MSG_BYTES]; /* Transmit the PPUA EMM every 1000 frames */ if(s->vid->frame > s->next_ppua) { emm1 = _ppua_emm; s->next_ppua = s->vid->frame + 1000; } /* Build part 1 of the VBI block */ msg1[ 0] = 0x72; /* Mode? (0x72 = Premiere / Canal+ Old, 0x48 = Clear, 0x7A or 0xFA) */ _ecm_part(s, &msg1[1]); msg1[11] = 0xFF; /* Simple checksum -- the Premiere VBI sample only has 0x00/0xFF here */ for(x = 0; x < 11; x++) { msg1[11] ^= msg1[x]; } memcpy(&msg1[12], emm1, 72); /* Build part 2 of the VBI block */ msg2[ 0] = 0xFE; /* ??? Premiere DE: 0xFE, Canal+ PL: 0x00 */ msg2[ 1] = 0x28; /* ??? Premiere DE: 0x28, Canal+ PL: 0x2A */ msg2[ 2] = 0xB1; /* ??? Premiere DE: 0xB1, Canal+ PL: 0xE4 */ msg2[ 3] = emm1 == _ppua_emm ? 0x01 : 0x00; /* 0x00, or 0x01 when a broadcast EMM is present */ msg2[ 4] = emm2 == _ppua_emm ? 0x01 : 0x00; msg2[ 5] = 0x00; /* The following bytes are always 0x00 */ msg2[ 6] = 0x00; msg2[ 7] = 0x00; msg2[ 8] = 0x00; msg2[ 9] = 0x00; msg2[10] = 0x00; msg2[11] = 0x00; memcpy(&msg2[12], emm2, 72); /* Pack the messages into the next 10 VBI lines */ _pack_vbi_block(s->vbi, msg1, msg2); /* Advance the block sequence counter */ s->block_seq++; } /* Render the line */ vbidata_render_nrz(s->lut, s->vbi[s->vbi_seq++], -45, NG_VBI_BYTES * 8, VBIDATA_LSB_FIRST, s->vid->output, 2); if(s->vbi_seq == 10) { s->vbi_seq = 0; } } } hacktv-master/syster.h000066400000000000000000000052041357376541300153500ustar00rootroot00000000000000/* Nagravision Syster encoder for hacktv */ /*=======================================================================*/ /* Copyright 2018 Alex L. James */ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #ifndef _SYSTER_H #define _SYSTER_H #include #include "video.h" #define NG_VBI_WIDTH 284 #define NG_VBI_BYTES 28 #define NG_MSG_BYTES 84 #define NG_FIELD_1_START 23 #define NG_FIELD_2_START 336 #define NG_LINES_PER_FIELD 287 /* NG_DELAY_LINES needs to be long enough for the scrambler to access any * line in the next field from at least the last 32 lines of the current. * This is a safe amount and can probably be reduced. */ #define NG_DELAY_LINES (625 + NG_FIELD_1_START + NG_LINES_PER_FIELD - (NG_FIELD_2_START + NG_LINES_PER_FIELD - 32)) /* Entitlement control messages */ typedef struct { uint64_t cw; uint8_t ecm[16]; } ng_ecm_t; typedef struct { vid_t *vid; /* VBI */ int16_t *lut; uint8_t vbi[10][NG_VBI_BYTES]; int vbi_seq; int block_seq; /* EMM */ int next_ppua; /* PRBS state */ uint64_t cw; uint32_t sr1; uint32_t sr2; /* PRNG seed values */ int s; /* 0, ..., 127 */ int r; /* 0, ..., 255 */ /* The line order for the next field (0-287) */ int order[NG_LINES_PER_FIELD]; /* Audio inversion FIR filter */ int16_t *firli, *firlq; /* Left channel, I + Q */ int16_t *firri, *firrq; /* Right channel, I + Q */ int mixx; int firx; } ng_t; extern int ng_init(ng_t *s, vid_t *vs); extern void ng_free(ng_t *s); extern void ng_invert_audio(ng_t *s, int16_t *audio, size_t samples); extern void ng_render_line(ng_t *s); #endif hacktv-master/teletext.c000066400000000000000000000637251357376541300156640ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* -=== Teletext encoder ===- * * Encodes a teletext stream, inserting packets into the VBI area of the * video signal. Teletext pages in the TTI file format are supported. * * This version only works with 625-line PAL modes. There are NTSC and * SECAM variations of Teletext but those are not currently supported. * * TODO: The clock on the header line will not be accurate when not * transmitting live. This could be fixed by calculating the time based * on the timecode, but it will need some flag to indicate if the output * is live or not. * */ #include #include #include #include #include #include #include #include #include #include #include "video.h" #include "vbidata.h" static const uint8_t _parity[0x80] = { 0x80, 0x01, 0x02, 0x83, 0x04, 0x85, 0x86, 0x07, 0x08, 0x89, 0x8A, 0x0B, 0x8C, 0x0D, 0x0E, 0x8F, 0x10, 0x91, 0x92, 0x13, 0x94, 0x15, 0x16, 0x97, 0x98, 0x19, 0x1A, 0x9B, 0x1C, 0x9D, 0x9E, 0x1F, 0x20, 0xA1, 0xA2, 0x23, 0xA4, 0x25, 0x26, 0xA7, 0xA8, 0x29, 0x2A, 0xAB, 0x2C, 0xAD, 0xAE, 0x2F, 0xB0, 0x31, 0x32, 0xB3, 0x34, 0xB5, 0xB6, 0x37, 0x38, 0xB9, 0xBA, 0x3B, 0xBC, 0x3D, 0x3E, 0xBF, 0x40, 0xC1, 0xC2, 0x43, 0xC4, 0x45, 0x46, 0xC7, 0xC8, 0x49, 0x4A, 0xCB, 0x4C, 0xCD, 0xCE, 0x4F, 0xD0, 0x51, 0x52, 0xD3, 0x54, 0xD5, 0xD6, 0x57, 0x58, 0xD9, 0xDA, 0x5B, 0xDC, 0x5D, 0x5E, 0xDF, 0xE0, 0x61, 0x62, 0xE3, 0x64, 0xE5, 0xE6, 0x67, 0x68, 0xE9, 0xEA, 0x6B, 0xEC, 0x6D, 0x6E, 0xEF, 0x70, 0xF1, 0xF2, 0x73, 0xF4, 0x75, 0x76, 0xF7, 0xF8, 0x79, 0x7A, 0xFB, 0x7C, 0xFD, 0xFE, 0x7F }; static const uint8_t _hamming84[0x10] = { 0x15, 0x02, 0x49, 0x5E, 0x64, 0x73, 0x38, 0x2F, 0xD0, 0xC7, 0x8C, 0x9B, 0xA1, 0xB6, 0xFD, 0xEA, }; static uint8_t _unhamming84(uint8_t b) { int i; /* This won't handle bit errors, it's only for internal use */ for(i = 0; i < 16; i++) { if(_hamming84[i] == b) { return(i); } } return(0); } static uint16_t _crc(uint16_t crc, const uint8_t *data, size_t length) { uint16_t i, bit; uint8_t b; while(length--) { b = *(data++); /* As per ETS 300 706 9.6.1 */ for(i = 0; i < 8; i++, b <<= 1) { bit = ((crc >> 15) ^ (crc >> 11) ^ (crc >> 8) ^ (crc >> 6) ^ (b >> 7)) & 1; crc = (crc << 1) | bit; } } return(crc); } static int _line_packet_number(const uint8_t line[45]) { return( (_unhamming84(line[4]) << 1) | (_unhamming84(line[3]) >> 3) ); } /*static void _dump_line(uint8_t line[45]) { int magazine; int packet_number; int i; char c; magazine = _unhamming84(line[3]) & 7; if(magazine == 0) magazine = 8; packet_number = _line_packet_number(line); printf("%d/%d '", magazine, packet_number); for(i = 0; i < (packet_number == 0 ? 32 : 40); i++) { c = line[(packet_number == 0 ? 13 : 5) + i] & 0x7F; printf("%c", isprint(c) ? c : '_'); } printf("'\n"); }*/ static void *_paritycpy(void *dest, const void *src, size_t n, uint8_t pad) { uint8_t *d = dest; const uint8_t *s = src; /* Copy the bytes, applying parity bits */ for(; n && *s; n--) { *(d++) = _parity[*(s++) & 0x7F]; } /* Fill the remainder of the line with spaces */ while(n--) { *(d++) = _parity[pad & 0x7F]; } return(dest); } static int _mjd(int year, int month, int day) { double mjd; /* Calculate Modified Julian Date */ mjd = 367.0 * year - (int) (7.0 * (year + (int) ((month + 9.0) / 12.0)) / 4.0) + (int) (275.0 * month / 9.0) + day - 678987.0; return((int) mjd); } #ifdef __MINGW32__ static struct tm *gmtime_r(const time_t *timep, struct tm *result) { struct tm *tm = gmtime(timep); if(!tm) { return(NULL); } memcpy(result, tm, sizeof(struct tm)); return(result); } #endif static void _packet830(uint8_t line[45], time_t timestamp) { int magazine = 8; int packet_number = 30; int initial_page = 0x100; int initial_subcode = 0x3F7F; struct tm tm; int mjd; /* Synchronization sequence (Clock run-in and framing code) */ line[0] = 0x55; line[1] = 0x55; line[2] = 0x27; /* Packet address */ line[3] = _hamming84[((packet_number & 1) << 3) | (magazine & 7)]; line[4] = _hamming84[(packet_number >> 1) & 15]; /* Designation code */ line[5] = _hamming84[0]; /* 0 = Multiplexed, 1 = Non-multiplexed */ /* Initial Page */ line[6] = _hamming84[initial_page & 0x0F]; line[7] = _hamming84[(initial_page >> 4) & 0x0F]; line[8] = _hamming84[initial_subcode & 0x0F]; line[9] = _hamming84[ (((initial_page >> 8) & 0x01) << 3) | ((initial_subcode >> 4) & 0x07) ]; line[10] = _hamming84[(initial_subcode >> 8) & 0x0F]; line[11] = _hamming84[ (((initial_page >> 9) & 0x03) << 2) | ((initial_subcode >> 12) & 0x03) ]; /* Network Identification Code */ /* This is not supported yet. Note: Bits are reversed */ line[12] = 0x00; line[13] = 0x00; /* Time Offset Code (TODO) */ line[14] = 0; /* Modified Julian Date */ gmtime_r(×tamp, &tm); mjd = _mjd(1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday); line[15] = mjd % 100000 / 10000 + 1; line[16] = (mjd % 10000 / 1000 + 1) << 4 | (mjd % 1000 / 100 + 1); line[17] = (mjd % 100 / 10 + 1) << 4 | (mjd % 10 + 1); /* UTC time */ line[18] = ((tm.tm_hour / 10) + 1) << 4 | ((tm.tm_hour % 10) + 1); line[19] = ((tm.tm_min / 10) + 1) << 4 | ((tm.tm_min % 10) + 1); line[20] = ((tm.tm_sec / 10) + 1) << 4 | ((tm.tm_sec % 10) + 1); /* Reserved */ line[21] = 0x00; line[22] = 0x00; line[23] = 0x00; line[24] = 0x00; /* Status Display */ _paritycpy(&line[25], "hacktv", 20, ' '); } static void _header(uint8_t line[45], int magazine, int page, int subcode, int status, char *data) { int packet_number = 0; int erase_page; int subtitle; int newsflash; int inhibit_display; int interrupted_sequence; int update_indicator; int suppress_header; int national_option_character_subset; int magazine_serial; erase_page = (status >> 14) & 1; newsflash = (status >> 0) & 1; subtitle = (status >> 1) & 1; suppress_header = (status >> 2) & 1; update_indicator = (status >> 3) & 1; interrupted_sequence = (status >> 4) & 1; inhibit_display = (status >> 5) & 1; magazine_serial = 0; // (status >> 6) & 1; /* We only use parallel */ national_option_character_subset = (status >> 7) & 7; /* Synchronization sequence (Clock run-in and framing code) */ line[0] = 0x55; line[1] = 0x55; line[2] = 0x27; /* Packet address */ line[3] = _hamming84[((packet_number & 1) << 3) | (magazine & 7)]; line[4] = _hamming84[(packet_number >> 1) & 15]; /* Page packet header (Y = 0) */ line[5] = _hamming84[page & 0x0F]; line[6] = _hamming84[(page >> 4) & 0x0F]; line[7] = _hamming84[subcode & 0x0F]; line[8] = _hamming84[ (erase_page ? 1 << 3 : 0) | ((subcode >> 4) & 0x07) ]; line[9] = _hamming84[(subcode >> 8) & 0x0F]; line[10] = _hamming84[ (subtitle ? 1 << 3 : 0) | (newsflash ? 1 << 2 : 0) | ((subcode >> 12) & 0x03) ]; line[11] = _hamming84[ (inhibit_display ? 1 << 3 : 0) | (interrupted_sequence ? 1 << 2 : 0) | (update_indicator ? 1 << 1 : 0) | (suppress_header ? 1 << 0 : 0) ]; line[12] = _hamming84[ (national_option_character_subset << 1) | (magazine_serial ? 1 << 0 : 0) ]; /* Copy the data, applying parity bits */ _paritycpy(&line[13], data, 32, ' '); } static void _fastext_line(uint8_t line[45], int magazine, int links[6]) { int packet_number = 27; int page; int subcode; uint8_t *link; int i; /* Synchronization sequence (Clock run-in and framing code) */ line[0] = 0x55; line[1] = 0x55; line[2] = 0x27; /* Packet address */ line[3] = _hamming84[((packet_number & 1) << 3) | (magazine & 7)]; line[4] = _hamming84[(packet_number >> 1) & 15]; /* Designation code, always 0 */ line[5] = _hamming84[0]; for(i = 0; i < 6; i++) { link = &line[6 + (6 * i)]; if(links[i] < 0x100) { page = 0x8FF; subcode = 0x3F7F; } else if(links[i] < 0x10000) { page = links[i]; subcode = 0x3F7F; } else { page = links[i] >> 8; subcode = links[i] & 0xFF; } /* The magazine number is xor'ed with the page number */ page ^= (magazine & 7) << 8; link[0] = _hamming84[page & 0x0F]; link[1] = _hamming84[(page >> 4) & 0x0F]; link[2] = _hamming84[subcode & 0x0F]; link[3] = _hamming84[ (((page >> 8) & 0x01) << 3) | ((subcode >> 4) & 0x07) ]; link[4] = _hamming84[(subcode >> 8) & 0x0F]; link[5] = _hamming84[ (((page >> 9) & 0x03) << 2) | ((subcode >> 12) & 0x03) ]; } /* Link Control Byte, always 0x0F */ line[42] = _hamming84[0x0F]; /* Page CRC padding. Real CRC is generated later. */ line[43] = 0x12; line[44] = 0x34; } static void _line(uint8_t line[45], int magazine, int packet_number, const uint8_t *data) { /* Synchronization sequence (Clock run-in and framing code) */ line[0] = 0x55; line[1] = 0x55; line[2] = 0x27; /* Packet address */ line[3] = _hamming84[((packet_number & 1) << 3) | (magazine & 7)]; line[4] = _hamming84[(packet_number >> 1) & 15]; /* Copy the data, applying parity bits */ _paritycpy(&line[5], data, 40, ' '); } /*static void _dump_service_map(tt_service_t *s) { tt_magazine_t *mag; tt_page_t *page; tt_page_t *subpage; int i; for(i = 1; i <= 8; i++) { printf("Magazine %d:\n", i); mag = &s->magazines[i & 7]; page = mag->pages; while(page) { printf("+ Page %03X/%d\n", page->page, page->subpage); for(subpage = page->next_subpage; subpage != page; subpage = subpage->next_subpage) { printf("+++ Subpage %03X/%X", subpage->page, subpage->subpage); if(subpage->subpages != page->subpages) { printf(" ! subpage->subpages != page->subpages"); } printf("\n"); } page = page->next; if(page == mag->pages) break; } } }*/ static char *_mk_header(char *s, uint16_t page, time_t timestamp) { char temp[33]; struct tm *tm; /* TODO: Make this customisable */ tm = localtime(×tamp); snprintf(temp, 33, "hacktv %03X %%a %%d %%b\x03" "%%H:%%M/%%S", page); strftime(s, 33, temp, tm); return(s); } static void _update_page_crc(tt_page_t *page, const uint8_t header[45]) { const uint8_t *blank = (const uint8_t *) " "; uint8_t *line; uint16_t crc; int l, i; /* Begin calculating the CRC from the header */ crc = _crc(0x0000, &header[13], 24); /* Scan each line in order, using the blank line if not found */ for(l = 1; l < 26; l++) { line = NULL; /* Try to find this line */ for(i = 0; i < page->packets; i++) { if(_line_packet_number(&page->data[i * 45]) == l) { line = &page->data[i * 45 + 5]; break; } } crc = _crc(crc, line != NULL ? line : blank, 40); } /* Scan for packet 27 and update CRC */ for(line = page->data, i = 0; i < page->packets; line += 45, i++) { if(_line_packet_number(line) == 27) { line[43] = (crc >> 8) & 0xFF; line[44] = (crc >> 0) & 0xFF; } } } static int _next_magazine_packet(tt_service_t *s, tt_magazine_t *mag, uint8_t line[45], unsigned int timecode) { char header[33]; if(mag->filler) { /* Generate the filler header packet */ _mk_header(header, 0x8FF, s->timestamp); _header(line, mag->magazine & 0x07, 0xFF, 0x3F7F, 0x8000, header); mag->filler = 0; return(TT_OK); } if(mag->pages == NULL) { return(TT_NO_PACKET); } if(mag->row == 0) { int status = mag->page->page_status; /* Set the erase flag if needed */ status &= ~(1 << 14); status |= mag->page->erase << 14; mag->page->erase = 0; _mk_header(header, mag->page->page, s->timestamp); _header(line, mag->magazine & 0x07, mag->page->page & 0xFF, mag->page->subcode, status, header); /* Update the page CRC */ _update_page_crc(mag->page, line); /* Set the delay time (20ms rule) */ mag->delay = timecode + s->header_delay; mag->row++; } else { /* Return nothing if the next row is a display row, * and we're still within the delay period */ if(mag->row - 1 == mag->page->nodelay_packets && timecode < mag->delay) { return(TT_NO_PACKET); } /* Copy the packet */ memcpy(line, &mag->page->data[(mag->row - 1) * 45], 45); mag->row++; } /* Test if this is the last row on this page */ if(mag->row - 1 == mag->page->packets) { tt_page_t *npage = mag->page->next; /* Test if we need to advance the next page's subpage */ if(npage->cycle_time && npage != npage->next_subpage) { int adv = 0; if(npage->cycle_mode == 0) { /* Timer mode */ if(timecode >= npage->cycle_count) { npage->cycle_count = timecode + npage->cycle_time * s->second_delay; adv = 1; } } else { /* Cycle mode */ npage->cycle_count++; if(npage->cycle_count == npage->cycle_time) { npage->cycle_count = 0; adv = 1; } } if(adv) { mag->page->next = npage->next_subpage; npage->next_subpage->next = npage->next; npage->next_subpage->cycle_count = npage->cycle_count; npage->next_subpage->erase = 1; } } /* Advance magazine to the next page */ mag->page = mag->page->next; mag->row = 0; /* Special case for magazines with only one page, * set the filler flag to correctly end the page */ /* TODO: Am I correct here? Is this needed? What about subpages? */ if(mag->pages->next == mag->pages) { mag->filler = 1; } } return(TT_OK); } static int _next_packet(tt_service_t *s, uint8_t line[45], unsigned int timecode) { int i, r; time_t timestamp; /* Update the timestamp */ timestamp = time(NULL); /* If the timestamp has changed, we need to insert an 8/30 packet */ if(s->timestamp != timestamp) { s->timestamp = timestamp; _packet830(line, timestamp); return(TT_OK); } /* Test each magazine for the next available packet */ for(i = 0; i < 8; i++) { r = _next_magazine_packet(s, &s->magazines[s->magazine++], line, timecode); s->magazine &= 7; if(r == TT_OK) { return(TT_OK); } } /* No magazine returned a packet, return nothing */ return(TT_NO_PACKET); } static int _line_len(uint8_t line[40]) { int x; for(x = 0; x < 40; x++) { if(line[x] != ' ' && line[x] != '\0') break; } return(40 - x); } static int _page_mkpackets(tt_page_t *page, uint8_t lines[25][40]) { int i, j; /* Count the number of non-empty packets (+ 1 for fastext packet) */ page->packets = 1; page->nodelay_packets = 0; for(i = 1; i < 25; i++) { if(_line_len(lines[i]) > 0) { page->packets++; } } /* (Re)allocate memory for the packets */ page->data = realloc(page->data, page->packets * 45); /* The fastext packet is transmitted before the page content (Annex B.2) */ _fastext_line(&page->data[0], (page->page >> 8) & 0x07, page->links); /* Generate the line packets */ for(j = 1, i = 1; i < 25; i++) { if(_line_len(lines[i]) > 0) { _line(&page->data[j++ * 45], (page->page >> 8) & 0x07, i, lines[i]); } } return(TT_OK); } static void _add_page(tt_service_t *s, tt_page_t *new_page) { tt_magazine_t *mag; tt_page_t *page; tt_page_t *subpage; /* Make sure erase flag is set for the new page */ new_page->erase = 1; mag = &s->magazines[(new_page->page >> 8) & 0x07]; if(mag->pages == NULL) { /* This is the first page added to the magazine */ mag->pages = new_page; mag->page = new_page; new_page->next = new_page; new_page->subpages = new_page; new_page->next_subpage = new_page; return; } /* Scan the magazine for the page insertion point */ for(page = mag->pages; page->next != mag->pages; page = page->next) { if(page->page <= new_page->page && page->next->page > new_page->page) break; } if(page->page != new_page->page) { /* This is a new page, to be appended */ new_page->next = page->next; new_page->subpages = new_page; new_page->next_subpage = new_page; page->next = new_page; if(new_page->page < mag->pages->page) { mag->pages = new_page; } } else { /* This is an existing page */ new_page->next = page->next; /* Scan for the sub-page insertion point */ for(subpage = page->subpages; subpage->next_subpage != page->subpages; subpage = subpage->next_subpage) { if(subpage->subpage <= new_page->subpage && subpage->next_subpage->subpage > new_page->subpage) break; } if(subpage->subpage != new_page->subpage) { /* This is a new subpage, to be appended */ new_page->next_subpage = subpage->next_subpage; subpage->next_subpage = new_page; if(new_page->subpage < page->subpages->subpage) { page->subpages = new_page; } new_page->subpages = page->subpages; } else { /* This is an existing subpage, replace it */ /* Copy the subpage pointers */ new_page->next_subpage = subpage->next_subpage; new_page->subpages = subpage->subpages; /* Free the old page packet data */ free(subpage->data); /* Overwrite the old page data with the new one */ memcpy(subpage, new_page, sizeof(tt_page_t)); /* And finally free the new page data */ free(new_page); } } } static int _load_tti(tt_service_t *s, char *filename) { char buf[200]; size_t i, len; int c; FILE *f; unsigned int x; char *t; uint8_t lines[25][40]; tt_page_t *page; int esc; f = fopen(filename, "rb"); if(!f) { perror("fopen"); return(TT_ERROR); } page = calloc(sizeof(tt_page_t), 1); if(!page) { perror("calloc"); fclose(f); return(TT_OUT_OF_MEMORY); } len = 0; /* Test if this is a TTI file */ len += fread(buf, sizeof(char), 3, f); /* Expect the file to begin with a two letter code followed by a comma */ if(len != 3 || buf[0] < 'A' || buf[0] > 'Z' || buf[1] < 'A' || buf[1] > 'Z' || buf[2] != ',') { fprintf(stderr, "%s: Unrecognised file format. Skipping...\n", filename); free(page); fclose(f); return(TT_ERROR); } while(!feof(f)) { /* Top-up input buffer */ len += fread(buf + len, sizeof(char), 200 - len, f); while(len > 0) { /* Search for a newline */ for(i = 0; i < len; i++) { if(buf[i] == '\r' || buf[i] == '\n' || buf[i] == '\0') { buf[i] = '\0'; break; } } if(i == len) { /* No newline found */ break; } if(buf[0] == '\0') { /* Blank line */ } else if(strncmp("PN,", buf, 3) == 0) { /* PN - Page Number */ /* PN,mppss (m = magazine, pp = page, ss = subpage) */ if(page->page > 0) { tt_page_t *opage = page; /* Save current page */ _page_mkpackets(page, lines); _add_page(s, page); /* Lazily copy the old page settings */ page = malloc(sizeof(tt_page_t)); if(!page) { perror("malloc"); fclose(f); return(TT_OUT_OF_MEMORY); } memcpy(page, opage, sizeof(tt_page_t)); /* Have to unset the packet pointer */ page->data = NULL; } /* Clear existing page data */ for(c = 0; c < 25; c++) { memset(lines[c], ' ', 40); } x = strtol(buf + 3, NULL, 16); if(x < 0x10000) { page->page = x; page->subpage = 0; } else { page->page = x >> 8; page->subpage = x & 0xFF; } } else if(strncmp("CT,", buf, 3) == 0) { /* CT - Cycle Time */ /* CT,n, (n = delay, t = C/T (Cycle/Timed) */ page->cycle_time = strtol(buf + 3, &t, 10); page->cycle_mode = 0; if(t[0] == ',' && (t[1] == 'C' || t[1] == 'c')) { page->cycle_mode = 1; } } else if(strncmp("DE,", buf, 3) == 0) { /* DE - Description */ /* DE, */ } else if(strncmp("PS,", buf, 3) == 0) { /* PS - Page Status */ /* PS,ssss */ x = strtol(buf + 3, NULL, 16); page->page_status = x; } else if(strncmp("SC,", buf, 3) == 0) { /* SC - Subcode */ /* SC,ssss */ x = strtol(buf + 3, NULL, 16); page->subcode = x; } else if(strncmp("OL,", buf, 3) == 0) { /* OL - Output Line */ /* OL,nn, */ x = strtol(buf + 3, &t, 10); if(x > 0 && x < 25) { if(*t == ',') { t++; } for(esc = 0, c = 0; *t && c < 40; t++) { if(*t == 0x1B) { esc = 1; continue; } lines[x][c++] = (esc ? *t - 0x40 : *t) & 0x7F; esc = 0; } } } else if(strncmp("FL,", buf, 3) == 0) { /* FL - Fastext Link */ /* FL,rrr,ggg,yyy,ccc,lll,iii */ t = buf + 2; for(c = 0; *t == ',' && c < 6; c++) { page->links[c] = strtol(t + 1, &t, 16); } } else if(buf[2] != ',') { fprintf(stderr, "%s: Unrecognised line: '%s'\n", filename, buf); } len -= i + 1; memmove(buf, buf + i + 1, len); } if(len == 200) { fprintf(stderr, "%s: Line too long (>200 characters)\n", filename); len = 0; } } fclose(f); if(page->page > 0) { _page_mkpackets(page, lines); _add_page(s, page); } return(TT_OK); } static int _new_service(tt_service_t *s) { int i; tt_magazine_t *mag; /* Create an empty service */ s->timestamp = 0; s->second_delay = 25 * 625; s->header_delay = (20e-3 * s->second_delay) + 0.5; s->magazine = 1; for(i = 1; i <= 8; i++) { mag = &s->magazines[i & 0x07]; mag->magazine = i; mag->filler = 0; mag->pages = NULL; mag->row = 0; mag->delay = 0; } return(TT_OK); } static void _free_service(tt_service_t *s) { tt_magazine_t *mag; tt_page_t *npage; tt_page_t *nsubpage; int i; for(i = 0; i < 8; i++) { mag = &s->magazines[i]; if(mag->pages == NULL) continue; mag->page = mag->pages->next->subpages; mag->pages->next = NULL; while(mag->page) { npage = mag->page->next == NULL ? NULL : mag->page->next->subpages; for(mag->page->next_subpage = mag->page->subpages->next_subpage; mag->page->next_subpage != mag->page; mag->page->next_subpage = nsubpage) { nsubpage = mag->page->next_subpage->next_subpage; free(mag->page->next_subpage->data); free(mag->page->next_subpage); } free(mag->page->data); free(mag->page); mag->page = npage; } mag->pages = NULL; } } int tt_init(tt_t *s, vid_t *vid, char *path) { int16_t level; struct stat fs; memset(s, 0, sizeof(tt_t)); /* Calculate the high level for teletext data, 66% of the white level */ level = round((vid->white_level - vid->black_level) * 0.66); s->vid = vid; s->lut = vbidata_init( 444, s->vid->width, level, VBIDATA_FILTER_RC, 0.7 ); if(!s->lut) { return(VID_OUT_OF_MEMORY); } /* Is the path to a raw teletext packet source? */ if(strncmp(path, "raw:", 4) == 0) { if(strcmp(path + 4, "-") == 0) { s->raw = stdin; } else { s->raw = fopen(path + 4, "rb"); if(!s->raw) { fprintf(stderr, "%s: ", path + 4); perror("fopen"); tt_free(s); return(VID_ERROR); } } return(VID_OK); } _new_service(&s->service); /* Test if the path is a file or a directory */ if(stat(path, &fs) != 0) { fprintf(stderr, "%s: ", path); perror("stat"); tt_free(s); return(VID_ERROR); } if(fs.st_mode & S_IFDIR) { DIR *dir; struct dirent *ent; char filename[PATH_MAX]; /* Path is a directory, scan all the files within */ dir = opendir(path); if(!dir) { fprintf(stderr, "%s: ", path); perror("opendir"); tt_free(s); return(VID_ERROR); } while((ent = readdir(dir))) { /* Skip hidden dot files */ if(ent->d_name[0] == '.') { continue; } snprintf(filename, PATH_MAX, "%s/%s", path, ent->d_name); _load_tti(&s->service, filename); } closedir(dir); } else if(fs.st_mode & S_IFREG) { /* Path is a single file */ _load_tti(&s->service, path); } else { fprintf(stderr, "%s: Not a file or directory\n", path); } return(VID_OK); } void tt_free(tt_t *s) { if(s == NULL) return; if(s->raw && s->raw != stdin) { fclose(s->raw); } else { _free_service(&s->service); } free(s->lut); memset(s, 0, sizeof(tt_t)); } int tt_next_packet(tt_t *s, uint8_t vbi[45]) { int r; /* Update the timecode */ s->timecode = (s->vid->frame - 1) * s->vid->conf.lines; s->timecode += s->vid->line - 1; /* Fetch the next line, or TT_NO_PACKET */ if(s->raw) { if(feof(s->raw)) { /* Return to the start of the file when we hit the end */ fseek(s->raw, 0, SEEK_SET); } /* Synchronization sequence (Clock run-in and framing code) */ vbi[0] = 0x55; vbi[1] = 0x55; vbi[2] = 0x27; r = fread(&vbi[3], 1, 42, s->raw); r = r == 42 ? TT_OK : TT_NO_PACKET; } else { r = _next_packet(&s->service, vbi, s->timecode); } return(r); } void tt_render_line(tt_t *s) { uint8_t vbi[45]; int r; /* Use 16 lines per field for teletext */ if((s->vid->line >= 7 && s->vid->line <= 22) || (s->vid->line >= 320 && s->vid->line <= 335)) { r = tt_next_packet(s, vbi); if(r == TT_OK) { vbidata_render_nrz(s->lut, vbi, -70, 360, VBIDATA_LSB_FIRST, s->vid->output, 2); } } } hacktv-master/teletext.h000066400000000000000000000076541357376541300156700ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #ifndef _TELETEXT_H #define _TELETEXT_H #include #include #include #include "video.h" #define TT_OK 0 #define TT_ERROR 1 #define TT_NO_PACKET 2 #define TT_OUT_OF_MEMORY 3 typedef struct _tt_page_t { /* The page number, 0x100 - 0x8FF */ uint16_t page; /* Subpage number: 0x00 - 0xFF */ uint8_t subpage; /* Subcode: 0x0000 - 0x3F7F */ uint16_t subcode; /* Page status 0x0000 - 0xFFFF */ uint16_t page_status; /* Cycle mode / time */ int cycle_mode; /* 0 = Seconds, 1 = Cycles */ int cycle_time; /* Seconds / cycles until next subpage */ int cycle_count; /* Cycle counter */ /* Fastext links */ int links[6]; /* Flag to transmit the erasure code, only done on a new subpage */ int erase; /* 0 = Don't erase, 1 = Erase */ /* The number of packets that make up the page, * not including the header */ int packets; /* The number of packets that can be transmitted * within 20ms of the header packet */ int nodelay_packets; /* Pointer to the packets that make up this * page. Each packet is 45 bytes long and * represents the full VBI line. */ uint8_t *data; /* A pointer to the first subpage */ struct _tt_page_t *subpages; /* A pointer to the next subpage */ struct _tt_page_t *next_subpage; /* A pointer to the next page */ struct _tt_page_t *next; } tt_page_t; typedef struct { /* The magazine number, 1-8 */ int magazine; /* Set to 1 if the next magazine packet has to * to be a header filler packet */ int filler; /* A pointer to the first page */ tt_page_t *pages; /* A pointer to the currently active page */ tt_page_t *page; /* The currently active row */ int row; /* Timecode to resume sending display packets */ int delay; } tt_magazine_t; typedef struct { /* The current timestamp to use for the clock */ time_t timestamp; /* The number of ticks that represent 20ms. This is * used to enforce a minimum time between header * packets and displayable packets of the same page. * Sometimes referred to as the 20ms rule, page * erasure interval or page clearing interval. */ unsigned int header_delay; /* The number of clock ticks that represent 1s. * This is used to determine when to send the next * 8/30 packet, containing the updated time */ unsigned int second_delay; /* The currently active magazine */ unsigned int magazine; /* The available magazines */ tt_magazine_t magazines[8]; } tt_service_t; typedef struct { vid_t *vid; int16_t *lut; FILE *raw; tt_service_t service; unsigned int timecode; } tt_t; extern int tt_init(tt_t *s, vid_t *vid, char *path); extern void tt_free(tt_t *s); extern int tt_next_packet(tt_t *s, uint8_t vbi[45]); extern void tt_render_line(tt_t *s); #endif hacktv-master/test.c000066400000000000000000000124421357376541300147730ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include "hacktv.h" /* A small 2-bit hacktv logo */ #define LOGO_WIDTH 48 #define LOGO_HEIGHT 9 #define LOGO_SCALE 4 static const char *_logo = " " " ## ## ## #### ## ## ###### ## ## " " ## ## #### ## ## ## ## ## ## ## " " ## ## ## ## ## #### ## ## ## " " ###### ###### ## ### ## ## ## " " ## ## ## ## ## #### ## ## ## " " ## ## ## ## ## ## ## ## ## #### " " ## ## ## ## #### ## ## ## ## " " "; /* AV test pattern source */ typedef struct { int width; int height; uint32_t *video; int16_t *audio; size_t audio_samples; } av_test_t; static uint32_t *_av_test_read_video(void *private, float *ratio) { av_test_t *av = private; if(ratio) *ratio = 4.0 / 3.0; return(av->video); } static int16_t *_av_test_read_audio(void *private, size_t *samples) { av_test_t *av = private; *samples = av->audio_samples; return(av->audio); } static int _av_test_close(void *private) { av_test_t *av = private; if(av->video) free(av->video); if(av->audio) free(av->audio); free(av); return(HACKTV_OK); } int av_test_open(vid_t *s) { uint32_t const bars[8] = { 0x000000, 0x0000BF, 0xBF0000, 0xBF00BF, 0x00BF00, 0x00BFBF, 0xBFBF00, 0xFFFFFF, }; av_test_t *av; int c, x, y; double d; int16_t l; av = calloc(1, sizeof(av_test_t)); if(!av) { return(HACKTV_OUT_OF_MEMORY); } /* Generate a basic test pattern */ av->width = s->active_width; av->height = s->conf.active_lines; av->video = malloc(vid_get_framebuffer_length(s)); if(!av->video) { free(av); return(HACKTV_OUT_OF_MEMORY); } for(y = 0; y < s->conf.active_lines; y++) { for(x = 0; x < s->active_width; x++) { if(y < s->conf.active_lines - 140) { /* 75% colour bars */ c = 7 - x * 8 / s->active_width; c = bars[c]; } else if(y < s->conf.active_lines - 120) { /* 75% red */ c = 0xBF0000; } else if(y < s->conf.active_lines - 100) { /* Gradient black to white */ c = x * 0xFF / (s->active_width - 1); c = c << 16 | c << 8 | c; } else { /* 8 level grey bars */ c = x * 0xFF / (s->active_width - 1); c &= 0xE0; c = c | (c >> 3) | (c >> 6); c = c << 16 | c << 8 | c; } av->video[y * s->active_width + x] = c; } } /* Overlay the logo */ if(s->active_width >= LOGO_WIDTH * LOGO_SCALE && s->conf.active_lines >= LOGO_HEIGHT * LOGO_SCALE) { x = s->active_width / 2; y = s->conf.active_lines / 10; for(x = 0; x < LOGO_WIDTH * LOGO_SCALE; x++) { for(y = 0; y < LOGO_HEIGHT * LOGO_SCALE; y++) { c = _logo[y / LOGO_SCALE * LOGO_WIDTH + x / LOGO_SCALE] == ' ' ? 0x000000 : 0xFFFFFF; av->video[(s->conf.active_lines / 10 + y) * s->active_width + ((s->active_width - LOGO_WIDTH * LOGO_SCALE) / 2) + x] = c; } } } /* Generate the 1khz test tones (BBC 1 style) */ d = 1000.0 * 2 * M_PI / HACKTV_AUDIO_SAMPLE_RATE; y = HACKTV_AUDIO_SAMPLE_RATE * 64 / 100; /* 640ms */ av->audio_samples = y * 10; /* 6.4 seconds */ av->audio = malloc(av->audio_samples * 2 * sizeof(int16_t)); if(!av->audio) { free(av); return(HACKTV_OUT_OF_MEMORY); } for(x = 0; x < av->audio_samples; x++) { l = sin(x * d) * INT16_MAX * 0.1; if(x < y) { /* 0 - 640ms, interrupt left channel */ av->audio[x * 2 + 0] = 0; av->audio[x * 2 + 1] = l; } else if(x >= y * 2 && x < y * 3) { /* 1280ms - 1920ms, interrupt right channel */ av->audio[x * 2 + 0] = l; av->audio[x * 2 + 1] = 0; } else if(x >= y * 4 && x < y * 5) { /* 2560ms - 3200ms, interrupt right channel again */ av->audio[x * 2 + 0] = l; av->audio[x * 2 + 1] = 0; } else { /* Use both channels for all other times */ av->audio[x * 2 + 0] = l; /* Left */ av->audio[x * 2 + 1] = l; /* Right */ } } /* Register the callback functions */ s->av_private = av; s->av_read_video = _av_test_read_video; s->av_read_audio = _av_test_read_audio; s->av_close = _av_test_close; return(HACKTV_OK); } hacktv-master/test.h000066400000000000000000000024161357376541300150000ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _TEST_H #define _TEST_H extern int av_test_open(vid_t *s); #endif hacktv-master/vbidata.c000066400000000000000000000066451357376541300154360ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include "vbidata.h" static double _sinc(double x) { return(sin(M_PI * x) / (M_PI * x)); } static double _raised_cosine(double x, double b, double t) { if(x == 0) return(1.0); return(_sinc(x / t) * (cos(M_PI * b * x / t) / (1.0 - (4.0 * b * b * x * x / (t * t))))); } static size_t _vbidata_init(int16_t *lut, unsigned int swidth, unsigned int dwidth, int16_t level, int filter, double beta) { size_t l; int b, x, lb; l = 0; for(lb = b = 0; b < swidth; b++) { double tt = (1.0 / swidth) * (0.5 + b); for(x = 0; x < dwidth; x++) { double tv = (1.0 / dwidth) * (0.5 + x); double tr = (tv - tt) * swidth; double h = _raised_cosine(tr, beta, 1); int16_t w = (int16_t) round(h * level); if(w != 0) { if(lb != b) { if(lut) { *(lut++) = b; *(lut++) = 0; } l += sizeof(int16_t) * 2; lb = b; } if(lut) { *(lut++) = x; *(lut++) = w; } l += sizeof(int16_t) * 2; } } l += sizeof(int16_t) * 2; } if(lut) { *(lut++) = 0; *(lut++) = 0; } l += sizeof(int16_t) * 2; return(l); } int16_t *vbidata_init(unsigned int swidth, unsigned int dwidth, int16_t level, int filter, double beta) { size_t l = _vbidata_init(NULL, swidth, dwidth, level, filter, beta); int16_t *s; /* Calculate the length of the lookup-table and allocate memory */ l = _vbidata_init(NULL, swidth, dwidth, level, filter, beta); s = malloc(l); if(!s) { return(NULL); } /* Generate the lookup-table and return */ _vbidata_init(s, swidth, dwidth, level, filter, beta); return(s); } void vbidata_render_nrz(const int16_t *lut, const uint8_t *src, int offset, size_t length, int order, int16_t *dst, size_t step) { int b = offset; int x = 0; int bit; bit = (b < 0 || b >= length ? 0 : (src[b >> 3] >> (order == VBIDATA_LSB_FIRST ? (b & 7) : 7 - (b & 7))) & 1); for(; !(lut[0] == 0 && lut[1] == 0); lut += 2) { if(lut[1] == 0) { b = lut[0] + offset; bit = (b < 0 || b >= length ? 0 : (src[b >> 3] >> (order == VBIDATA_LSB_FIRST ? (b & 7) : 7 - (b & 7))) & 1); } else if(bit) { x = lut[0]; dst[x * step] += lut[1]; } } } hacktv-master/vbidata.h000066400000000000000000000031121357376541300154250ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #ifndef _VBIDATA_H #define _VBIDATA_H #define VBIDATA_FILTER_RC (0) #define VBIDATA_LSB_FIRST (0) #define VBIDATA_MSB_FIRST (1) extern int16_t *vbidata_init(unsigned int swidth, unsigned int dwidth, int16_t level, int filter, double beta); extern void vbidata_render_nrz(const int16_t *lut, const uint8_t *src, int offset, size_t length, int order, int16_t *dst, size_t step); #endif hacktv-master/video.c000066400000000000000000002061541357376541300151270ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #include #include #include #include #include "video.h" #include "nicam728.h" #include "hacktv.h" /* * Video generation * * The output from this encoder is a 16-bit IQ signal which * hopefully contains an accurate video and audio signal for * display on old analogue TV sets. * * The encoder makes liberal use of lookup tables: * * - 3x for RGB > gamma corrected Y, I and Q levels. * * - A temporary gamma table used while generating the above. * * - PAL colour carrier (4 full frames in length + 1 line) or * NTSC colour carrier (2 full lines + 1 line). */ const vid_config_t vid_config_pal_i = { /* System I (PAL) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 5500000, /* Hz */ .vsb_lower_bw = 1250000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.71, /* Power level of video */ .fm_audio_level = 0.22, /* FM audio carrier power level */ .nicam_level = 0.07 / 2, /* NICAM audio carrier power level */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 0.20, .black_level = 0.76, .blanking_level = 0.76, .sync_level = 1.00, .colour_mode = VID_PAL, .burst_width = 0.00000225, /* 2.25 ±0.23µs */ .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .burst_level = 3.0 / 7.0, /* 3 / 7 of white - blanking level */ .colour_carrier = 4433618.75, .colour_lookup_lines = 625 * 4, /* The carrier repeats after 4 frames */ .gamma = 1.4, /* 2.8 in spec? too bright */ .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.877, .qu_co = 0.493, .qv_co = 0.000, .fm_mono_carrier = 6000000 - 400, /* Hz */ .fm_audio_preemph = 0.000050, /* Seconds */ .fm_audio_deviation = 50000, /* +/- Hz */ .nicam_carrier = 6552000, /* Hz */ .nicam_beta = 1.0, }; const vid_config_t vid_config_pal_bg = { /* System B/G (PAL) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 5000000, /* Hz */ .vsb_lower_bw = 750000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.71, /* Power level of video */ .fm_audio_level = 0.22, /* FM audio carrier power level */ .nicam_level = 0.07 / 2, /* NICAM audio carrier power level */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 0.20, .black_level = 0.76, .blanking_level = 0.76, .sync_level = 1.00, .colour_mode = VID_PAL, .burst_width = 0.00000225, /* 2.25 ±0.23µs */ .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .burst_level = 3.0 / 7.0, /* 3 / 7 of white - blanking level */ .colour_carrier = 4433618.75, .colour_lookup_lines = 625 * 4, /* The carrier repeats after 4 frames */ .gamma = 1.4, /* 2.8 in spec? too bright */ .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.877, .qu_co = 0.493, .qv_co = 0.000, .fm_mono_carrier = 5500000, /* Hz */ .fm_audio_preemph = 0.000050, /* Seconds */ .fm_audio_deviation = 50000, /* +/- Hz */ .nicam_carrier = 5850000, /* Hz */ .nicam_beta = 0.4, }; const vid_config_t vid_config_pal_fm = { /* PAL FM (satellite) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_FM, .fm_level = 1.0, .fm_deviation = 10000000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 1.00, /* Power level of video */ .fm_audio_level = 0.05, /* FM audio carrier power level */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 0.50, .black_level = -0.20, .blanking_level = -0.20, .sync_level = -0.50, .colour_mode = VID_PAL, .burst_width = 0.00000225, /* 2.25 ±0.23µs */ .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .burst_level = 3.0 / 7.0, /* 3 / 7 of white - blanking level */ .colour_carrier = 4433618.75, .colour_lookup_lines = 625 * 4, /* The carrier repeats after 4 frames */ .gamma = 1.4, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.877, .qu_co = 0.493, .qv_co = 0.000, .fm_mono_carrier = 6500000, /* Hz */ //.fm_left_carrier = 7200000, /* Hz */ //.fm_right_carrier = 7020000, /* Hz */ .fm_audio_preemph = 0.000050, /* Seconds */ .fm_audio_deviation = 85000, /* +/- Hz */ }; const vid_config_t vid_config_pal = { /* Composite PAL */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 0.70, .black_level = 0.00, .blanking_level = 0.00, .sync_level = -0.30, .colour_mode = VID_PAL, .burst_width = 0.00000225, /* 2.25 ±0.23µs */ .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .burst_level = 3.0 / 7.0, /* 3 / 7 of white - blanking level */ .colour_carrier = 4433618.75, .colour_lookup_lines = 625 * 4, /* The carrier repeats after 4 frames */ .gamma = 1.4, /* 2.8 in spec? too bright */ .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.877, .qu_co = 0.493, .qv_co = 0.000, }; const vid_config_t vid_config_secam_l = { /* System L (SECAM) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 6000000, /* Hz */ .vsb_lower_bw = 1250000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.80, /* Power level of video */ .am_audio_level = 0.10, /* AM audio carrier power level */ .nicam_level = 0.04, /* NICAM audio carrier power level */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 1.00, .black_level = 0.30, .blanking_level = 0.30, .sync_level = 0.00, .colour_mode = VID_SECAM, .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 1.000, .iv_co = 0.000, .qu_co = 0.000, .qv_co = -1.000, .am_mono_carrier = 6500000, /* Hz */ .nicam_carrier = 5850000, /* Hz */ .nicam_beta = 0.4, }; const vid_config_t vid_config_secam = { /* Composite SECAM */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_625, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_width = 0.00005195, /* 51.95µs */ .active_left = 0.00001040, /* |-->| 10.40µs */ .hsync_width = 0.00000470, /* 4.70 ±0.20µs */ .vsync_short_width = 0.00000235, /* 2.35 ±0.10µs */ .vsync_long_width = 0.00002730, /* 2.73 ±0.20µs */ .white_level = 0.70, .black_level = 0.00, .blanking_level = 0.00, .sync_level = -0.30, .colour_mode = VID_SECAM, .burst_left = 0.00000560, /* |-->| 5.6 ±0.1µs */ .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 1.000, .iv_co = 0.000, .qu_co = 0.000, .qv_co = -1.000, }; const vid_config_t vid_config_ntsc_m = { /* System M (NTSC) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 4200000, /* Hz */ .vsb_lower_bw = 750000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.83, /* Power level of video */ .fm_audio_level = 0.17, /* FM audio carrier power level */ .type = VID_RASTER_525, .frame_rate_num = 30000, .frame_rate_den = 1001, .lines = 525, .active_lines = 480, .active_width = 0.00005290, /* 52.90µs */ .active_left = 0.00000920, /* |-->| 9.20µs */ .hsync_width = 0.00000470, /* 4.70 ±1.00µs */ .vsync_short_width = 0.00000230, /* 2.30 ±0.10µs */ .vsync_long_width = 0.00002710, /* 27.10 µs */ .white_level = 0.2000, .black_level = 0.7280, .blanking_level = 0.7712, .sync_level = 1.0000, .colour_mode = VID_NTSC, .burst_width = 0.00000250, /* 2.5 ±0.28µs */ .burst_left = 0.00000530, /* |-->| 5.3 ±0.1µs */ .burst_level = 4.0 / 10.0, /* 4/10 of white - blanking level */ .colour_carrier = 5000000.0 * 63 / 88, .colour_lookup_lines = 2, /* The carrier repeats after 2 lines */ .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = -0.177, .iv_co = 0.768, .qu_co = 0.626, .qv_co = -0.082, .fm_mono_carrier = 4500000, /* Hz */ .fm_audio_preemph = 0.000075, /* Seconds */ .fm_audio_deviation = 25000, /* +/- Hz */ }; const vid_config_t vid_config_ntsc = { /* Composite NTSC */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_525, .frame_rate_num = 30000, .frame_rate_den = 1001, .lines = 525, .active_lines = 480, .active_width = 0.00005290, /* 52.90µs */ .active_left = 0.00000920, /* |-->| 9.20µs */ .hsync_width = 0.00000470, /* 4.70 ±1.00µs */ .vsync_short_width = 0.00000230, /* 2.30 ±0.10µs */ .vsync_long_width = 0.00002710, /* 27.10 µs */ .white_level = 0.70, .black_level = 0.0525, .blanking_level = 0.00, .sync_level = -0.30, .colour_mode = VID_NTSC, .burst_width = 0.00000250, /* 2.5 ±0.28µs */ .burst_left = 0.00000530, /* |-->| 5.3 ±0.1µs */ .burst_level = 4.0 / 10.0, /* 4/10 of white - blanking level */ .colour_carrier = 5000000.0 * 63 / 88, .colour_lookup_lines = 2, /* The carrier repeats after 2 lines */ .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = -0.177, .iv_co = 0.768, .qu_co = 0.626, .qv_co = -0.082, }; const vid_config_t vid_config_d2mac_am = { /* D2-MAC AM */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_AM, .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 0.000028938, .active_width = 0.000034667, .level = 1.00, /* Overall signal level */ .video_level = 0.85, /* Chrominance may clip if this is set to 1 */ .white_level = 0.10, .black_level = 1.00, .blanking_level = 0.55, .sync_level = 0.55, .mac_mode = MAC_MODE_D2, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_d2mac_fm = { /* D2-MAC FM (Satellite) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_FM, .fm_level = 1.0, .fm_deviation = 10000000, /* Hz */ .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 0.000028938, .active_width = 0.000034667, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .white_level = 0.50, .black_level = -0.50, .blanking_level = 0.00, .sync_level = 0.00, .mac_mode = MAC_MODE_D2, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_d2mac = { /* D2-MAC */ .output_type = HACKTV_INT16_REAL, .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 0.000028938, .active_width = 0.000034667, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .white_level = 0.50, .black_level = -0.50, .blanking_level = 0.00, .sync_level = 0.00, .mac_mode = MAC_MODE_D2, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_dmac_am = { /* D-MAC AM */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_AM, .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 0.000028938, .active_width = 0.000034667, .level = 1.00, /* Overall signal level */ .video_level = 0.85, /* Chrominance may clip if this is set to 1 */ .white_level = 0.10, .black_level = 1.00, .blanking_level = 0.55, .sync_level = 0.55, .mac_mode = MAC_MODE_D, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_dmac_fm = { /* D2-MAC FM (Satellite) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_FM, .fm_level = 1.0, .fm_deviation = 10000000, /* Hz */ .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 584.0 / MAC_CLOCK_RATE, // 0.000028938, .active_width = 702.0 / MAC_CLOCK_RATE, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .white_level = 0.50, .black_level = -0.50, .blanking_level = 0.00, .sync_level = 0.00, .mac_mode = MAC_MODE_D, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_dmac = { /* D-MAC */ .output_type = HACKTV_INT16_REAL, .type = VID_MAC, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 625, .active_lines = 576, .active_left = 0.000028938, .active_width = 0.000034667, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .white_level = 0.50, .black_level = -0.50, .blanking_level = 0.00, .sync_level = 0.00, .mac_mode = MAC_MODE_D, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .iu_co = 0.000, .iv_co = 0.927, .qu_co = 0.733, .qv_co = 0.000, }; const vid_config_t vid_config_819_e = { /* System E (819 line monochrome, French variant) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 2000000, /* Hz */ .vsb_lower_bw = 10400000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.8, /* Power level of video */ .am_audio_level = 0.2, /* Power level of audio */ .type = VID_RASTER_819, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 819, .active_lines = 720, /* Normally 738 */ .active_width = 0.00003944, /* 39.44µs */ .active_left = 0.00000890, /* |-->| 8.9µs */ .hsync_width = 0.00000250, /* 2.50 ±0.10µs */ .vsync_long_width = 0.00002000, /* 20.0 ±1.00µs */ .white_level = 1.00, .black_level = 0.35, .blanking_level = 0.30, .sync_level = 0.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ /* AM modulated */ .am_mono_carrier = 11500000, /* Hz */ .am_mono_bandwidth = 10000, /* Hz */ }; const vid_config_t vid_config_819 = { /* 819 line video, French variant */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_819, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 819, .active_lines = 720, /* Normally 738 */ .active_width = 0.00003944, /* 39.44µs */ .active_left = 0.00000890, /* |-->| 8.9µs */ .hsync_width = 0.00000250, /* 2.50 ±0.10µs */ .vsync_long_width = 0.00002000, /* 20.0 ±1.00µs */ .white_level = 0.70, .black_level = 0.05, .blanking_level = 0.00, .sync_level = -0.30, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_405_a = { /* System A (405 line monochrome) */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_VSB, .vsb_upper_bw = 750000, /* Hz */ .vsb_lower_bw = 3000000, /* Hz */ .level = 1.0, /* Overall signal level */ .video_level = 0.8, /* Power level of video */ .am_audio_level = 0.2, /* Power level of audio */ .type = VID_RASTER_405, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 405, .active_lines = 376, .active_width = 0.00008030, /* 80.3µs */ .active_left = 0.00001680, /* |-->| 16.8µs */ .hsync_width = 0.00000900, /* 9.00 ±1.00µs */ .vsync_long_width = 0.00004000, /* 40.0 ±2.00µs */ .white_level = 1.00, .black_level = 0.30, .blanking_level = 0.30, .sync_level = 0.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ /* AM modulated */ .am_mono_carrier = -3500000, /* Hz */ .am_mono_bandwidth = 10000, /* Hz */ }; const vid_config_t vid_config_405 = { /* 405 line video */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_405, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 405, .active_lines = 376, .active_width = 0.00008030, /* 80.3µs */ .active_left = 0.00001680, /* |-->| 16.8µs */ .hsync_width = 0.00000900, /* 9.00 ±1.00µs */ .vsync_long_width = 0.00004000, /* 40.0 ±2.00µs */ .white_level = 0.70, .black_level = 0.00, .blanking_level = 0.00, .sync_level = -0.30, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_baird_240_am = { /* Baird 240 line, AM modulation */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_AM, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_BAIRD_240, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 240, .active_lines = 220, .active_width = 0.00015, /* 150µs */ .active_left = 0.000016667, /* |-->| 16.667µs */ .hsync_width = 0.000013333, /* 13.333µs */ .vsync_long_width = 0.000166667, /* 166.667µs */ .white_level = 1.00, .black_level = 0.40, .blanking_level = 0.40, .sync_level = 0.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_baird_240 = { /* Baird 240 line */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_BAIRD_240, .frame_rate_num = 25, .frame_rate_den = 1, .lines = 240, .active_lines = 220, .active_width = 0.00015, /* 150µs */ .active_left = 0.000016667, /* |-->| 16.667µs */ .hsync_width = 0.000013333, /* 13.333µs */ .vsync_long_width = 0.000166667, /* 166.667µs */ .white_level = 1.00, .black_level = 0.40, .blanking_level = 0.40, .sync_level = 0.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_baird_30_am = { /* Baird 30 line, AM modulation */ .output_type = HACKTV_INT16_COMPLEX, .modulation = VID_AM, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_BAIRD_30, .frame_rate_num = 25, .frame_rate_den = 2, .lines = 30, .active_lines = 30, .active_width = 0.002666667, /* 2.667ms */ .active_left = 0, .white_level = 1.00, .black_level = 0.00, .blanking_level = 0.00, .sync_level = 0.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_baird_30 = { /* Baird 30 line */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_BAIRD_30, .frame_rate_num = 25, .frame_rate_den = 2, .lines = 30, .active_lines = 30, .active_width = 0.002666667, /* 2.667ms */ .active_left = 0, .white_level = 1.00, .black_level = -1.00, .blanking_level = -1.00, .sync_level = -1.00, .gamma = 1.2, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_apollo_colour_fm = { /* Unified S-Band, Apollo Colour Lunar Television */ .output_type = HACKTV_INT16_COMPLEX, .level = 1.000, /* Overall signal level */ .video_level = 1.000, /* Power level of video */ .fm_audio_level = 0.150, /* Power level of audio */ .modulation = VID_FM, .fm_level = 1.0, .fm_deviation = 1000000, /* Hz */ .type = VID_RASTER_525, .frame_rate_num = 30000, .frame_rate_den = 1001, .lines = 525, .active_lines = 480, .active_width = 0.00005290, /* 52.90µs */ .active_left = 0.00000920, /* |-->| 9.20µs */ .hsync_width = 0.00000470, /* 4.70 ±1.00µs */ .vsync_short_width = 0.00000230, /* 2.30 ±0.10µs */ .vsync_long_width = 0.00002710, /* 27.10 µs */ .white_level = 0.5000, .black_level = -0.1475, .blanking_level = -0.2000, .sync_level = -0.5000, .colour_mode = VID_APOLLO_FSC, .fsc_flag_width = 0.00002000, /* 20.00µs */ .fsc_flag_left = 0.00001470, /* |-->| 14.70µs */ .fsc_flag_level = 1.00, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ /* The audio carrier overlaps the video signal and * requires the video to either be low pass filtered * to 750kHz (Apollo 10 to 14) or cancelled out * in post-processing (Apollo 15-17). */ .fm_mono_carrier = 1250000, /* Hz */ .fm_audio_deviation = 25000, /* +/- Hz */ }; const vid_config_t vid_config_apollo_colour = { /* Apollo Colour Lunar Television */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_RASTER_525, .frame_rate_num = 30000, .frame_rate_den = 1001, .lines = 525, .active_lines = 480, .active_width = 0.00005290, /* 52.90µs */ .active_left = 0.00000920, /* |-->| 9.20µs */ .hsync_width = 0.00000470, /* 4.70 ±1.00µs */ .vsync_short_width = 0.00000230, /* 2.30 ±0.10µs */ .vsync_long_width = 0.00002710, /* 27.10 µs */ .white_level = 0.70, .black_level = 0.0525, .blanking_level = 0.00, .sync_level = -0.30, .colour_mode = VID_APOLLO_FSC, .fsc_flag_width = 0.00002000, /* 20.00µs */ .fsc_flag_left = 0.00001470, /* |-->| 14.70µs */ .fsc_flag_level = 1.00, .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_config_t vid_config_apollo_mono_fm = { /* Unified S-Band, Apollo Lunar Television 10 fps video (Mode 1) */ .output_type = HACKTV_INT16_COMPLEX, .level = 1.000, /* Overall signal level */ .video_level = 1.000, /* Power level of video */ .fm_audio_level = 0.150, /* Power level of audio */ .modulation = VID_FM, .fm_level = 1.0, .fm_deviation = 1000000, /* Hz */ .type = VID_APOLLO_320, .frame_rate_num = 10, .frame_rate_den = 1, .lines = 320, .active_lines = 312, .active_width = 0.00028250, /* 282.5µs */ .active_left = 0.00002500, /* |-->| 25.0µs */ .hsync_width = 0.00002000, /* 20.00µs */ .vsync_long_width = 0.00026750, /* 267.5µs */ /* Hacky: hacktv can't generate a single pulse wider than half a line, * which we need here. Use the vsync short pulse to complete the long */ .vsync_short_width = 1.0 / 10.0 / 320.0 / 2.0 - 45e-6, /* The Apollo TV camera supports a pulse and tone sync mode. The * pulse mode is a normal negative pulse, and the tone mode uses * a 409600 Hz tone. I'm not sure which was used for the live * transmissions from the surface. This implementation uses the * negative pulse mode. */ .white_level = 0.50, .black_level = -0.20, .blanking_level = -0.20, .sync_level = -0.50, /* These are copied from the NTSC values */ .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ .fm_mono_carrier = 1250000, /* Hz */ .fm_audio_deviation = 25000, /* +/- Hz */ }; const vid_config_t vid_config_apollo_mono = { /* Apollo Lunar Television 10 fps video (Mode 1) */ .output_type = HACKTV_INT16_REAL, .level = 1.0, /* Overall signal level */ .video_level = 1.0, /* Power level of video */ .type = VID_APOLLO_320, .frame_rate_num = 10, .frame_rate_den = 1, .lines = 320, .active_lines = 312, .active_width = 0.00028250, /* 282.5µs */ .active_left = 0.00002500, /* |-->| 25.0µs */ .hsync_width = 0.00002000, /* 20.00µs */ .vsync_long_width = 0.00026750, /* 267.5µs */ /* Hacky: hacktv can't generate a single pulse wider than half a line, * which we need here. Use the vsync short pulse to complete the long */ .vsync_short_width = 1.0 / 10.0 / 320.0 / 2.0 - 45e-6, /* The Apollo TV camera supports a pulse and tone sync mode. The * pulse mode is a normal negative pulse, and the tone mode uses * a 409600 Hz tone. I'm not sure which was used for the live * transmissions from the surface. This implementation uses the * negative pulse mode. */ .white_level = 0.70, .black_level = 0.00, .blanking_level = 0.00, .sync_level = -0.30, /* These are copied from the NTSC values */ .gamma = 1.0, .rw_co = 0.299, /* R weight */ .gw_co = 0.587, /* G weight */ .bw_co = 0.114, /* B weight */ }; const vid_configs_t vid_configs[] = { { "i", &vid_config_pal_i }, { "b", &vid_config_pal_bg }, { "g", &vid_config_pal_bg }, { "pal-fm", &vid_config_pal_fm }, { "pal", &vid_config_pal }, { "l", &vid_config_secam_l }, { "secam", &vid_config_secam }, { "m", &vid_config_ntsc_m }, { "ntsc", &vid_config_ntsc }, { "d2mac-am", &vid_config_d2mac_am }, { "d2mac-fm", &vid_config_d2mac_fm }, { "d2mac", &vid_config_d2mac }, { "dmac-am", &vid_config_dmac_am }, { "dmac-fm", &vid_config_dmac_fm }, { "dmac", &vid_config_dmac }, { "e", &vid_config_819_e }, { "819", &vid_config_819 }, { "a", &vid_config_405_a }, { "405", &vid_config_405 }, { "240-am", &vid_config_baird_240_am }, { "240", &vid_config_baird_240 }, { "30-am", &vid_config_baird_30_am }, { "30", &vid_config_baird_30 }, { "apollo-fsc-fm", &vid_config_apollo_colour_fm }, { "apollo-fsc", &vid_config_apollo_colour }, { "apollo-fm", &vid_config_apollo_mono_fm }, { "apollo", &vid_config_apollo_mono }, { NULL, NULL }, }; /* Test taps for a CCIR-405 video pre-emphasis filter at 16 MHz */ #define FM_TAPS 47 const int16_t fm_taps[FM_TAPS] = { -1,-1,-2,-2,-4,-5,-8,-12,-18,-26,-39,-56,-82,-117,-172,-247,-363,-526,-776,-1131,-1682,-2457,-3711,32767,-3711,-2457,-1682,-1131,-776,-526,-363,-247,-172,-117,-82,-56,-39,-26,-18,-12,-8,-5,-4,-2,-2,-1,-1 }; /* Test taps for D/D2-MAC pre-emphasis at 20.25 MHz */ const int16_t fm_mac_taps[FM_TAPS] = { -1,0,-1,0,-1,-1,-2,-2,-4,-5,-9,-13,-22,-33,-55,-86,-141,-222,-360,-567,-919,-1442,-2361,32767,-2361,-1442,-919,-567,-360,-222,-141,-86,-55,-33,-22,-13,-9,-5,-4,-2,-2,-1,-1,0,-1,0,-1 }; static double _dlimit(double v, double min, double max) { if(v < min) return(min); if(v > max) return(max); return(v); } static int16_t *_colour_subcarrier_phase(vid_t *s, int phase) { int frame = (s->frame - 1) & 3; int line = s->line - 1; int p; /* Limit phase offset to 0 > 359 */ if((phase %= 360) < 0) { phase += 360; } /* Find the position in the table for 0 degrees at the start of this line */ p = (s->conf.lines * frame + line) * s->width; /* And apply the offset for the required phase */ if(phase == 0) { p += 0; } else if(phase == 45) { p += s->colour_lookup_width * 3 / 8; } else if(phase == 90) { p += s->colour_lookup_width * 6 / 8; } else if(phase == 135) { p += s->colour_lookup_width * 1 / 8; } else if(phase == 180) { p += s->colour_lookup_width * 4 / 8; } else if(phase == 225) { p += s->colour_lookup_width * 7 / 8; } else if(phase == 270) { p += s->colour_lookup_width * 2 / 8; } else if(phase == 315) { p += s->colour_lookup_width * 5 / 8; } /* Keep the position within the buffer */ p %= s->colour_lookup_width; /* Return a pointer to the line */ return(&s->colour_lookup[p]); } /* FM modulator */ static int _init_fm_modulator(_mod_fm_t *fm, int sample_rate, double frequency, double deviation, double level) { int r; double d; fm->level = round(INT16_MAX * level); fm->counter = INT16_MAX; fm->phase.i = INT16_MAX; fm->phase.q = 0; fm->lut = malloc(sizeof(cint32_t) * (UINT16_MAX + 1)); if(!fm->lut) { return(VID_OUT_OF_MEMORY); } for(r = INT16_MIN; r <= INT16_MAX; r++) { d = 2.0 * M_PI / sample_rate * (frequency + (double) r / INT16_MAX * deviation); fm->lut[r - INT16_MIN].i = lround(cos(d) * INT32_MAX); fm->lut[r - INT16_MIN].q = lround(sin(d) * INT32_MAX); } return(VID_OK); } static void inline _fm_modulator_add(_mod_fm_t *fm, int16_t *dst, int16_t sample) { cint32_mul(&fm->phase, &fm->phase, &fm->lut[sample - INT16_MIN]); dst[0] += ((fm->phase.i >> 16) * fm->level) >> 15; dst[1] += ((fm->phase.q >> 16) * fm->level) >> 15; /* Correct the amplitude after INT16_MAX samples */ if(--fm->counter == 0) { double ra = atan2(fm->phase.q, fm->phase.i); fm->phase.i = lround(cos(ra) * INT32_MAX); fm->phase.q = lround(sin(ra) * INT32_MAX); fm->counter = INT16_MAX; } } static void inline _fm_modulator(_mod_fm_t *fm, int16_t *dst, int16_t sample) { cint32_mul(&fm->phase, &fm->phase, &fm->lut[sample - INT16_MIN]); dst[0] = ((fm->phase.i >> 16) * fm->level) >> 15; dst[1] = ((fm->phase.q >> 16) * fm->level) >> 15; /* Correct the amplitude after INT16_MAX samples */ if(--fm->counter == 0) { double ra = atan2(fm->phase.q, fm->phase.i); fm->phase.i = lround(cos(ra) * INT32_MAX); fm->phase.q = lround(sin(ra) * INT32_MAX); fm->counter = INT16_MAX; } } static void _free_fm_modulator(_mod_fm_t *fm) { free(fm->lut); } /* AM modulator */ static int _init_am_modulator(_mod_am_t *am, int sample_rate, double frequency, double level) { double d; am->level = round(INT16_MAX * level); am->counter = INT16_MAX; am->phase.i = INT16_MAX; am->phase.q = 0; d = 2.0 * M_PI / sample_rate * frequency; am->delta.i = lround(cos(d) * INT32_MAX); am->delta.q = lround(sin(d) * INT32_MAX); return(VID_OK); } static void inline _am_modulator_add(_mod_am_t *am, int16_t *dst, int16_t sample) { cint32_mul(&am->phase, &am->phase, &am->delta); sample = ((int32_t) sample + INT16_MIN) / 2; dst[0] += ((((am->phase.i >> 16) * sample) >> 16) * am->level) >> 15; dst[1] += ((((am->phase.q >> 16) * sample) >> 16) * am->level) >> 15; /* Correct the amplitude after INT16_MAX samples */ if(--am->counter == 0) { double ra = atan2(am->phase.q, am->phase.i); am->phase.i = lround(cos(ra) * INT32_MAX); am->phase.q = lround(sin(ra) * INT32_MAX); am->counter = INT16_MAX; } } static void _free_am_modulator(_mod_am_t *am) { /* Nothing */ } /* AV source callback handlers */ static uint32_t *_av_read_video(vid_t *s, float *ratio) { if(s->av_read_video) { return(s->av_read_video(s->av_private, ratio)); } return(NULL); } static int16_t *_av_read_audio(vid_t *s, size_t *samples) { if(s->av_read_audio) { return(s->av_read_audio(s->av_private, samples)); } return(NULL); } static int _av_eof(vid_t *s) { if(s->av_eof) { return(s->av_eof(s->av_private)); } return(0); } int vid_av_close(vid_t *s) { int r; r = s->av_close ? s->av_close(s->av_private) : VID_ERROR; s->av_private = NULL; s->av_read_video = NULL; s->av_read_audio = NULL; s->av_eof = NULL; s->av_close = NULL; return(r); } int vid_init(vid_t *s, unsigned int sample_rate, const vid_config_t * const conf) { int r, x; int64_t c; double d; double glut[0x100]; double level, slevel; memset(s, 0, sizeof(vid_t)); memcpy(&s->conf, conf, sizeof(vid_config_t)); /* Calculate the number of samples per line */ s->width = round((double) sample_rate / ((double) s->conf.frame_rate_num / s->conf.frame_rate_den) / s->conf.lines); s->half_width = round((double) sample_rate / ((double) s->conf.frame_rate_num / s->conf.frame_rate_den) / s->conf.lines / 2); /* Calculate the "actual" sample rate we use. This is calculated * to give us an exact number of samples per line */ //s->sample_rate = s->width * s->conf.lines * s->conf.frame_rate; // This won't work with NTSC //if(s->sample_rate != sample_rate) //{ // fprintf(stderr, "Sample rate error %0.2f%%\n", (double) s->sample_rate / sample_rate * 100); //} s->sample_rate = sample_rate; /* Calculate the active video width and offset */ s->active_left = round(s->sample_rate * s->conf.active_left); s->active_width = ceil(s->sample_rate * s->conf.active_width); if(s->active_width > s->width) s->active_width = s->width; s->hsync_width = round(s->sample_rate * s->conf.hsync_width); s->vsync_short_width = round(s->sample_rate * s->conf.vsync_short_width); s->vsync_long_width = round(s->sample_rate * s->conf.vsync_long_width); /* Calculate signal levels */ /* slevel is the the sub-carrier level. When FM modulating * this is always 1.0, otherwise it equals the overall level */ slevel = s->conf.modulation == VID_FM ? 1.0 : s->conf.level; level = s->conf.video_level * slevel; /* Calculate 16-bit blank and sync levels */ s->white_level = round(s->conf.white_level * level * INT16_MAX); s->black_level = round(s->conf.black_level * level * INT16_MAX); s->blanking_level = round(s->conf.blanking_level * level * INT16_MAX); s->sync_level = round(s->conf.sync_level * level * INT16_MAX); /* Allocate memory for YUV lookup tables */ s->y_level_lookup = malloc(0x1000000 * sizeof(int16_t)); s->i_level_lookup = malloc(0x1000000 * sizeof(int16_t)); s->q_level_lookup = malloc(0x1000000 * sizeof(int16_t)); if(s->y_level_lookup == NULL || s->i_level_lookup == NULL || s->q_level_lookup == NULL) { vid_free(s); return(VID_OUT_OF_MEMORY); } /* Generate the gamma lookup table. LUTception */ for(c = 0; c < 0x100; c++) { glut[c] = pow((double) c / 255, 1 / s->conf.gamma); } /* Generate the RGB > signal level lookup tables */ for(c = 0x000000; c <= 0xFFFFFF; c++) { double r, g, b; double y, u, v; double i, q; /* Calculate RGB 0..1 values */ r = glut[(c & 0xFF0000) >> 16]; g = glut[(c & 0x00FF00) >> 8]; b = glut[(c & 0x0000FF) >> 0]; /* Calculate Y, Cb and Cr values */ y = r * s->conf.rw_co + g * s->conf.gw_co + b * s->conf.bw_co; u = (b - y); v = (r - y); i = s->conf.iv_co * v + s->conf.iu_co * u; q = s->conf.qv_co * v + s->conf.qu_co * u; /* Adjust values to correct signal level */ y = s->conf.black_level + (y * (s->conf.white_level - s->conf.black_level)); i *= s->conf.white_level - s->conf.black_level; q *= s->conf.white_level - s->conf.black_level; /* Convert to INT16 range and store in tables */ s->y_level_lookup[c] = round(_dlimit(y * level, -1, 1) * INT16_MAX); s->i_level_lookup[c] = round(_dlimit(i * level, -1, 1) * INT16_MAX); s->q_level_lookup[c] = round(_dlimit(q * level, -1, 1) * INT16_MAX); } if(s->conf.colour_lookup_lines > 0) { /* Generate the colour subcarrier lookup table */ /* This carrier is in phase with the U (B-Y) component */ s->colour_lookup_width = s->width * s->conf.colour_lookup_lines; d = 2.0 * M_PI * s->conf.colour_carrier / s->sample_rate; s->colour_lookup = malloc((s->colour_lookup_width + s->width) * sizeof(int16_t)); if(!s->colour_lookup) { vid_free(s); return(VID_OUT_OF_MEMORY); } for(c = 0; c < s->colour_lookup_width; c++) { s->colour_lookup[c] = round(-sin(d * c) * INT16_MAX); } /* To make overflow easier to handle, we repeat the first line at the end */ memcpy(&s->colour_lookup[s->colour_lookup_width], s->colour_lookup, s->width * sizeof(int16_t)); } s->burst_left = round(s->sample_rate * s->conf.burst_left); s->burst_width = round(s->sample_rate * s->conf.burst_width); s->burst_level = round(s->conf.burst_level * (s->conf.white_level - s->conf.blanking_level) / 2 * level * INT16_MAX); s->fsc_flag_left = round(s->sample_rate * s->conf.fsc_flag_left); s->fsc_flag_width = round(s->sample_rate * s->conf.fsc_flag_width); s->fsc_flag_level = round(s->conf.fsc_flag_level * (s->conf.white_level - s->conf.blanking_level) * level * INT16_MAX); if(s->conf.colour_mode == VID_SECAM) { double secam_level = 0.23 * (s->conf.white_level - s->conf.blanking_level) * level; r = _init_fm_modulator(&s->fm_secam_cr, s->sample_rate, 4250000, 230000, secam_level); if(r != VID_OK) { vid_free(s); return(r); } r = _init_fm_modulator(&s->fm_secam_cb, s->sample_rate, 4406260, 280000, secam_level); if(r != VID_OK) { vid_free(s); return(r); } } /* Set the next line/frame counter */ /* NOTE: TV line and frame numbers start at 1 rather than 0 */ s->bline = s->line = 1; s->bframe = s->frame = 1; s->framebuffer = NULL; s->olines = 1; s->audio = 0; /* Initalise D/D2-MAC state */ if(s->conf.type == VID_MAC) { mac_init(s); } /* FM audio */ if(s->conf.fm_audio_level > 0 && s->conf.fm_mono_carrier != 0) { r = _init_fm_modulator(&s->fm_mono, s->sample_rate, s->conf.fm_mono_carrier, s->conf.fm_audio_deviation, s->conf.fm_audio_level * slevel); if(r != VID_OK) { vid_free(s); return(r); } s->audio = 1; } if(s->conf.fm_audio_level > 0 && s->conf.fm_left_carrier != 0) { r = _init_fm_modulator(&s->fm_left, s->sample_rate, s->conf.fm_left_carrier, s->conf.fm_audio_deviation, s->conf.fm_audio_level * slevel); if(r != VID_OK) { vid_free(s); return(r); } s->audio = 1; } if(s->conf.fm_audio_level > 0 && s->conf.fm_right_carrier != 0) { r = _init_fm_modulator(&s->fm_right, s->sample_rate, s->conf.fm_right_carrier, s->conf.fm_audio_deviation, s->conf.fm_audio_level * slevel); if(r != VID_OK) { vid_free(s); return(r); } s->audio = 1; } /* NICAM audio */ if(s->conf.nicam_level > 0 && s->conf.nicam_carrier != 0) { r = nicam_mod_init(&s->nicam, NICAM_MODE_STEREO, 0, s->sample_rate, s->conf.nicam_carrier, s->conf.nicam_beta, s->conf.nicam_level * slevel); if(r != 0) { vid_free(s); return(VID_OUT_OF_MEMORY); } s->nicam_buf_len = 0; } /* AM audio */ if(s->conf.am_audio_level > 0 && s->conf.am_mono_carrier != 0) { r = _init_am_modulator(&s->am_mono, s->sample_rate, s->conf.am_mono_carrier, s->conf.am_audio_level * slevel); if(r != VID_OK) { vid_free(s); return(r); } s->audio = 1; } /* FM video */ if(s->conf.modulation == VID_FM) { r = _init_fm_modulator(&s->fm_video, s->sample_rate, 0, s->conf.fm_deviation, s->conf.fm_level * s->conf.level); if(r != VID_OK) { vid_free(s); return(r); } } /* Initalise the teletext system */ if(s->conf.teletext && (r = tt_init(&s->tt, s, s->conf.teletext)) != VID_OK) { vid_free(s); return(r); } /* Initalise the WSS system */ if(s->conf.wss && (r = wss_init(&s->wss, s, s->conf.wss)) != VID_OK) { vid_free(s); return(r); } /* Initialise videocrypt I/II encoder */ if((s->conf.videocrypt || s->conf.videocrypt2) && (r = vc_init(&s->vc, s, s->conf.videocrypt, s->conf.videocrypt2)) != VID_OK) { vid_free(s); return(r); } /* Initialise videocrypt S encoder */ if(s->conf.videocrypts && (r = vcs_init(&s->vcs, s, s->conf.videocrypts)) != VID_OK) { vid_free(s); return(r); } /* Initalise syster encoder */ if(s->conf.syster && (r = ng_init(&s->ng, s)) != VID_OK) { vid_free(s); return(r); } /* Initalise ACP renderer */ if(s->conf.acp && (r = acp_init(&s->acp, s)) != VID_OK) { vid_free(s); return(r); } /* Output line buffer(s) */ s->oline = calloc(sizeof(int16_t *), s->olines); if(!s->oline) { vid_free(s); return(VID_OUT_OF_MEMORY); } for(r = 0; r < s->olines; r++) { s->oline[r] = malloc(sizeof(int16_t) * 2 * s->width); if(!s->oline[r]) { vid_free(s); return(VID_OUT_OF_MEMORY); } /* Blank the lines */ for(x = 0; x < s->width; x++) { s->oline[r][x * 2] = s->blanking_level; } } return(VID_OK); } void vid_free(vid_t *s) { int i; /* Close the AV source */ vid_av_close(s); if(s->conf.acp) { acp_free(&s->acp); } if(s->conf.syster) { ng_free(&s->ng); } if(s->conf.videocrypt || s->conf.videocrypt2) { vc_free(&s->vc); } if(s->conf.videocrypts) { vcs_free(&s->vcs); } if(s->conf.wss) { wss_free(&s->wss); } if(s->conf.teletext) { tt_free(&s->tt); } if(s->video_filter_taps) { fir_int16_free(&s->video_filter); free(s->video_filter_taps); } if(s->conf.type == VID_MAC) { mac_free(s); } /* Free allocated memory */ if(s->y_level_lookup != NULL) free(s->y_level_lookup); if(s->i_level_lookup != NULL) free(s->i_level_lookup); if(s->q_level_lookup != NULL) free(s->q_level_lookup); if(s->colour_lookup != NULL) free(s->colour_lookup); _free_fm_modulator(&s->fm_secam_cr); _free_fm_modulator(&s->fm_secam_cb); _free_fm_modulator(&s->fm_video); _free_fm_modulator(&s->fm_mono); _free_fm_modulator(&s->fm_left); _free_fm_modulator(&s->fm_right); nicam_mod_free(&s->nicam); _free_am_modulator(&s->am_mono); if(s->oline) { for(i = 0; i < s->olines; i++) { free(s->oline[i]); } free(s->oline); } memset(s, 0, sizeof(vid_t)); } void vid_info(vid_t *s) { fprintf(stderr, "Video: %dx%d %.2f fps (full frame %dx%d)\n", s->active_width, s->conf.active_lines, (double) s->conf.frame_rate_num / s->conf.frame_rate_den, s->width, s->conf.lines ); fprintf(stderr, "Sample rate: %d\n", s->sample_rate); } int vid_init_filter(vid_t *s) { int taps; if(s->conf.modulation == VID_VSB) { taps = 51; s->video_filter_taps = calloc(taps, sizeof(int16_t) * 2); if(!s->video_filter_taps) { return(VID_OUT_OF_MEMORY); } fir_int16_complex_band_pass(s->video_filter_taps, taps, s->sample_rate, -s->conf.vsb_lower_bw, s->conf.vsb_upper_bw, 750000, 1); fir_int16_complex_init(&s->video_filter, s->video_filter_taps, taps, 1, 1); } else if(s->conf.modulation == VID_FM) { taps = FM_TAPS; s->video_filter_taps = calloc(taps, sizeof(int16_t)); if(!s->video_filter_taps) { return(VID_OUT_OF_MEMORY); } memcpy(s->video_filter_taps, s->conf.type == VID_MAC ? fm_mac_taps : fm_taps, taps * sizeof(int16_t)); fir_int16_init(&s->video_filter, s->video_filter_taps, taps, 1, 1); if(s->conf.type == VID_MAC && s->sample_rate != 20250000) { fprintf(stderr, "Warning: The D/D2-MAC pre-emphasis filter is designed to run at 20.25 MHz only.\n"); } else if(s->conf.type != VID_MAC && s->sample_rate != 16000000) { fprintf(stderr, "Warning: The FM video pre-emphasis filter is designed to run at 16 MHz only.\n"); } } return(VID_OK); } size_t vid_get_framebuffer_length(vid_t *s) { return(sizeof(uint32_t) * s->active_width * s->conf.active_lines); } int16_t *vid_adj_delay(vid_t *s, int lines) { s->odelay -= lines; s->output = s->oline[s->odelay]; s->line -= lines; while(s->line < 1) { s->line += s->conf.lines; s->frame--; } return(s->output); } static void _vid_next_line_raster(vid_t *s) { const char *seq; int x; int vy; int w; uint32_t rgb; int pal; int odd; int fsc = 0; int16_t *lut_b; int16_t *lut_i; int16_t *lut_q; /* Sequence codes: abcd * * a: first sync * h = horizontal sync pulse * v = short vertical sync pulse * V = long vertical sync pulse * _ = no sync pulse * * b: colour burst * 0 = line always has a colour burst * _ = line never has a colour burst * 1 = line has a colour burst on odd frames * 2 = line has a colour burst on even frames * * c: left content * _ = blanking * a = active video * * d: right content * _ = blanking * a = active video * v = short vertical sync pulse * V = long vertical sync pulse * **** I don't like this code, it's overly complicated for all it does. */ vy = -1; seq = "____"; if(s->conf.type == VID_RASTER_625) { switch(s->line) { case 1: seq = "V__V"; break; case 2: seq = "V__V"; break; case 3: seq = "V__v"; break; case 4: seq = "v__v"; break; case 5: seq = "v__v"; break; case 6: seq = "h1__"; break; case 7: seq = "h0__"; break; case 8: seq = "h0__"; break; case 9: seq = "h0__"; break; case 10: seq = "h0__"; break; case 11: seq = "h0__"; break; case 12: seq = "h0__"; break; case 13: seq = "h0__"; break; case 14: seq = "h0__"; break; case 15: seq = "h0__"; break; case 16: seq = "h0__"; break; case 17: seq = "h0__"; break; case 18: seq = "h0__"; break; case 19: seq = "h0__"; break; case 20: seq = "h0__"; break; case 21: seq = "h0__"; break; case 22: seq = "h0__"; break; case 23: seq = "h0_a"; break; case 310: seq = "h1aa"; break; case 311: seq = "v__v"; break; case 312: seq = "v__v"; break; case 313: seq = "v__V"; break; case 314: seq = "V__V"; break; case 315: seq = "V__V"; break; case 316: seq = "v__v"; break; case 317: seq = "v__v"; break; case 318: seq = "v___"; break; case 319: seq = "h2__"; break; case 320: seq = "h0__"; break; case 321: seq = "h0__"; break; case 322: seq = "h0__"; break; case 323: seq = "h0__"; break; case 324: seq = "h0__"; break; case 325: seq = "h0__"; break; case 326: seq = "h0__"; break; case 327: seq = "h0__"; break; case 328: seq = "h0__"; break; case 329: seq = "h0__"; break; case 330: seq = "h0__"; break; case 331: seq = "h0__"; break; case 332: seq = "h0__"; break; case 333: seq = "h0__"; break; case 334: seq = "h0__"; break; case 335: seq = "h0__"; break; case 622: seq = "h2aa"; break; case 623: seq = "h_av"; break; case 624: seq = "v__v"; break; case 625: seq = "v__v"; break; default: seq = "h0aa"; break; } /* Calculate the active line number */ vy = (s->line < 313 ? (s->line - 23) * 2 : (s->line - 336) * 2 + 1); } else if(s->conf.type == VID_RASTER_525) { switch(s->line) { case 1: seq = "v__v"; break; case 2: seq = "v__v"; break; case 3: seq = "v__v"; break; case 4: seq = "V__V"; break; case 5: seq = "V__V"; break; case 6: seq = "V__V"; break; case 7: seq = "v__v"; break; case 8: seq = "v__v"; break; case 9: seq = "v__v"; break; case 10: seq = "h0__"; break; case 11: seq = "h0__"; break; case 12: seq = "h0__"; break; case 13: seq = "h0__"; break; case 14: seq = "h0__"; break; case 15: seq = "h0__"; break; case 16: seq = "h0__"; break; case 17: seq = "h0__"; break; case 18: seq = "h0__"; break; case 19: seq = "h0__"; break; case 20: seq = "h0__"; break; case 263: seq = "h0av"; break; case 264: seq = "v__v"; break; case 265: seq = "v__v"; break; case 266: seq = "v__V"; break; case 267: seq = "V__V"; break; case 268: seq = "V__V"; break; case 269: seq = "V__v"; break; case 270: seq = "v__v"; break; case 271: seq = "v__v"; break; case 272: seq = "v___"; break; case 273: seq = "h0__"; break; case 274: seq = "h0__"; break; case 275: seq = "h0__"; break; case 276: seq = "h0__"; break; case 277: seq = "h0__"; break; case 278: seq = "h0__"; break; case 279: seq = "h0__"; break; case 280: seq = "h0__"; break; case 281: seq = "h0__"; break; case 282: seq = "h0__"; break; case 283: seq = "h0_a"; break; default: seq = "h0aa"; break; } /* Calculate the active line number */ /* There are 486 lines in this mode with some active video, * but encoded files normally only have 480 of these. Here * we use the line numbers suggested by SMPTE Recommended * Practice RP-202. Lines 23-262 from the first field and * 286-525 from the second. */ vy = (s->line < 265 ? (s->line - 23) * 2 : (s->line - 286) * 2 + 1); } else if(s->conf.type == VID_RASTER_819) { switch(s->line) { case 817: seq = "h___"; break; case 818: seq = "h___"; break; case 819: seq = "h___"; break; case 1: seq = "V___"; break; case 2: seq = "h___"; break; case 3: seq = "h___"; break; case 4: seq = "h___"; break; case 5: seq = "h___"; break; case 6: seq = "h___"; break; case 7: seq = "h___"; break; case 8: seq = "h___"; break; case 9: seq = "h___"; break; case 10: seq = "h___"; break; case 11: seq = "h___"; break; case 12: seq = "h___"; break; case 13: seq = "h___"; break; case 14: seq = "h___"; break; case 15: seq = "h___"; break; case 16: seq = "h___"; break; case 17: seq = "h___"; break; case 18: seq = "h___"; break; case 19: seq = "h___"; break; case 20: seq = "h___"; break; case 21: seq = "h___"; break; case 22: seq = "h___"; break; case 23: seq = "h___"; break; case 24: seq = "h___"; break; case 25: seq = "h___"; break; case 26: seq = "h___"; break; case 27: seq = "h___"; break; case 28: seq = "h___"; break; case 29: seq = "h___"; break; case 30: seq = "h___"; break; case 31: seq = "h___"; break; case 32: seq = "h___"; break; case 33: seq = "h___"; break; case 34: seq = "h___"; break; case 35: seq = "h___"; break; case 36: seq = "h___"; break; case 37: seq = "h___"; break; case 38: seq = "h___"; break; case 406: seq = "h_a_"; break; case 407: seq = "h___"; break; case 408: seq = "h___"; break; case 409: seq = "h__V"; break; case 410: seq = "h___"; break; case 411: seq = "h___"; break; case 412: seq = "h___"; break; case 413: seq = "h___"; break; case 414: seq = "h___"; break; case 415: seq = "h___"; break; case 416: seq = "h___"; break; case 417: seq = "h___"; break; case 418: seq = "h___"; break; case 419: seq = "h___"; break; case 420: seq = "h___"; break; case 421: seq = "h___"; break; case 422: seq = "h___"; break; case 423: seq = "h___"; break; case 424: seq = "h___"; break; case 425: seq = "h___"; break; case 426: seq = "h___"; break; case 427: seq = "h___"; break; case 428: seq = "h___"; break; case 429: seq = "h___"; break; case 430: seq = "h___"; break; case 431: seq = "h___"; break; case 432: seq = "h___"; break; case 433: seq = "h___"; break; case 434: seq = "h___"; break; case 435: seq = "h___"; break; case 436: seq = "h___"; break; case 437: seq = "h___"; break; case 438: seq = "h___"; break; case 439: seq = "h___"; break; case 440: seq = "h___"; break; case 441: seq = "h___"; break; case 442: seq = "h___"; break; case 443: seq = "h___"; break; case 444: seq = "h___"; break; case 445: seq = "h___"; break; case 446: seq = "h___"; break; case 447: seq = "h__a"; break; default: seq = "h_aa"; break; } /* Calculate the active line number */ vy = (s->line < 406 ? (s->line - 48) * 2 : (s->line - 457) * 2 + 1); } else if(s->conf.type == VID_RASTER_405) { switch(s->line) { case 1: seq = "V__V"; break; case 2: seq = "V__V"; break; case 3: seq = "V__V"; break; case 4: seq = "V__V"; break; case 5: seq = "h___"; break; case 6: seq = "h___"; break; case 7: seq = "h___"; break; case 8: seq = "h___"; break; case 9: seq = "h___"; break; case 10: seq = "h___"; break; case 11: seq = "h___"; break; case 12: seq = "h___"; break; case 13: seq = "h___"; break; case 14: seq = "h___"; break; case 15: seq = "h___"; break; case 203: seq = "h_aV"; break; case 204: seq = "V__V"; break; case 205: seq = "V__V"; break; case 206: seq = "V__V"; break; case 207: seq = "V___"; break; case 208: seq = "h___"; break; case 209: seq = "h___"; break; case 210: seq = "h___"; break; case 211: seq = "h___"; break; case 212: seq = "h___"; break; case 213: seq = "h___"; break; case 214: seq = "h___"; break; case 215: seq = "h___"; break; case 216: seq = "h___"; break; case 217: seq = "h___"; break; case 218: seq = "h__a"; break; default: seq = "h_aa"; break; } /* Calculate the active line number */ vy = (s->line < 210 ? (s->line - 16) * 2 : (s->line - 219) * 2 + 1); } else if(s->conf.type == VID_APOLLO_320) { if(s->line <= 8) seq = "V__v"; else seq = "h_aa"; vy = s->line - 9; if(vy < 0 || vy >= s->conf.active_lines) vy = -1; } else if(s->conf.type == VID_BAIRD_240) { switch(s->line) { case 1: seq = "V__V"; break; case 2: seq = "V__V"; break; case 3: seq = "V__V"; break; case 4: seq = "V__V"; break; case 5: seq = "V__V"; break; case 6: seq = "V__V"; break; case 7: seq = "V__V"; break; case 8: seq = "V__V"; break; case 9: seq = "V__V"; break; case 10: seq = "V__V"; break; case 11: seq = "V__V"; break; case 12: seq = "V__V"; break; case 13: seq = "h___"; break; case 14: seq = "h___"; break; case 15: seq = "h___"; break; case 16: seq = "h___"; break; case 17: seq = "h___"; break; case 18: seq = "h___"; break; case 19: seq = "h___"; break; case 20: seq = "h___"; break; default: seq = "h_aa"; break; } /* Calculate the active line number */ vy = s->line - 20; } else if(s->conf.type == VID_BAIRD_30) { /* The original Baird 30 line standard has no sync pulses */ seq = "__aa"; vy = s->line - 1; } if(vy < 0 || vy >= s->conf.active_lines) vy = -1; /* Does this line use colour? */ pal = seq[1] == '0'; pal |= seq[1] == '1' && (s->frame & 1) == 1; pal |= seq[1] == '2' && (s->frame & 1) == 0; /* odd == 1 if this is an odd line, otherwise odd == 0 */ odd = (s->frame + s->line + 1) & 1; /* Calculate colour sub-carrier lookup-positions for the start of this line */ if(s->conf.colour_mode == VID_PAL) { /* PAL */ lut_b = _colour_subcarrier_phase(s, odd ? -135 : 135); lut_i = _colour_subcarrier_phase(s, odd ? -90 : 90); lut_q = _colour_subcarrier_phase(s, 0); } else if(s->conf.colour_mode == VID_NTSC) { /* NTSC */ lut_b = _colour_subcarrier_phase(s, 180); lut_i = _colour_subcarrier_phase(s, 90); lut_q = _colour_subcarrier_phase(s, 0); } else if(s->conf.colour_mode == VID_APOLLO_FSC) { /* Apollo Field Sequential Colour */ fsc = (s->frame * 2 + (s->line < 264 ? 0 : 1)) % 3; lut_b = NULL; lut_i = NULL; lut_q = NULL; pal = 0; } else { /* No colour */ lut_b = NULL; lut_i = NULL; lut_q = NULL; pal = 0; } /* Render the left side sync pulse */ if(seq[0] == 'v') w = s->vsync_short_width; else if(seq[0] == 'V') w = s->vsync_long_width; else if(seq[0] == 'h') w = s->hsync_width; else w = 0; for(x = 0; x < w && x < s->half_width; x++) { s->output[x * 2] = s->sync_level; } /* Render left side of active video if required */ if(seq[2] == 'a' && vy != -1) { for(; x < s->active_left; x++) { s->output[x * 2] = s->blanking_level; } for(; x < s->half_width; x++) { rgb = s->framebuffer != NULL ? s->framebuffer[vy * s->active_width + x - s->active_left] & 0xFFFFFF : 0x000000; if(s->conf.colour_mode == VID_APOLLO_FSC) { rgb = (rgb >> (8 * fsc)) & 0xFF; rgb |= (rgb << 8) | (rgb << 16); } s->output[x * 2] = s->y_level_lookup[rgb]; if(pal) { s->output[x * 2] += (s->i_level_lookup[rgb] * lut_i[x]) >> 15; s->output[x * 2] += (s->q_level_lookup[rgb] * lut_q[x]) >> 15; } } } else { for(; x < s->half_width; x++) { s->output[x * 2] = s->blanking_level; } } if(seq[3] == 'a' && vy != -1) { for(; x < s->active_left + s->active_width; x++) { rgb = s->framebuffer != NULL ? s->framebuffer[vy * s->active_width + x - s->active_left] & 0xFFFFFF : 0x000000; if(s->conf.colour_mode == VID_APOLLO_FSC) { rgb = (rgb >> (8 * fsc)) & 0xFF; rgb |= (rgb << 8) | (rgb << 16); } s->output[x * 2] = s->y_level_lookup[rgb]; if(pal) { s->output[x * 2] += (s->i_level_lookup[rgb] * lut_i[x]) >> 15; s->output[x * 2] += (s->q_level_lookup[rgb] * lut_q[x]) >> 15; } } } else { if(seq[3] == 'v') w = s->vsync_short_width; else if(seq[3] == 'V') w = s->vsync_long_width; else w = 0; for(; x < s->half_width + w && x < s->width; x++) { s->output[x * 2] = s->sync_level; } } /* Blank the remainder of the line */ for(; x < s->width; x++) { s->output[x * 2] = s->blanking_level; } /* Render the colour burst */ if(pal) { for(x = s->burst_left; x < s->burst_left + s->burst_width; x++) { s->output[x * 2] += (lut_b[x] * s->burst_level) >> 15; } } /* Render the FSC flag */ if(s->conf.colour_mode == VID_APOLLO_FSC && fsc == 1 && (s->line == 18 || s->line == 281)) { /* The Apollo colour standard transmits one colour per field * (Blue, Red, Green), with the green field indicated by a flag * on field line 18. The flag also indicates the temperature of * the camera by its duration, varying between 5 and 45 μs. The * duration is fixed to 20 μs in hacktv. */ for(x = s->fsc_flag_left; x < s->fsc_flag_left + s->fsc_flag_width; x++) { s->output[x * 2] = s->fsc_flag_level; } } /* Render the SECAM colour subcarrier */ if(s->conf.colour_mode == VID_SECAM && (seq[2] == 'a' || seq[3] == 'a')) { x = s->active_left; w = x + s->active_width; if(seq[2] != 'a') x = s->half_width; if(seq[3] != 'a') w = s->half_width; x -= s->burst_left; for(; x < w; x++) { rgb = 0x000000; if(x >= s->active_left && x < s->active_left + s->active_width) { rgb = s->framebuffer != NULL ? s->framebuffer[vy * s->active_width + x - s->active_left] & 0xFFFFFF : 0x000000; } if(((s->frame * s->conf.lines) + s->line) & 1) { _fm_modulator_add(&s->fm_secam_cr, &s->output[x * 2], s->i_level_lookup[rgb]); } else { _fm_modulator_add(&s->fm_secam_cb, &s->output[x * 2], s->q_level_lookup[rgb]); } } } /* Teletext, if enabled */ if(s->conf.teletext) { tt_render_line(&s->tt); } /* WSS, if enabled */ if(s->conf.wss) { wss_render_line(&s->wss); } /* Videocrypt scrambling, if enabled */ if(s->conf.videocrypt || s->conf.videocrypt2) { vc_render_line(&s->vc); } /* Videocrypt S scrambling, if enabled */ if(s->conf.videocrypts) { vcs_render_line(&s->vcs); } /* Syster scrambling, if enabled */ if(s->conf.syster == 1) { ng_render_line(&s->ng); } /* ACP renderer, if enabled */ if(s->conf.acp == 1) { acp_render_line(&s->acp); } /* Clear the Q channel */ for(x = 0; x < s->width; x++) { s->output[x * 2 + 1] = 0; } } static void _process_audio(vid_t *s) { static int16_t audio[2] = { 0, 0 }; static int interp = 0; int x; for(x = 0; x < s->width; x++) { int16_t add[2] = { 0, 0 }; /* TODO: Replace this with a real FIR filter... */ interp += HACKTV_AUDIO_SAMPLE_RATE; if(interp >= s->sample_rate) { interp -= s->sample_rate; if(s->audiobuffer_samples == 0) { s->audiobuffer = _av_read_audio(s, &s->audiobuffer_samples); if(s->conf.systeraudio == 1) { ng_invert_audio(&s->ng, s->audiobuffer, s->audiobuffer_samples); } } if(s->audiobuffer) { /* Fetch next sample */ audio[0] = s->audiobuffer[0]; audio[1] = s->audiobuffer[1]; s->audiobuffer += 2; s->audiobuffer_samples--; } else { /* No audio from the source */ audio[0] = 0; audio[1] = 0; } if((s->conf.nicam_level > 0 && s->conf.nicam_carrier != 0) || s->conf.type == VID_MAC) { s->nicam_buf[s->nicam_buf_len++] = audio[0]; s->nicam_buf[s->nicam_buf_len++] = audio[1]; if(s->nicam_buf_len == NICAM_AUDIO_LEN * 2) { if(s->conf.nicam_level > 0 && s->conf.nicam_carrier != 0) { nicam_mod_input(&s->nicam, s->nicam_buf); } if(s->conf.type == VID_MAC) { mac_write_audio(s, s->nicam_buf); } s->nicam_buf_len = 0; } } } if(s->conf.fm_audio_level > 0 && s->conf.fm_mono_carrier != 0) { _fm_modulator_add(&s->fm_mono, add, (audio[0] + audio[1]) / 2); } if(s->conf.fm_audio_level > 0 && s->conf.fm_left_carrier != 0) { _fm_modulator_add(&s->fm_left, add, audio[0]); } if(s->conf.fm_audio_level > 0 && s->conf.fm_right_carrier != 0) { _fm_modulator_add(&s->fm_right, add, audio[1]); } if(s->conf.am_audio_level > 0 && s->conf.am_mono_carrier != 0) { _am_modulator_add(&s->am_mono, add, (audio[0] + audio[1]) / 2); } s->output[x * 2 + 0] += add[0]; s->output[x * 2 + 1] += add[1]; } if(s->conf.nicam_level > 0 && s->conf.nicam_carrier != 0) { nicam_mod_output(&s->nicam, s->output, s->width); } } static int16_t *_vid_next_line(vid_t *s, size_t *samples) { int x; s->odelay = s->olines - 1; s->output = s->oline[s->odelay]; s->frame = s->bframe; s->line = s->bline; /* Load the next frame */ if(s->line == 1) { /* Have we reached the end of the video? */ if(_av_eof(s)) { return(NULL); } s->framebuffer = _av_read_video(s, &s->ratio); } if(s->conf.type == VID_MAC) { mac_next_line(s); } else { _vid_next_line_raster(s); } /* Ensure the Q part of the signal is zero */ for(x = 0; x < s->width; x++) { s->output[x * 2 + 1] = 0; } /* Apply video filter if enabled */ if(s->video_filter_taps) { if(s->conf.modulation == VID_VSB) { //fir_int16_complex_process(&s->video_filter, s->output, 1, s->output, s->width, 1); fir_int16_complex_process_simple(&s->video_filter, s->output, s->width); } else if(s->conf.modulation == VID_FM) { //fir_int16_process(&s->video_filter, s->output, 2, s->output, s->width, 2); fir_int16_process_simple(&s->video_filter, s->output, s->width); } } /* Process the audio */ if(s->audio == 1) { _process_audio(s); } /* FM modulate the video and audio if requested */ if(s->conf.modulation == VID_FM) { for(x = 0; x < s->width; x++) { _fm_modulator(&s->fm_video, &s->output[x * 2], s->output[x * 2]); } } /* Advance the next line/frame counter */ if(s->bline++ == s->conf.lines) { s->bline = 1; s->bframe++; } /* Return a pointer to the line buffer */ *samples = s->width; /* Rotate the output lines */ s->output = s->oline[0]; for(x = 1; x < s->olines; x++) { s->oline[x - 1] = s->oline[x]; } s->oline[x - 1] = s->output; return(s->output); } int16_t *vid_next_line(vid_t *s, size_t *samples) { int16_t *output; /* Drop any delay lines introduced by scramblers / filters */ do { output = _vid_next_line(s, samples); } while(s->frame < 1); return(output); } hacktv-master/video.h000066400000000000000000000147751357376541300151420ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _VIDEO_H #define _VIDEO_H #include #include "nicam728.h" #include "fir.h" typedef struct vid_t vid_t; #include "mac.h" #include "teletext.h" #include "wss.h" #include "videocrypt.h" #include "videocrypts.h" #include "syster.h" #include "acp.h" /* Return codes */ #define VID_OK 0 #define VID_ERROR -1 #define VID_OUT_OF_MEMORY -2 /* Frame type */ #define VID_RASTER_625 0 #define VID_RASTER_525 1 #define VID_RASTER_405 2 #define VID_RASTER_819 3 #define VID_BAIRD_240 4 #define VID_BAIRD_30 5 #define VID_APOLLO_320 6 #define VID_MAC 7 /* Output modulation types */ #define VID_NONE 0 #define VID_AM 1 #define VID_VSB 2 #define VID_FM 3 /* Colour modes */ #define VID_MONOCHROME 0 #define VID_PAL 1 #define VID_NTSC 2 #define VID_SECAM 3 #define VID_APOLLO_FSC 4 /* AV source function prototypes */ typedef uint32_t *(*vid_read_video_t)(void *private, float *ratio); typedef int16_t *(*vid_read_audio_t)(void *private, size_t *samples); typedef int (*vid_eof_t)(void *private); typedef int (*vid_close_t)(void *private); /* RF modulation */ typedef struct { int16_t level; int32_t counter; cint32_t phase; cint32_t *lut; } _mod_fm_t; typedef struct { int16_t level; int32_t counter; cint32_t phase; cint32_t delta; } _mod_am_t; typedef struct { /* Output type */ int output_type; /* Output modulation */ int modulation; /* VSB modulation options */ double vsb_upper_bw; double vsb_lower_bw; /* FM modulation options */ double fm_level; double fm_deviation; /* Overall signal level (pre-modulation) */ double level; /* Level of each component. The total sum should be exactly 1.0 */ double video_level; double fm_audio_level; double am_audio_level; double nicam_level; /* Video */ int type; int mac_mode; int frame_rate_num; int frame_rate_den; int lines; int active_lines; double hsync_width; double vsync_short_width; double vsync_long_width; double white_level; double black_level; double blanking_level; double sync_level; double active_width; double active_left; double gamma; char *teletext; char *wss; char *videocrypt; char *videocrypt2; char *videocrypts; int syster; int systeraudio; int acp; /* RGB weights, should add up to 1.0 */ double rw_co; double gw_co; double bw_co; int colour_mode; double colour_carrier; int colour_lookup_lines; double burst_width; double burst_left; double burst_level; double fsc_flag_width; double fsc_flag_left; double fsc_flag_level; double iu_co; double iv_co; double qu_co; double qv_co; /* FM audio */ double fm_mono_carrier; double fm_left_carrier; double fm_right_carrier; double fm_audio_preemph; double fm_audio_deviation; /* Stereo NICAM audio */ double nicam_carrier; double nicam_beta; /* AM audio */ double am_mono_carrier; double am_mono_bandwidth; } vid_config_t; typedef struct { const char *id; const vid_config_t *conf; } vid_configs_t; struct vid_t { /* Source interface */ void *av_private; vid_read_video_t av_read_video; vid_read_audio_t av_read_audio; vid_eof_t av_eof; vid_close_t av_close; /* Signal configuration */ vid_config_t conf; /* Video setup */ int sample_rate; int width; int half_width; int active_width; int active_left; int hsync_width; int vsync_short_width; int vsync_long_width; int16_t white_level; int16_t black_level; int16_t blanking_level; int16_t sync_level; int16_t *y_level_lookup; int16_t *i_level_lookup; int16_t *q_level_lookup; int colour_lookup_width; int16_t *colour_lookup; int burst_left; int burst_width; int16_t burst_level; _mod_fm_t fm_secam_cr; _mod_fm_t fm_secam_cb; int fsc_flag_left; int fsc_flag_width; int16_t fsc_flag_level; /* Video state */ uint32_t *framebuffer; /* The frame and line number being rendered next */ int bframe; int bline; /* The frame and line number returned by vid_next_line() */ int frame; int line; /* Current frame's aspect ratio */ float ratio; /* Video filter */ int16_t *video_filter_taps; fir_int16_t video_filter; /* Teletext state */ tt_t tt; /* WSS state */ wss_t wss; /* Videocrypt state */ vc_t vc; vcs_t vcs; /* Nagravision Syster state */ ng_t ng; /* ACP state */ acp_t acp; /* Audio state */ int audio; int16_t *audiobuffer; size_t audiobuffer_samples; /* FM Mono/Stereo audio state */ _mod_fm_t fm_mono; _mod_fm_t fm_left; _mod_fm_t fm_right; /* NICAM stereo audio state */ nicam_mod_t nicam; int16_t nicam_buf[NICAM_AUDIO_LEN * 2]; size_t nicam_buf_len; /* AM Mono audio state */ _mod_am_t am_mono; /* FM Video state */ _mod_fm_t fm_video; /* D/D2-MAC specific data */ mac_t mac; /* Output line(s) buffer */ int olines; /* The number of lines */ int16_t **oline; /* Pointer to each line */ int16_t *output; /* Pointer to the current line */ int odelay; /* Index of the current line */ }; extern const vid_configs_t vid_configs[]; extern int vid_init(vid_t *s, unsigned int sample_rate, const vid_config_t * const conf); extern void vid_free(vid_t *s); extern int vid_av_close(vid_t *s); extern void vid_info(vid_t *s); extern int vid_init_filter(vid_t *s); extern size_t vid_get_framebuffer_length(vid_t *s); extern int16_t *vid_adj_delay(vid_t *s, int lines); extern int16_t *vid_next_line(vid_t *s, size_t *samples); #endif hacktv-master/videocrypt.c000066400000000000000000000271211357376541300162040ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ /* -=== Videocrypt encoder ===- * * This is a Videocrypt I/II encoder. It scrambles the image using a technique * called "line cut-and-rotate", and inserts the necessary data into the * VBI area of the image to activate the Videocrypt hardware unscrambler. * * THANKS * * Markus Kuhn and William Andrew Steer for their detailed descriptions * and examples of how Videocrypt works: * * https://www.cl.cam.ac.uk/~mgk25/tv-crypt/ * http://www.techmind.org/vdc/ * * Ralph Metzler for the details of how the VBI data is encoded: * * http://src.gnu-darwin.org/ports/misc/vbidecode/work/bttv/apps/vbidecode/vbidecode.cc * * Alex L. James for providing an active Sky subscriber card, VBI samples, * Videocrypt 2 information and testing. * */ #include #include #include #include #include "video.h" /* Packet header sequences */ static const uint8_t _sequence[8] = { 0x87,0x96,0xA5,0xB4,0xC3,0xD2,0xE1,0x87, }; static const uint8_t _sequence2[8] = { 0x80,0x91,0xA2,0xB3,0xC4,0xD5,0xE6,0xF7, }; /* Hamming codes */ static const uint8_t _hamming[16] = { 0x15,0x02,0x49,0x5E,0x64,0x73,0x38,0x2F, 0xD0,0xC7,0x8C,0x9B,0xA1,0xB6,0xFD,0xEA, }; /* Blocks for VC1 free-access decoding */ static const _vc_block_t _fa_blocks[] = { { 0x05, VC_PRBS_CW_FA } }; /* Blocks for VC1 conditional-access sample, taken from MTV UK and modified, */ /* requires an active Sky card to decode */ static const _vc_block_t _mtv_blocks[] = { { 0x07, 0xB2DD55A7BCE178EUL, { { 0x20 }, { }, { }, { }, { }, { }, { 0xF8,0x19,0x10,0x83,0x20,0x85,0x60,0xAF,0x8F,0xF0,0x49,0x34,0x86,0xC4,0x6A,0xCA,0xC3,0x21,0x4D,0x44,0xB3,0x24,0x36,0x57,0xEC,0xA7,0xCE,0x12,0x38,0x91,0x3E } } }, { 0x07, 0xF9885DA50770B80UL, { /* Modify the following line to change the channel name displayed by the decoder. * The third byte is 0x60 + number of characters, followed by the ASCII characters themselves. */ { 0x20,0x00,0x69,0x20,0x20,0x20,0x48,0x41,0x43,0x4B,0x54,0x56 }, { }, { }, { }, { }, { }, { 0xF8,0x19,0x10,0x83,0x20,0xD1,0xB5,0xA9,0x1F,0x82,0xFE,0xB3,0x6B,0x0A,0x82,0xC3,0x30,0x7B,0x65,0x9C,0xF2,0xBD,0x5C,0xB0,0x6A,0x3B,0x64,0x0F,0xA2,0x66,0xBB } } }, }; /* Blocks for VC2 free-access decoding */ static const _vc2_block_t _fa2_blocks[] = { { 0x9C, VC_PRBS_CW_FA } }; /* Reverse bits in an 8-bit value */ static uint8_t _reverse(uint8_t b) { b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return(b); } /* Reverse bits in an x-bit value */ static uint64_t _rev(uint64_t b, int x) { uint64_t r = 0; while(x--) { r = (r << 1) | (b & 1); b >>= 1; } return(r); } /* Reverse nibbles in a byte */ static inline uint8_t _rnibble(uint8_t a) { return((a >> 4) | (a << 4)); } /* Generate IW for PRBS */ static uint64_t _generate_iw(uint64_t cw, uint8_t fcnt) { uint64_t iw; /* FCNT is repeated 8 times, each time inverted */ iw = ((fcnt ^ 0xFF) << 8) | fcnt; iw |= (iw << 16) | (iw << 32) | (iw << 48); return((iw ^ cw) & VC_PRBS_CW_MASK); } /* Apply VBI frame interleaving */ static void _interleave(uint8_t *frame) { int b, i, j; int offset[6] = { 0, 6, 12, 20, 26, 32 }; uint8_t r[8]; uint8_t m; for(b = 0; b < 6; b++) { uint8_t *s = frame + offset[b]; s[0] = _reverse(s[0]); s[7] = _reverse(s[7]); for(i = 0, m = 0x80; i < 8; i++, m >>= 1) { r[i] = 0x00; for(j = 0; j < 8; j++) { r[i] |= ((m & s[j]) ? 1 : 0) << j; } } memcpy(s, r, 8); } } /* Encode VBI data */ static void _encode_vbi(uint8_t vbi[40], const uint8_t data[16], uint8_t a, uint8_t b) { int x; uint8_t crc; crc = vbi[0] = a; for(x = 0; x < 8; x++) { crc += vbi[1 + x] = data[0 + x]; } vbi[9] = crc; crc = vbi[10] = b; for(x = 0; x < 8; x++) { crc += vbi[11 + x] = data[8 + x]; } vbi[19] = crc; /* Hamming code the VBI data */ for(x = 19; x >= 0; x--) { vbi[x * 2 + 1] = _hamming[vbi[x] & 0x0F]; vbi[x * 2 + 0] = _hamming[vbi[x] >> 4]; } /* Interleave the VBI data */ _interleave(vbi); } int vc_init(vc_t *s, vid_t *vid, const char *mode, const char *mode2) { double f, l; int x; memset(s, 0, sizeof(vc_t)); s->vid = vid; s->counter = 0; s->cw = VC_PRBS_CW_FA; /* Videocrypt I setup */ if(mode == NULL) { s->blocks = NULL; s->block_len = 0; } else if(strcmp(mode, "free") == 0) { s->blocks = _fa_blocks; s->block_len = 1; } else if(strcmp(mode, "conditional") == 0) { s->blocks = _mtv_blocks; s->block_len = 2; } else { fprintf(stderr, "Unrecognised Videocrypt I mode '%s'.\n", mode); return(VID_ERROR); } s->block = 0; /* Videocrypt II setup */ if(mode2 == NULL) { s->blocks2 = NULL; s->block2_len = 0; } else if(strcmp(mode2, "free") == 0) { s->blocks2 = _fa2_blocks; s->block2_len = 1; } else { fprintf(stderr, "Unrecognised Videocrypt II mode '%s'.\n", mode2); return(VID_ERROR); } s->block2 = 0; /* Sample rate ratio */ f = (double) s->vid->width / VC_WIDTH; /* Videocrypt timings appear to be calculated against the centre of the hsync pulse */ l = (double) VC_SAMPLE_RATE * s->vid->conf.hsync_width / 2; /* Quick and dirty sample rate conversion array */ for(x = 0; x < VC_WIDTH; x++) { s->video_scale[x] = round((l + x) * f); } /* Add one delay line */ s->vid->olines += 1; return(VID_OK); } void vc_free(vc_t *s) { /* Nothing */ } void vc_render_line(vc_t *s) { int x; const uint8_t *bline = NULL; /* On the first line of each frame, generate the VBI data */ if(s->vid->line == 1) { uint64_t iw; uint8_t crc; /* Videocrypt I */ if(s->blocks) { if((s->counter & 7) == 0) { /* The active message is updated every 8th frame. The last * message in the block is a duplicate of the first. */ for(crc = x = 0; x < 31; x++) { crc += s->message[x] = s->blocks[s->block].messages[((s->counter >> 3) & 7) % 7][x]; } s->message[x] = ~crc + 1; } if((s->counter & 4) == 0) { /* The first half of the message. Transmitted for 4 frames */ _encode_vbi( s->vbi, s->message, _sequence[(s->counter >> 4) & 7], s->counter & 0xFF ); } else { /* The second half of the message. Transmitted for 4 frames */ _encode_vbi( s->vbi, s->message + 16, _rnibble(_sequence[(s->counter >> 4) & 7]), s->blocks[s->block].mode ); } } /* Videocrypt II */ if(s->blocks2) { if((s->counter & 1) == 0) { /* The active message is updated every 2nd frame */ for(crc = x = 0; x < 31; x++) { crc += s->message2[x] = s->blocks2[s->block2].messages[(s->counter >> 1) & 0x1F][x]; } s->message2[x] = ~crc + 1; } if((s->counter & 1) == 0) { /* The first half of the message */ _encode_vbi( s->vbi2, s->message2, _sequence2[(s->counter >> 1) & 7], s->counter & 0xFF ); } else { /* The second half of the message */ _encode_vbi( s->vbi2, s->message2 + 16, _rnibble(_sequence2[(s->counter >> 1) & 7]), (s->counter & 0x08 ? 0x00 : s->blocks2[s->block2].mode) ); } } /* Reset the PRBS */ iw = _generate_iw(s->cw, s->counter); s->sr1 = iw & VC_PRBS_SR1_MASK; s->sr2 = (iw >> 31) & VC_PRBS_SR2_MASK; /* After 64 frames, advance to the next block and codeword */ s->counter++; if((s->counter & 0x3F) == 0) { /* Apply the current block codeword */ s->cw = VC_PRBS_CW_FA; if(s->blocks) s->cw = s->blocks[s->block].codeword; if(s->blocks2) s->cw = s->blocks2[s->block2].codeword; /* Move to the next block */ if(++s->block == s->block_len) { s->block = 0; } if(++s->block2 == s->block2_len) { s->block2 = 0; } } } /* Calculate VBI line, or < 0 if not */ if(s->blocks && s->vid->line >= VC_VBI_FIELD_1_START && s->vid->line < VC_VBI_FIELD_1_START + VC_VBI_LINES_PER_FIELD) { /* Top VBI field */ bline = &s->vbi[(s->vid->line - VC_VBI_FIELD_1_START) * VC_VBI_BYTES_PER_LINE]; } else if(s->blocks && s->vid->line >= VC_VBI_FIELD_2_START && s->vid->line < VC_VBI_FIELD_2_START + VC_VBI_LINES_PER_FIELD) { /* Bottom VBI field */ bline = &s->vbi[(s->vid->line - VC_VBI_FIELD_2_START + VC_VBI_LINES_PER_FIELD) * VC_VBI_BYTES_PER_LINE]; } else if(s->blocks2 && s->vid->line >= VC2_VBI_FIELD_1_START && s->vid->line < VC2_VBI_FIELD_1_START + VC_VBI_LINES_PER_FIELD) { /* Top VBI field VC2 */ bline = &s->vbi2[(s->vid->line - VC2_VBI_FIELD_1_START) * VC_VBI_BYTES_PER_LINE]; } else if(s->blocks2 && s->vid->line >= VC2_VBI_FIELD_2_START && s->vid->line < VC2_VBI_FIELD_2_START + VC_VBI_LINES_PER_FIELD) { /* Bottom VBI field VC2 */ bline = &s->vbi2[(s->vid->line - VC2_VBI_FIELD_2_START + VC_VBI_LINES_PER_FIELD) * VC_VBI_BYTES_PER_LINE]; } /* Render the VBI line if necessary */ if(bline) { int b, c; x = s->video_scale[VC_VBI_LEFT]; for(b = 0; b < VC_VBI_BITS_PER_LINE; b++) { c = (bline[b / 8] >> (b % 8)) & 1; c = c ? s->vid->white_level : s->vid->black_level; for(; x < s->video_scale[VC_VBI_LEFT + VC_VBI_SAMPLES_PER_BIT * (b + 1)]; x++) { s->vid->output[x * 2] = c; } } } /* Scramble the line if necessary */ x = -1; if((s->vid->line >= VC_FIELD_1_START && s->vid->line < VC_FIELD_1_START + VC_LINES_PER_FIELD) || (s->vid->line >= VC_FIELD_2_START && s->vid->line < VC_FIELD_2_START + VC_LINES_PER_FIELD)) { int i; x = (s->c >> 8) & 0xFF; for(i = 0; i < 16; i++) { int a; /* Update shift registers */ s->sr1 = (s->sr1 >> 1) ^ (s->sr1 & 1 ? 0x7BB88888UL : 0); s->sr2 = (s->sr2 >> 1) ^ (s->sr2 & 1 ? 0x17A2C100UL : 0); /* Load the multiplexer address */ a = _rev(s->sr2, 29) & 0x1F; if(a == 31) a = 30; /* Shift into result register */ s->c = (s->c >> 1) | (((_rev(s->sr1, 31) >> a) & 1) << 15); } } /* Hack to preserve WSS signal data */ if(s->vid->line == 24) x = -1; if(x != -1) { int cut; int lshift; int y; int16_t *delay = s->vid->oline[s->vid->odelay - 1]; cut = 105 + (0xFF - x) * 2; lshift = 710 - cut; y = s->video_scale[VC_LEFT + lshift]; for(x = s->video_scale[VC_LEFT]; x < s->video_scale[VC_LEFT + cut]; x++, y++) { delay[x * 2] = s->vid->output[y * 2]; } y = s->video_scale[VC_LEFT]; for(; x < s->video_scale[VC_RIGHT + VC_OVERLAP]; x++, y++) { delay[x * 2] = s->vid->output[y * 2]; } } vid_adj_delay(s->vid, 1); } hacktv-master/videocrypt.h000066400000000000000000000062171357376541300162140ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2017 Philip Heron */ /* */ /* 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 . */ #ifndef _VIDEOCRYPT_H #define _VIDEOCRYPT_H #include #include "video.h" #define VC_SAMPLE_RATE 14000000 #define VC_WIDTH (VC_SAMPLE_RATE / 25 / 625) #define VC_VBI_LEFT 120 #define VC_VBI_FIELD_1_START 12 #define VC_VBI_FIELD_2_START 325 #define VC_VBI_LINES_PER_FIELD 4 #define VC_VBI_LINES_PER_FRAME (VC_VBI_LINES_PER_FIELD * 2) #define VC_VBI_SAMPLES_PER_BIT 18 #define VC_VBI_BITS_PER_LINE 40 #define VC_VBI_BYTES_PER_LINE (VC_VBI_BITS_PER_LINE / 8) #define VC_PACKET_LENGTH 32 #define VC_LEFT 120 #define VC_RIGHT (VC_LEFT + 710) #define VC_OVERLAP 15 #define VC_FIELD_1_START 24 #define VC_FIELD_2_START 336 #define VC_LINES_PER_FIELD 287 #define VC_LINES_PER_FRAME (VC_LINES_PER_FIELD * 2) #define VC_PRBS_CW_FA (((uint64_t) 1 << 60) - 1) #define VC_PRBS_CW_MASK (((uint64_t) 1 << 60) - 1) #define VC_PRBS_SR1_MASK (((uint32_t) 1 << 31) - 1) #define VC_PRBS_SR2_MASK (((uint32_t) 1 << 29) - 1) #define VC2_VBI_FIELD_1_START (VC_VBI_FIELD_1_START - 4) #define VC2_VBI_FIELD_2_START (VC_VBI_FIELD_2_START - 4) typedef struct { uint8_t mode; uint64_t codeword; uint8_t messages[7][32]; } _vc_block_t; typedef struct { uint8_t mode; uint64_t codeword; uint8_t messages[32][32]; } _vc2_block_t; typedef struct { vid_t *vid; uint8_t counter; /* VC1 blocks */ const _vc_block_t *blocks; size_t block; size_t block_len; uint8_t message[32]; uint8_t vbi[VC_VBI_BYTES_PER_LINE * VC_VBI_LINES_PER_FRAME]; /* VC2 blocks */ const _vc2_block_t *blocks2; size_t block2; size_t block2_len; uint8_t message2[32]; uint8_t vbi2[VC_VBI_BYTES_PER_LINE * VC_VBI_LINES_PER_FRAME]; /* PRBS generator */ uint64_t cw; uint64_t sr1; uint64_t sr2; uint16_t c; int video_scale[VC_WIDTH]; } vc_t; extern int vc_init(vc_t *s, vid_t *vs, const char *mode, const char *mode2); extern void vc_free(vc_t *s); extern void vc_render_line(vc_t *s); #endif hacktv-master/videocrypts.c000066400000000000000000000163401357376541300163700ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ /* -=== Videocrypt S encoder ===- * * This is untested on real hardware and should be considered just a * simulation. The VBI data *may* be valid but the shuffling sequence * is definitly not. There may also be colour distortion due to hacktv * not operating at the specified sample rate of FPAL * 4. */ #include #include #include #include #include "video.h" /* The first line of each block */ static const int _block_start[12] = { 28, 75, 122, 169, 216, 263, 340, 387, 434, 481, 528, 575, }; /* Header synchronisation sequence */ static const uint8_t _sequence[8] = { 0x81,0x92,0xA3,0xB4,0xC5,0xD6,0xE7,0xF0, }; /* Hamming codes */ static const uint8_t _hamming[16] = { 0x15,0x02,0x49,0x5E,0x64,0x73,0x38,0x2F, 0xD0,0xC7,0x8C,0x9B,0xA1,0xB6,0xFD,0xEA, }; /* Reverse bits in an 8-bit value */ static uint8_t _reverse(uint8_t b) { b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return(b); } /* Reverse nibbles in a byte */ static inline uint8_t _rnibble(uint8_t a) { return((a >> 4) | (a << 4)); } /* Apply VBI frame interleaving */ static void _interleave(uint8_t *frame) { int b, i, j; int offset[6] = { 0, 6, 12, 20, 26, 32 }; uint8_t r[8]; uint8_t m; for(b = 0; b < 6; b++) { uint8_t *s = frame + offset[b]; s[0] = _reverse(s[0]); s[7] = _reverse(s[7]); for(i = 0, m = 0x80; i < 8; i++, m >>= 1) { r[i] = 0x00; for(j = 0; j < 8; j++) { r[i] |= ((m & s[j]) ? 1 : 0) << j; } } memcpy(s, r, 8); } } /* Encode VBI data */ static void _encode_vbi(uint8_t vbi[40], const uint8_t data[16], uint8_t a, uint8_t b) { int x; /* Set the information (a, b) and initial check bytes for each field */ vbi[ 9] = vbi[ 0] = a; vbi[19] = vbi[10] = b; /* Copy the eight security bytes for each field, * while updating the check byte */ for(x = 0; x < 8; x++) { vbi[ 9] += vbi[ 1 + x] = data[0 + x]; vbi[19] += vbi[11 + x] = data[8 + x]; } /* Hamming code the VBI data */ for(x = 19; x >= 0; x--) { vbi[x * 2 + 1] = _hamming[vbi[x] & 0x0F]; vbi[x * 2 + 0] = _hamming[vbi[x] >> 4]; } /* Interleave the VBI data */ _interleave(vbi); } static void _block_shuffle(vcs_t *s) { int i, t, r; for(i = 0; i < 47; i++) { s->block[i] = i; } for(i = 0; i < 47; i++) { r = rand() % 47; t = s->block[i]; s->block[i] = s->block[r]; s->block[r] = t; } } int vcs_init(vcs_t *s, vid_t *vid, const char *mode) { double f; int x; memset(s, 0, sizeof(vcs_t)); s->vid = vid; s->mode = 0x00; s->counter = 0; if(strcmp(mode, "free") == 0) { /* Nothing yet */ } else { fprintf(stderr, "Unrecognised Videocrypt S mode '%s'.\n", mode); return(VID_ERROR); } /* Sample rate ratio */ f = (double) s->vid->width / VCS_WIDTH; /* Quick and dirty sample rate conversion array */ for(x = 0; x < VCS_WIDTH; x++) { s->video_scale[x] = round(x * f); } /* Allocate memory for the delay */ s->vid->olines += VCS_DELAY_LINES; return(VID_OK); } void vcs_free(vcs_t *s) { /* Nothing */ } void vcs_render_line(vcs_t *s) { int x, j; uint8_t *bline = NULL; int line; /* Calculate which line is about to be transmitted due to the delay */ line = s->vid->line - VCS_DELAY_LINES; if(line < 1) line += s->vid->conf.lines; /* Swap the active line with the oldest line in the delay buffer, * with active video offset in j if necessary. */ j = 0; if((line >= 28 && line <= 309) || (line >= 340 && line <= 621)) { int block; int bline; /* Calculate the line number, * 0 - 281 top field, * 282 - 563 bottom field */ x = line - (line < 340 ? 28 : 340 - 282); /* Calculate block number and block line */ block = x / 47; bline = x % 47; if(bline == 0) { _block_shuffle(s); } /* Calculate target block/line */ block = (block + 1) % 12; bline = s->block[bline]; /* Calculate position in delay buffer */ j = (_block_start[block] + bline) - line; if(j < 0) j += s->vid->conf.lines - 1; } vid_adj_delay(s->vid, VCS_DELAY_LINES); if(j > 0) { int16_t *dline = s->vid->oline[s->vid->odelay + j]; for(x = s->vid->active_left * 2; x < s->vid->width * 2; x += 2) { s->vid->output[x] = dline[x]; } } /* On the first line of each frame, generate the VBI data */ if(line == 1) { uint8_t crc; if((s->counter & 1) == 0) { /* The active message is updated every 2nd frame */ for(crc = x = 0; x < 31; x++) { crc += s->message[x] = 0x00; } s->message[x] = ~crc + 1; } if((s->counter & 1) == 0) { /* The first half of the message */ _encode_vbi( s->vbi, s->message, _sequence[(s->counter >> 1) & 7], s->counter & 0xFF ); } else { /* The second half of the message */ _encode_vbi( s->vbi, s->message + 16, _rnibble(_sequence[(s->counter >> 1) & 7]), (s->counter & 0x08 ? 0x00 : s->mode) ); } /* After 64 frames, advance to the next block and codeword */ s->counter++; } /* Set a pointer to the VBI data to render on this line, or NULL if none */ if(line >= VCS_VBI_FIELD_1_START && line < VCS_VBI_FIELD_1_START + VCS_VBI_LINES_PER_FIELD) { /* Top field VBI */ bline = &s->vbi[(line - VCS_VBI_FIELD_1_START) * VCS_VBI_BYTES_PER_LINE]; } else if(line >= VCS_VBI_FIELD_2_START && line < VCS_VBI_FIELD_2_START + VCS_VBI_LINES_PER_FIELD) { /* Bottom field VBI */ bline = &s->vbi[(line - VCS_VBI_FIELD_2_START + VCS_VBI_LINES_PER_FIELD) * VCS_VBI_BYTES_PER_LINE]; } if(bline) { int b, c; /* Videocrypt S's VBI data sits in the active video area. Clear it first */ for(x = s->vid->active_left; x < s->vid->active_left + s->vid->active_width; x++) { s->vid->output[x * 2] = s->vid->black_level; } x = s->video_scale[VCS_VBI_LEFT]; for(b = 0; b < VCS_VBI_BITS_PER_LINE; b++) { c = (bline[b / 8] >> (b % 8)) & 1; c = c ? s->vid->white_level : s->vid->black_level; for(; x < s->video_scale[VCS_VBI_LEFT + VCS_VBI_SAMPLES_PER_BIT * (b + 1)]; x++) { s->vid->output[x * 2] = c; } } } } hacktv-master/videocrypts.h000066400000000000000000000043441357376541300163760ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2019 Philip Heron */ /* */ /* 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 . */ #ifndef _VIDEOCRYPTS_H #define _VIDEOCRYPTS_H #include #include "video.h" #define VCS_SAMPLE_RATE 17734475 #define VCS_WIDTH 1135 #define VCS_VBI_LEFT 211 #define VCS_VBI_FIELD_1_START 24 #define VCS_VBI_FIELD_2_START 336 #define VCS_VBI_LINES_PER_FIELD 4 #define VCS_VBI_LINES_PER_FRAME (VCS_VBI_LINES_PER_FIELD * 2) #define VCS_VBI_SAMPLES_PER_BIT 22 #define VCS_VBI_BITS_PER_LINE 40 #define VCS_VBI_BYTES_PER_LINE (VCS_VBI_BITS_PER_LINE / 8) #define VCS_PACKET_LENGTH 32 /* VCS_DELAY_LINES needs to be long enough for the scrambler to access any * line in the next block, which may be in the next field... */ #define VCS_DELAY_LINES 125 typedef struct { vid_t *vid; uint8_t mode; uint8_t counter; uint8_t message[32]; uint8_t vbi[VCS_VBI_BYTES_PER_LINE * VCS_VBI_LINES_PER_FRAME]; int block[47]; int video_scale[VCS_WIDTH]; } vcs_t; extern int vcs_init(vcs_t *s, vid_t *vs, const char *mode); extern void vcs_free(vcs_t *s); extern void vcs_render_line(vcs_t *s); #endif hacktv-master/wss.c000066400000000000000000000077001357376541300146310ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ /* -=== WSS encoder ===- */ #include #include #include #include #include "video.h" #include "vbidata.h" typedef struct { const char *id; uint8_t code; } _wss_modes_t; static const _wss_modes_t _wss_modes[] = { { "4:3", 0x08 }, { "16:9", 0x07 }, { "14:9-letterbox", 0x01 }, { "16:9-letterbox", 0x04 }, { "auto", 0xFF }, { NULL, 0 }, }; static size_t _group_bits(uint8_t *vbi, uint8_t code, size_t offset, size_t length) { int i, b; while(length--) { for(i = 0; i < 6; i++, offset++) { if(i == 3) code ^= 1; b = 7 - (offset % 8); vbi[offset / 8] &= ~(1 << b); vbi[offset / 8] |= (code & 1) << b; } code >>= 1; } return(offset); } int wss_init(wss_t *s, vid_t *vid, char *mode) { int16_t level; size_t o; memset(s, 0, sizeof(wss_t)); /* Find the mode settings */ s->code = 0; for(o = 0; _wss_modes[o].id != NULL; o++) { if(strcasecmp(mode, _wss_modes[o].id) == 0) { s->code = _wss_modes[o].code; break; } } if(s->code == 0) { fprintf(stderr, "wss: Unrecognised mode '%s'.\n", mode); return(VID_ERROR); } /* Calculate the high level for the VBI data */ level = round((vid->white_level - vid->black_level) * (5.0 / 7.0)); s->vid = vid; s->lut = vbidata_init( 320, s->vid->width, level, VBIDATA_FILTER_RC, 0.7 ); if(!s->lut) { return(VID_OUT_OF_MEMORY); } /* Prepare the VBI data. Start with the run-in and start code */ s->vbi[0] = 0xF8; // 11111000 s->vbi[1] = 0xE3; // 11100011 s->vbi[2] = 0x8E; // 10001110 s->vbi[3] = 0x38; // 00111000 s->vbi[4] = 0xF1; // 11110001 s->vbi[5] = 0xE0; // 11100000 s->vbi[6] = 0xF8; // 11111___ /* Group 1 (Aspect Ratio) */ o = _group_bits(s->vbi, s->code, 29 + 24, 4); /* Group 2 (Enhanced Services) */ o = _group_bits(s->vbi, 0x00, o, 4); /* Group 3 (Subtitles) */ o = _group_bits(s->vbi, 0x00, o, 3); /* Group 4 (Reserved) */ o = _group_bits(s->vbi, 0x00, o, 3); /* Calculate width of line to blank */ s->blank_width = round(s->vid->sample_rate * 42.5e-6); return(VID_OK); } void wss_free(wss_t *s) { if(s == NULL) return; free(s->lut); memset(s, 0, sizeof(wss_t)); } void wss_render_line(wss_t *s) { int x; /* WSS is rendered on line 23 */ if(s->vid->line == 23) { if(s->code == 0xFF) { /* Auto mode selects between 4:3 and 16:9 based on the * the ratio of the source frame. */ _group_bits(s->vbi, s->vid->ratio <= (14.0 / 9.0) ? 0x08 : 0x07, 29 + 24, 4); } /* 42.5μs of line 23 needs to be blanked otherwise the WSS bits may * overlap active video */ for(x = s->vid->half_width; x < s->blank_width; x++) { s->vid->output[x * 2] = s->vid->black_level; } vbidata_render_nrz(s->lut, s->vbi, -55, 137, VBIDATA_MSB_FIRST, s->vid->output, 2); } } hacktv-master/wss.h000066400000000000000000000027711357376541300146410ustar00rootroot00000000000000/* hacktv - Analogue video transmitter for the HackRF */ /*=======================================================================*/ /* Copyright 2018 Philip Heron */ /* */ /* 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 . */ #ifndef _WSS_H #define _WSS_H #include #include "video.h" typedef struct { vid_t *vid; uint8_t code; int16_t *lut; uint8_t vbi[18]; int blank_width; } wss_t; extern int wss_init(wss_t *s, vid_t *vid, char *mode); extern void wss_free(wss_t *s); extern void wss_render_line(wss_t *s); #endif