pax_global_header00006660000000000000000000000064141416406010014507gustar00rootroot0000000000000052 comment=65ca82c6320b4c7c6c2531adc6953bdbd134eeda ustreamer-4.9/000077500000000000000000000000001414164060100133725ustar00rootroot00000000000000ustreamer-4.9/.editorconfig000066400000000000000000000001711414164060100160460ustar00rootroot00000000000000root = true [*] end_of_file = lf indent_style = tab indent_size = 4 [*.{py,yaml}] indent_style = space indent_size = 4 ustreamer-4.9/LICENSE000066400000000000000000001045131414164060100144030ustar00rootroot00000000000000 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 . ustreamer-4.9/Makefile000066400000000000000000000043321414164060100150340ustar00rootroot00000000000000-include config.mk DESTDIR ?= PREFIX ?= /usr/local MANPREFIX ?= $(PREFIX)/share/man CC ?= gcc PY ?= python3 CFLAGS ?= -O3 LDFLAGS ?= RPI_VC_HEADERS ?= /opt/vc/include RPI_VC_LIBS ?= /opt/vc/lib export _LINTERS_IMAGE ?= ustreamer-linters # ===== define optbool $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1) endef # ===== all: + $(MAKE) apps ifneq ($(call optbool,$(WITH_PYTHON)),) + $(MAKE) python endif ifneq ($(call optbool,$(WITH_JANUS)),) + $(MAKE) janus endif apps: $(MAKE) -C src @ ln -sf src/ustreamer.bin ustreamer @ ln -sf src/ustreamer-dump.bin ustreamer-dump python: $(MAKE) -C python @ ln -sf python/build/lib.*/*.so . janus: $(MAKE) -C janus @ ln -sf janus/*.so . install: all $(MAKE) -C src install ifneq ($(call optbool,$(WITH_PYTHON)),) $(MAKE) -C python install endif ifneq ($(call optbool,$(WITH_JANUS)),) $(MAKE) -C janus install endif mkdir -p $(DESTDIR)$(MANPREFIX)/man1 for man in $(shell ls man); do \ install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \ gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \ done install-strip: install $(MAKE) -C src install-strip regen: tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX release: $(MAKE) clean $(MAKE) tox $(MAKE) push $(MAKE) bump V=$(V) $(MAKE) push $(MAKE) clean tox: linters time docker run --rm \ --volume `pwd`:/src:ro \ --volume `pwd`/linters:/src/linters:rw \ -t $(_LINTERS_IMAGE) bash -c " \ cd /src \ && tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \ " linters: docker build \ $(if $(call optbool,$(NC)),--no-cache,) \ --rm \ --tag $(_LINTERS_IMAGE) \ -f linters/Dockerfile linters bump: bumpversion $(if $(V),$(V),minor) push: git push git push --tags clean-all: linters clean - docker run --rm \ --volume `pwd`:/src \ -it $(_LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}" clean: rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst} rm -f ustreamer ustreamer-dump *.so $(MAKE) -C src clean $(MAKE) -C python clean $(MAKE) -C janus clean .PHONY: python janus linters ustreamer-4.9/README.md000066400000000000000000000222451414164060100146560ustar00rootroot00000000000000# µStreamer [![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI) [![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5) [[Русская версия]](README.ru.md) µStreamer is a lightweight and very quick server to stream [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible. µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are: | **Feature** | **µStreamer** | **mjpg-streamer** | |----------|---------------|-------------------| | Multithreaded JPEG encoding | ✔ | ✘ | | [OpenMAX IL](https://www.khronos.org/openmaxil) hardware acceleration
on Raspberry Pi | ✔ | ✘ | | Behavior when the device
is disconnected while streaming | ✔ Shows a black screen
with ```NO SIGNAL``` on it
until reconnected | ✘ Stops the streaming 1 | | [DV-timings](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) support -
the ability to change resolution
on the fly by source signal | ✔ | ☹ Partially yes 1 | | Option to skip frames when streaming
static images by HTTP to save the traffic | ✔ 2 | ✘ | | Streaming via UNIX domain socket | ✔ | ✘ | | Debug logs without recompiling,
performance statistics log,
access to HTTP streaming parameters | ✔ | ✘ | | Option to serve files
with a built-in HTTP server | ✔ | ☹ Regular files only | | Signaling about the stream state
on GPIO using [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ | | Access to webcam controls (focus, servos)
and settings such as brightness via HTTP | ✘ | ✔ | | Compatibility with mjpg-streamer's API | ✔ | :) | Footnotes: * ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code. * ```2``` This feature allows to cut down outgoing traffic several-fold when streaming HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to stream the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```. ----- # TL;DR If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win. ----- # Building You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libbsd``` (only for Linux). * Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`. * Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`. * Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`. On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```. ``` $ git clone --depth=1 https://github.com/pikvm/ustreamer $ cd ustreamer $ make $ ./ustreamer --help ``` AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. FreeBSD port: https://www.freshports.org/multimedia/ustreamer. ----- # Usage Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x480 resolution and start streaming on ```http://127.0.0.1:8080```. You can override this behavior using parameters ```--device```, ```--host``` and ```--port```. For example, to stream to the world, run: ``` # ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80 ``` :exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`. The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi: ```bash $ ./ustreamer \ --format=uyvy \ # Device input format --encoder=omx \ # Hardware encoding with OpenMAX --workers=3 \ # Maximum workers for OpenMAX --persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected) --dv-timings \ # Use DV-timings --drop-same-frames=30 # Save the traffic ``` :exclamation: Please note that to use `--drop-same-frames` for different browsers you need to use some specific URL `/stream` parameters (see URL `/` for details). You can always view the full list of options with ```ustreamer --help```. ----- # Raspberry Pi Camera Example Example usage for the Raspberry Pi v1 camera: ```bash $ sudo modprobe bcm2835-v4l2 $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944 ``` :exclamation: Please note that newer camera models have a different maximum resolution. You can see the supported resolutions at the [PiCamera documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes). :exclamation: If you get a poor framerate, it could be that the camera is switched to photo mode, which produces a low framerate (but a higher quality picture). This is because `bcm2835-v4l2` switches to photo mode at resolutions higher than `1280x720`. To work around this, pass the `max_video_width` and `max_video_height` module parameters like so: ```bash $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944 ``` ----- # Integrations ## Nginx When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency: ```nginx location /stream { postpone_output 0; proxy_buffering off; proxy_ignore_headers X-Accel-Buffering; proxy_pass http://ustreamer; } ``` ----- # Tips & tricks for v4l2 v4l2 utilities provide the tools to manage USB webcam setting and information. Scripts can be use to make adjustments and run manually or with cron. Running in cron for example to change the exposure settings at certain times of day. The package is available in all Linux distributions and is usually called `v4l-utils`. * List of available video devices: `v4l2-ctl --list-devices`. * List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`. * List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`. * Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`. * Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`. [Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl). ----- # See also * [Running uStreamer via systemd service](https://github.com/pikvm/ustreamer/issues/16). ----- # License Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. ustreamer-4.9/README.ru.md000066400000000000000000000342621414164060100153050ustar00rootroot00000000000000# µStreamer [![CI](https://github.com/pikvm/ustreamer/workflows/CI/badge.svg)](https://github.com/pikvm/ustreamer/actions?query=workflow%3ACI) [![Discord](https://img.shields.io/discord/580094191938437144?logo=discord)](https://discord.gg/bpmXfz5) [[English version]](README.md) µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо. Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице: | **Фича** | **µStreamer** | **mjpg-streamer** | |----------|---------------|-------------------| | Многопоточное кодирование JPEG | ✔ | ✘ | | Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ✔ | ✘ | | Поведение при физическом отключении
устройства от сервера во время работы | ✔ Транслирует черный экран
с надписью ```NO SIGNAL```,
пока устройство не будет подключено снова | ✘ Прерывает трансляцию 1 | | Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/dv-timings.html) - возможности
изменения параметров разрешения
трансляции на лету по сигналу
источника (устройства видеозахвата) | ✔ | ☹ Условно есть 1 | | Возможность пропуска фреймов при передаче
статического изображения по HTTP
для экономии трафика | ✔ 2 | ✘ | | Стрим через UNIX domain socket | ✔ | ✘ | | Дебаг-логи без перекомпиляции,
логгирование статистики производительности,
возможность получения параметров
трансляции по HTTP | ✔ | ✘ | | Возможность сервить файлы встроенным
HTTP-сервером | ✔ | ☹ Нет каталогов | | Вывод сигналов о состоянии стрима на GPIO
с помощью [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) | ✔ | ✘ | | Поддержка контролов веб-камер (фокус,
движение сервами) и всяких настроек,
типа яркости, через HTTP | ✘ | ✔ | | Совместимость с API mjpg-streamer'а | ✔ | :) | Сносочки: * ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси. * ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```. ----- # TL;DR Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро. ----- # Сборка Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libbsd``` (только для Linux). * Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`. * Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`. * Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`. На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```. ``` $ git clone --depth=1 https://github.com/pikvm/ustreamer $ cd ustreamer $ make $ ./ustreamer --help ``` Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer. ----- # Использование Будучи запущенным без аргументов, ```ustreamer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://127.0.0.1:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80-м порту: ``` # ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80 ``` :exclamation: Обратите внимание, что начиная с версии µStreamer v2.0 кросс-доменные запросы были выключены по умолчанию [по соображениям безопасности](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). Чтобы включить старое поведение, используйте опцию `--allow-origin=\*`. Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi: ```bash $ ./ustreamer \ --format=uyvy \ # Настройка входного формата устройства --encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX --workers=3 \ # Максимум воркеров для OpenMAX --persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель) --dv-timings \ # Включение DV-таймингов --drop-same-frames=30 # Экономим трафик ``` :exclamation: Обратите внимание, что для использования `--drop-same-frames` для разных браузеров нужно использовать ряд специальных параметров в `/stream` (за деталями обратитесь к урлу `/`). За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```. ----- # Камера Raspberry Pi Пример использования камеры Raspberry Pi v1: ```bash $ sudo modprobe bcm2835-v4l2 $ ./ustreamer --host :: -m jpeg --device-timeout=5 --buffers=3 -r 2592x1944 ``` :exclamation: Обратите внимание что боле новые модели камеры имеют другое максимальное разрешение. Список поддерживаемых разрешений можно найти в [документации PiCamera](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes). :exclamation: Если камера выдает низкий фреймрейт, возможно что она работает в фото-режиме, где производит более низкий фпс, но более качественную кратинку. Это происходит потому что `bcm2835-v4l2` переключает камеру в фото-режим на разрешениях выше `1280x720`. Чтобы обойти это, передайте параметры `max_video_width` и `max_video_height` при загрузке модуля, например: ```bash $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944 ``` ----- # Интеграция ## Nginx Если uStreamer находится на Nginx, то последний будет буферизировать поток и создавать дополнительную задержку в стриме. Чтобы задержки не было, буферизацию можно отключить: ```nginx location /stream { postpone_output 0; proxy_buffering off; proxy_ignore_headers X-Accel-Buffering; proxy_pass http://ustreamer; } ``` ----- # Утилиты V4L2 V4L2 предоставляет ряд официальных утилит для управления USB-вебкамерами и получения информации об устройствах. С их помощью можно писать всякие настроечные скрипты и запускать их по крону, если, например, вам требуется изменять настройки экспозиции в зависимости от времени суток. Пакет с этими утилитами доступен на всех дистрибутивах Linux и обычно называется `v4l-utils`. * Вывести список видеоустройств: `v4l2-ctl --list-devices`. * Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`. * Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`. * Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`. * Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`. Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl). ----- # Смотрите также * [Запуск с помощью systemd-сервиса](https://github.com/pikvm/ustreamer/issues/16). ----- # Лицензия Copyright (C) 2018-2021 by Maxim Devaev mdevaev@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. ustreamer-4.9/janus/000077500000000000000000000000001414164060100145125ustar00rootroot00000000000000ustreamer-4.9/janus/Makefile000066400000000000000000000017351414164060100161600ustar00rootroot00000000000000DESTDIR ?= PREFIX ?= /usr/local CC ?= gcc CFLAGS ?= -O3 LDFLAGS ?= # ===== _PLUGIN = libjanus_ustreamer.so _CFLAGS = -fPIC -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(shell pkg-config --cflags glib-2.0) $(CFLAGS) _LDFLAGS = -shared -lm -pthread -lrt -ljansson $(shell pkg-config --libs glib-2.0) $(LDFLAGS) _SRCS = $(shell ls src/*.c) _BUILD = build define optbool $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1) endef WITH_PTHREAD_NP ?= 1 ifneq ($(call optbool,$(WITH_PTHREAD_NP)),) override _CFLAGS += -DWITH_PTHREAD_NP endif # ===== $(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o) $(info == SO $@) @ $(CC) $^ -o $@ $(_LDFLAGS) $(_BUILD)/%.o: %.c $(info -- CC $<) @ mkdir -p $(dir $@) || true @ $(CC) $< -o $@ $(_CFLAGS) install: $(_PLUGIN) mkdir -p $(DESTDIR)$(PREFIX)/lib/ustreamer/janus install -m755 $(_PLUGIN) $(DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN) clean: rm -rf $(_PLUGIN) $(_BUILD) _OBJS = $(_SRCS:%.c=$(_BUILD)/%.o) -include $(_OBJS:%.o=%.d) ustreamer-4.9/janus/src/000077500000000000000000000000001414164060100153015ustar00rootroot00000000000000ustreamer-4.9/janus/src/base64.c000077700000000000000000000000001414164060100221222../../src/libs/base64.custar00rootroot00000000000000ustreamer-4.9/janus/src/base64.h000077700000000000000000000000001414164060100221342../../src/libs/base64.hustar00rootroot00000000000000ustreamer-4.9/janus/src/config.h000077700000000000000000000000001414164060100224762../../src/libs/config.hustar00rootroot00000000000000ustreamer-4.9/janus/src/frame.c000077700000000000000000000000001414164060100221362../../src/libs/frame.custar00rootroot00000000000000ustreamer-4.9/janus/src/frame.h000077700000000000000000000000001414164060100221502../../src/libs/frame.hustar00rootroot00000000000000ustreamer-4.9/janus/src/list.h000077700000000000000000000000001414164060100217122../../src/libs/list.hustar00rootroot00000000000000ustreamer-4.9/janus/src/memsinksh.h000077700000000000000000000000001414164060100237602../../src/libs/memsinksh.hustar00rootroot00000000000000ustreamer-4.9/janus/src/plugin.c000066400000000000000000000360351414164060100167520ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 #include #include #include #include #include #include #include #include "config.h" #include "tools.h" #include "threading.h" #include "list.h" #include "memsinksh.h" #include "rtp.h" static int _plugin_init(janus_callbacks *gw, const char *config_file_path); static void _plugin_destroy(void); static void _plugin_create_session(janus_plugin_session *session, int *error); static void _plugin_destroy_session(janus_plugin_session *session, int *error); static json_t *_plugin_query_session(janus_plugin_session *session); static void _plugin_setup_media(janus_plugin_session *session); static void _plugin_hangup_media(janus_plugin_session *session); static struct janus_plugin_result *_plugin_handle_message( janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep); static int _plugin_get_api_compatibility(void); static int _plugin_get_version(void); static const char *_plugin_get_version_string(void); static const char *_plugin_get_description(void); static const char *_plugin_get_name(void); static const char *_plugin_get_author(void); static const char *_plugin_get_package(void); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverride-init" static janus_plugin _plugin = JANUS_PLUGIN_INIT( .init = _plugin_init, .destroy = _plugin_destroy, .create_session = _plugin_create_session, .destroy_session = _plugin_destroy_session, .query_session = _plugin_query_session, .setup_media = _plugin_setup_media, .hangup_media = _plugin_hangup_media, .handle_message = _plugin_handle_message, .get_api_compatibility = _plugin_get_api_compatibility, .get_version = _plugin_get_version, .get_version_string = _plugin_get_version_string, .get_description = _plugin_get_description, .get_name = _plugin_get_name, .get_author = _plugin_get_author, .get_package = _plugin_get_package, ); #pragma GCC diagnostic pop janus_plugin *create(void) { // cppcheck-suppress unusedFunction return &_plugin; } typedef struct _client_sx { janus_plugin_session *session; bool transmit; LIST_STRUCT(struct _client_sx); } _client_s; static char *_g_memsink_obj = NULL; const long double _g_wait_timeout = 1; const long double _g_lock_timeout = 1; const useconds_t _g_lock_polling = 1000; const useconds_t _g_watchers_polling = 100000; static _client_s *_g_clients = NULL; static janus_callbacks *_g_gw = NULL; static rtp_s *_g_rtp = NULL; static pthread_t _g_tid; static pthread_mutex_t _g_lock = PTHREAD_MUTEX_INITIALIZER; static atomic_bool _g_ready = ATOMIC_VAR_INIT(false); static atomic_bool _g_stop = ATOMIC_VAR_INIT(false); static atomic_bool _g_has_watchers = ATOMIC_VAR_INIT(false); #define JLOG_INFO(_msg, ...) JANUS_LOG(LOG_INFO, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__) #define JLOG_WARN(_msg, ...) JANUS_LOG(LOG_WARN, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__) #define JLOG_ERROR(_msg, ...) JANUS_LOG(LOG_ERR, "== %s -- " _msg "\n", _plugin_get_name(), ##__VA_ARGS__) #define JLOG_PERROR(_msg, ...) { \ char _perror_buf[1024] = {0}; \ char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \ JANUS_LOG(LOG_ERR, "[%s] " _msg ": %s\n", _plugin_get_name(), ##__VA_ARGS__, _perror_ptr); \ } #define LOCK A_MUTEX_LOCK(&_g_lock) #define UNLOCK A_MUTEX_UNLOCK(&_g_lock) #define READY atomic_load(&_g_ready) #define STOP atomic_load(&_g_stop) #define HAS_WATCHERS atomic_load(&_g_has_watchers) static void _relay_rtp_clients(const uint8_t *datagram, size_t size) { janus_plugin_rtp packet = { .video = true, .buffer = (char *)datagram, .length = size, }; janus_plugin_rtp_extensions_reset(&packet.extensions); LIST_ITERATE(_g_clients, client, { if (client->transmit) { _g_gw->relay_rtp(client->session, &packet); } }); } static int _wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) { long double deadline_ts = get_now_monotonic() + _g_wait_timeout; long double now; do { int retval = flock_timedwait_monotonic(fd, _g_lock_timeout); now = get_now_monotonic(); if (retval < 0 && errno != EWOULDBLOCK) { JLOG_PERROR("Can't lock memsink"); return -1; } else if (retval == 0) { if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) { return 0; } if (flock(fd, LOCK_UN) < 0) { JLOG_PERROR("Can't unlock memsink"); return -1; } } usleep(_g_lock_polling); } while (now < deadline_ts); return -2; } static int _get_frame(int fd, memsink_shared_s *mem, frame_s *frame, uint64_t *frame_id) { frame_set_data(frame, mem->data, mem->used); FRAME_COPY_META(mem, frame); *frame_id = mem->id; mem->last_client_ts = get_now_monotonic(); int retval = 0; if (frame->format != V4L2_PIX_FMT_H264) { JLOG_ERROR("Got non-H264 frame from memsink"); retval = -1; } if (flock(fd, LOCK_UN) < 0) { JLOG_PERROR("Can't unlock memsink"); retval = -1; } return retval; } static void *_clients_thread(UNUSED void *arg) { A_THREAD_RENAME("us_clients"); atomic_store(&_g_ready, true); frame_s *frame = frame_init(); uint64_t frame_id = 0; unsigned error_reported = 0; # define IF_NOT_REPORTED(_error, ...) { \ if (error_reported != _error) { __VA_ARGS__; error_reported = _error; } \ } while (!STOP) { if (!HAS_WATCHERS) { IF_NOT_REPORTED(1, { JLOG_INFO("No active watchers, memsink disconnected"); }); usleep(_g_watchers_polling); continue; } int fd = -1; memsink_shared_s *mem = NULL; if ((fd = shm_open(_g_memsink_obj, O_RDWR, 0)) <= 0) { IF_NOT_REPORTED(2, { JLOG_PERROR("Can't open memsink"); }); goto close_memsink; } if ((mem = memsink_shared_map(fd)) == NULL) { IF_NOT_REPORTED(3, { JLOG_PERROR("Can't map memsink"); }); goto close_memsink; } error_reported = 0; JLOG_INFO("Memsink opened; reading frames ..."); while (!STOP && HAS_WATCHERS) { int result = _wait_frame(fd, mem, frame_id); if (result == 0) { if (_get_frame(fd, mem, frame, &frame_id) != 0) { goto close_memsink; } LOCK; rtp_wrap_h264(_g_rtp, frame, _relay_rtp_clients); UNLOCK; } else if (result == -1) { goto close_memsink; } } close_memsink: if (mem != NULL) { JLOG_INFO("Memsink closed"); memsink_shared_unmap(mem); mem = NULL; } if (fd > 0) { close(fd); fd = -1; } sleep(1); // error_delay } # undef IF_NOT_REPORTED frame_destroy(frame); return NULL; } static int _read_config(const char *config_dir_path) { char *config_file_path; janus_config *config = NULL; A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, _plugin_get_package()); JLOG_INFO("Reading config file '%s' ...", config_file_path); config = janus_config_parse(config_file_path); if (config == NULL) { JLOG_ERROR("Can't read config"); goto error; } janus_config_print(config); janus_config_category *config_memsink = janus_config_get_create(config, NULL, janus_config_type_category, "memsink"); janus_config_item *config_memsink_obj = janus_config_get(config, config_memsink, janus_config_type_item, "object"); if (config_memsink_obj == NULL || config_memsink_obj->value == NULL || config_memsink_obj->value[0] == '\0') { JLOG_ERROR("Missing config value: memsink.object"); goto error; } _g_memsink_obj = strdup(config_memsink_obj->value); int retval = 0; goto ok; error: retval = -1; ok: if (config) { janus_config_destroy(config); } free(config_file_path); return retval; } static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) { // https://groups.google.com/g/meetecho-janus/c/xoWIQfaoJm8 // sysctl -w net.core.rmem_default=500000 // sysctl -w net.core.wmem_default=500000 // sysctl -w net.core.rmem_max=1000000 // sysctl -w net.core.wmem_max=1000000 JLOG_INFO("Initializing plugin ..."); assert(!READY); assert(!STOP); if (gw == NULL || config_dir_path == NULL || _read_config(config_dir_path) < 0) { return -1; } _g_gw = gw; _g_rtp = rtp_init(); A_THREAD_CREATE(&_g_tid, _clients_thread, NULL); return 0; } static void _plugin_destroy(void) { JLOG_INFO("Destroying plugin ..."); atomic_store(&_g_stop, true); if (READY) { A_THREAD_JOIN(_g_tid); } LIST_ITERATE(_g_clients, client, { LIST_REMOVE(_g_clients, client); free(client); }); _g_clients = NULL; rtp_destroy(_g_rtp); _g_rtp = NULL; _g_gw = NULL; if (_g_memsink_obj) { free(_g_memsink_obj); _g_memsink_obj = NULL; } } #define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } } static void _plugin_create_session(janus_plugin_session *session, int *error) { IF_DISABLED({ *error = -1; return; }); LOCK; JLOG_INFO("Creating session %p ...", session); _client_s *client; A_CALLOC(client, 1); client->session = session; client->transmit = true; LIST_APPEND(_g_clients, client); atomic_store(&_g_has_watchers, true); UNLOCK; } static void _plugin_destroy_session(janus_plugin_session* session, int *error) { IF_DISABLED({ *error = -1; return; }); LOCK; bool found = false; bool has_watchers = false; LIST_ITERATE(_g_clients, client, { if (client->session == session) { JLOG_INFO("Removing session %p ...", session); LIST_REMOVE(_g_clients, client); free(client); found = true; } else { has_watchers = (has_watchers || client->transmit); } }); if (!found) { JLOG_WARN("No session %p", session); *error = -2; } atomic_store(&_g_has_watchers, has_watchers); UNLOCK; } static json_t *_plugin_query_session(janus_plugin_session *session) { IF_DISABLED({ return NULL; }); json_t *info = NULL; LOCK; LIST_ITERATE(_g_clients, client, { if (client->session == session) { info = json_string("session_found"); break; } }); UNLOCK; return info; } static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) { IF_DISABLED({ return; }); LOCK; bool found = false; bool has_watchers = false; LIST_ITERATE(_g_clients, client, { if (client->session == session) { client->transmit = transmit; //JLOG_INFO("%s session %p", msg, session); found = true; } has_watchers = (has_watchers || client->transmit); }); if (!found) { JLOG_WARN("No session %p", session); } atomic_store(&_g_has_watchers, has_watchers); UNLOCK; } #undef IF_DISABLED static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); } static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); } static struct janus_plugin_result *_plugin_handle_message( janus_plugin_session *session, char *transaction, json_t *msg, json_t *jsep) { assert(transaction != NULL); # define FREE_MSG_JSEP { \ if (msg) json_decref(msg); \ if (jsep) json_decref(jsep); \ } if (session == NULL || msg == NULL) { free(transaction); FREE_MSG_JSEP; return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL); } # define PUSH_ERROR(_error, _reason) { \ /*JLOG_ERROR("Message error in session %p: %s", session, _reason);*/ \ json_t *_event = json_object(); \ json_object_set_new(_event, "ustreamer", json_string("event")); \ json_object_set_new(_event, "error_code", json_integer(_error)); \ json_object_set_new(_event, "error", json_string(_reason)); \ _g_gw->push_event(session, &_plugin, transaction, _event, NULL); \ json_decref(_event); \ } json_t *request_obj = json_object_get(msg, "request"); if (request_obj == NULL) { PUSH_ERROR(400, "Request missing"); goto ok_wait; } const char *request_str = json_string_value(request_obj); if (!request_str) { PUSH_ERROR(400, "Request not a string"); goto ok_wait; } //JLOG_INFO("Message: %s", request_str); # define PUSH_STATUS(_status, _jsep) { \ json_t *_event = json_object(); \ json_object_set_new(_event, "ustreamer", json_string("event")); \ json_t *_result = json_object(); \ json_object_set_new(_result, "status", json_string(_status)); \ json_object_set_new(_event, "result", _result); \ _g_gw->push_event(session, &_plugin, transaction, _event, _jsep); \ json_decref(_event); \ } if (!strcmp(request_str, "start")) { PUSH_STATUS("started", NULL); } else if (!strcmp(request_str, "stop")) { PUSH_STATUS("stopped", NULL); } else if (!strcmp(request_str, "watch")) { char *sdp = rtp_make_sdp(_g_rtp); if (sdp == NULL) { PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet"); goto ok_wait; } //JLOG_INFO("SDP generated:\n%s", sdp); json_t *offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp); free(sdp); PUSH_STATUS("started", offer_jsep); json_decref(offer_jsep); } else { PUSH_ERROR(405, "Not implemented"); } ok_wait: FREE_MSG_JSEP; return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL); # undef PUSH_ERROR # undef FREE_MSG_JSEP } static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; } static int _plugin_get_version(void) { return VERSION_U; } static const char *_plugin_get_version_string(void) { return VERSION; } static const char *_plugin_get_description(void) { return "Pi-KVM uStreamer Janus plugin for H.264 video"; } static const char *_plugin_get_name(void) { return "ustreamer"; } static const char *_plugin_get_author(void) { return "Maxim Devaev "; } static const char *_plugin_get_package(void) { return "janus.plugin.ustreamer"; } #undef STOP #undef READY #undef UNLOCK #undef LOCK #undef JLOG_PERROR #undef JLOG_ERROR #undef JLOG_WARN #undef JLOG_INFO ustreamer-4.9/janus/src/rtp.c000066400000000000000000000152431414164060100162570ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # This source file is partially based on this code: # # - https://github.com/catid/kvm/blob/master/kvm_pipeline/src # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "rtp.h" #define PAYLOAD 96 // Payload type #define PRE 3 // Annex B prefix length void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback); static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked); static ssize_t _find_annexb(const uint8_t *data, size_t size); rtp_s *rtp_init(void) { rtp_s *rtp; A_CALLOC(rtp, 1); rtp->ssrc = triple_u32(get_now_monotonic_u64()); rtp->sps = frame_init(); rtp->pps = frame_init(); A_MUTEX_INIT(&rtp->mutex); return rtp; } void rtp_destroy(rtp_s *rtp) { A_MUTEX_DESTROY(&rtp->mutex); frame_destroy(rtp->pps); frame_destroy(rtp->sps); free(rtp); } char *rtp_make_sdp(rtp_s *rtp) { A_MUTEX_LOCK(&rtp->mutex); if (rtp->sps->used == 0 || rtp->pps->used == 0) { A_MUTEX_UNLOCK(&rtp->mutex); return NULL; } char *sps = NULL; char *pps = NULL; base64_encode(rtp->sps->data, rtp->sps->used, &sps, NULL); base64_encode(rtp->pps->data, rtp->pps->used, &pps, NULL); A_MUTEX_UNLOCK(&rtp->mutex); // https://tools.ietf.org/html/rfc6184 // https://github.com/meetecho/janus-gateway/issues/2443 char *sdp; A_ASPRINTF(sdp, "v=0" RN "o=- %" PRIu64 " 1 IN IP4 127.0.0.1" RN "s=Pi-KVM uStreamer" RN "t=0 0" RN "m=video 1 RTP/SAVPF %d" RN "c=IN IP4 0.0.0.0" RN "a=rtpmap:%d H264/90000" RN "a=fmtp:%d profile-level-id=42E01F" RN "a=fmtp:%d packetization-mode=1" RN "a=fmtp:%d sprop-sps=%s" RN "a=fmtp:%d sprop-pps=%s" RN "a=rtcp-fb:%d nack" RN "a=rtcp-fb:%d nack pli" RN "a=rtcp-fb:%d goog-remb" RN "a=sendonly" RN, get_now_id() >> 1, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, sps, PAYLOAD, pps, PAYLOAD, PAYLOAD, PAYLOAD ); free(sps); free(pps); return sdp; } void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) { // There is a complicated logic here but everything works as it should: // - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775 assert(frame->format == V4L2_PIX_FMT_H264); const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz ssize_t last_offset = -PRE; while (true) { // Find and iterate by nalus const size_t next_start = last_offset + PRE; ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start); if (offset < 0) { break; } offset += next_start; if (last_offset >= 0) { const uint8_t *data = frame->data + last_offset + PRE; size_t size = offset - last_offset - PRE; if (data[size - 1] == 0) { // Check for extra 00 --size; } _rtp_process_nalu(rtp, data, size, pts, false, callback); } last_offset = offset; } if (last_offset >= 0) { const uint8_t *data = frame->data + last_offset + PRE; size_t size = frame->used - last_offset - PRE; _rtp_process_nalu(rtp, data, size, pts, true, callback); } } void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) { const unsigned ref_idc = (data[0] >> 5) & 3; const unsigned type = data[0] & 0x1F; frame_s *ps = NULL; switch (type) { case 7: ps = rtp->sps; break; case 8: ps = rtp->pps; break; } if (ps) { A_MUTEX_LOCK(&rtp->mutex); frame_set_data(ps, data, size); A_MUTEX_UNLOCK(&rtp->mutex); } # define HEADER_SIZE 12 if (size + HEADER_SIZE <= RTP_DATAGRAM_SIZE) { _rtp_write_header(rtp, pts, marked); memcpy(rtp->datagram + HEADER_SIZE, data, size); callback(rtp->datagram, size + HEADER_SIZE); return; } const size_t fu_overhead = HEADER_SIZE + 2; // FU-A overhead const uint8_t *src = data + 1; ssize_t remaining = size - 1; bool first = true; while (remaining > 0) { ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead; const bool last = (remaining <= frag_size); if (last) { frag_size = remaining; } _rtp_write_header(rtp, pts, (marked && last)); rtp->datagram[HEADER_SIZE] = 28 | (ref_idc << 5); uint8_t fu = type; if (first) { fu |= 0x80; } if (last) { fu |= 0x40; } rtp->datagram[HEADER_SIZE + 1] = fu; memcpy(rtp->datagram + fu_overhead, src, frag_size); callback(rtp->datagram, fu_overhead + frag_size); src += frag_size; remaining -= frag_size; first = false; } # undef HEADER_SIZE } static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) { uint32_t word0 = 0x80000000; if (marked) { word0 |= 1 << 23; } word0 |= (PAYLOAD & 0x7F) << 16; word0 |= rtp->seq; ++rtp->seq; # define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value) WRITE_BE_U32(0, word0); WRITE_BE_U32(4, pts); WRITE_BE_U32(8, rtp->ssrc); # undef WRITE_BE_U32 } static ssize_t _find_annexb(const uint8_t *data, size_t size) { // Parses buffer for 00 00 01 start codes if (size >= PRE) { for (size_t index = 0; index <= size - PRE; ++index) { if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) { return index; } } } return -1; } #undef PRE #undef PAYLOAD ustreamer-4.9/janus/src/rtp.h000066400000000000000000000051731414164060100162650ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # This source file is partially based on this code: # # - https://github.com/catid/kvm/blob/master/kvm_pipeline/src # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include "tools.h" #include "threading.h" #include "frame.h" #include "base64.h" // https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes #define RTP_DATAGRAM_SIZE 1200 typedef struct { uint32_t ssrc; uint16_t seq; uint8_t datagram[RTP_DATAGRAM_SIZE]; frame_s *sps; // Actually not a frame, just a bytes storage frame_s *pps; pthread_mutex_t mutex; } rtp_s; typedef void (*rtp_callback_f)(const uint8_t *datagram, size_t size); rtp_s *rtp_init(void); void rtp_destroy(rtp_s *rtp); char *rtp_make_sdp(rtp_s *rtp); void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback); ustreamer-4.9/janus/src/threading.h000077700000000000000000000000001414164060100236762../../src/libs/threading.hustar00rootroot00000000000000ustreamer-4.9/janus/src/tools.h000077700000000000000000000000001414164060100222642../../src/libs/tools.hustar00rootroot00000000000000ustreamer-4.9/linters/000077500000000000000000000000001414164060100150525ustar00rootroot00000000000000ustreamer-4.9/linters/.dockerignore000066400000000000000000000000251414164060100175230ustar00rootroot00000000000000/.tox/ /.mypy_cache/ ustreamer-4.9/linters/Dockerfile000066400000000000000000000010521414164060100170420ustar00rootroot00000000000000FROM archlinux/archlinux:base-devel RUN mkdir -p /etc/pacman.d/hooks \ && ln -s /dev/null /etc/pacman.d/hooks/30-systemd-tmpfiles.hook RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist RUN pacman -Syu --noconfirm \ && pacman -S --needed --noconfirm \ vim \ git \ libjpeg \ libevent \ libutil-linux \ libbsd \ python \ python-pip \ python-tox \ cppcheck \ npm \ && (pacman -Sc --noconfirm || true) \ && rm -rf /var/cache/pacman/pkg/* RUN npm install htmlhint -g CMD /bin/bash ustreamer-4.9/linters/cppcheck.h000066400000000000000000000001451414164060100170030ustar00rootroot00000000000000#define CHAR_BIT 8 #define WITH_OMX #define WITH_GPIO #define JANUS_PLUGIN_INIT(...) { __VA_ARGS__ } ustreamer-4.9/linters/flake8.ini000066400000000000000000000005111414164060100167220ustar00rootroot00000000000000[flake8] inline-quotes = double max-line-length = 160 ignore = W503, E227, E241, E252, Q003 # W503 line break before binary operator # E227 missing whitespace around bitwise or shift operator # E241 multiple spaces after # E252 missing whitespace around parameter equals # Q003 Change outer quotes to avoid escaping inner quotes ustreamer-4.9/linters/mypy.ini000066400000000000000000000001561414164060100165530ustar00rootroot00000000000000[mypy] python_version = 3.9 ignore_missing_imports = true disallow_untyped_defs = true strict_optional = true ustreamer-4.9/linters/pylint.ini000066400000000000000000000026651414164060100171030ustar00rootroot00000000000000[MASTER] ignore = .git [DESIGN] min-public-methods = 0 max-args = 10 [TYPECHECK] ignored-classes= AioQueue, [MESSAGES CONTROL] disable = file-ignored, locally-disabled, fixme, missing-docstring, no-init, no-self-use, superfluous-parens, abstract-class-not-used, abstract-class-little-used, duplicate-code, bad-continuation, bad-whitespace, star-args, broad-except, redundant-keyword-arg, wrong-import-order, too-many-ancestors, no-else-return, len-as-condition, unspecified-encoding, [REPORTS] msg-template = {symbol} -- {path}:{line}({obj}): {msg} [FORMAT] max-line-length = 160 [BASIC] # List of builtins function names that should not be used, separated by a comma bad-functions = # Good variable names which should always be accepted, separated by a comma good-names = _, __, x, y, ws, make-html-h, make-jpeg-h # Regular expression matching correct method names method-rgx = [a-z_][a-z0-9_]{2,50}$ # Regular expression matching correct function names function-rgx = [a-z_][a-z0-9_]{2,50}$ # Regular expression which should only match correct module level names const-rgx = ([a-zA-Z_][a-zA-Z0-9_]*)$ # Regular expression which should only match correct argument names argument-rgx = [a-z_][a-z0-9_]{1,30}$ # Regular expression which should only match correct variable names variable-rgx = [a-z_][a-z0-9_]{1,30}$ # Regular expression which should only match correct instance attribute names attr-rgx = [a-z_][a-z0-9_]{1,30}$ ustreamer-4.9/linters/tox.ini000066400000000000000000000023141414164060100163650ustar00rootroot00000000000000[tox] envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint skipsdist = true [testenv] basepython = python3.9 changedir = /src [testenv:cppcheck] whitelist_externals = cppcheck commands = cppcheck \ --force \ --std=c11 \ --error-exitcode=1 \ --quiet \ --enable=warning,unusedFunction,portability,performance,style \ --suppress=assignmentInAssert \ --suppress=variableScope \ --inline-suppr \ --library=python \ --include=linters/cppcheck.h \ src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c [testenv:flake8] whitelist_externals = bash commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py deps = flake8 flake8-quotes [testenv:pylint] whitelist_externals = bash commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py' deps = pylint [testenv:mypy] whitelist_externals = bash commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py' deps = mypy [testenv:vulture] whitelist_externals = bash commands = bash -c 'vulture tools/*.py python/*.py' deps = vulture [testenv:htmlhint] whitelist_externals = htmlhint commands = htmlhint src/ustreamer/http/data/*.html ustreamer-4.9/man/000077500000000000000000000000001414164060100141455ustar00rootroot00000000000000ustreamer-4.9/man/ustreamer-dump.1000066400000000000000000000046561414164060100172140ustar00rootroot00000000000000.\" Manpage for ustreamer-dump. .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .TH USTREAMER-DUMP 1 "version 4.9" "January 2021" .SH NAME ustreamer-dump \- Dump uStreamer's memory sink to file .SH SYNOPSIS .B ustreamer-dump .RI [OPTIONS] .SH DESCRIPTION µStreamer-dump (\fBustreamer-dump\fP) writes a local stream from ustreamer to a file or redirect it to other utilities (such as \fBffmpeg\fR). .SH USAGE \fBustreamer\fR requires at least the \fB\-\-sink\fR option to operate. To output ustreamers sink "test" to ffmpeg, and into a file called test.mp4: \fBustreamer-dump \e\fR .RS \fB\-\-sink=test \e\fR # Use ustreamer sink "test" .nf \fB\-\-output\ \- \e\fR # Output to stdout \fB|\ ffmpeg\ \-use_wallclock_as_timestamps\ 1\ \-i\ pipe:\ \-c:v\ libx264\ test\.mp4\fR .SH OPTIONS .SS "Sink options" .TP .BR \-s ", " \-\-sink\ \fIname Memory sink ID. No default. .TP .BR \-t ", " \-\-sink\-timeout\ \fIsec Timeout for the upcoming frame. Default: 1. .TP .BR \-o ", " \-\-output\ \fIfilename Filename to dump output to. Use '-' for stdout. Default: just consume the sink. .TP .BR \-j ", " \-\-output-json Format output as JSON. Required option --output. Default: disabled. .TP .BR \-c ", " \-\-count\ \fIN Limit the number of frames. Default: 0 (infinite). .TP .BR \-i ", "\-\-interval\ \fIsec Delay between reading frames (float). Default: 0. .SS "Logging options" .TP .BR \-\-log\-level\ \fIN Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program. Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug). Default: 0. .TP .BR \-\-perf Enable performance messages (same as \-\-log\-level=1). Default: disabled. .TP .BR \-\-verbose Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled. .TP .BR \-\-debug Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled. .TP .BR \-\-force\-log\-colors Force color logging. Default: colored if stderr is a TTY. .TP .BR \-\-no\-log\-colors Disable color logging. Default: ditto. .SS "Help options" .TP .BR \-h ", " \-\-help Print this text and exit. .TP .BR \-v ", " \-\-version Print version and exit. .SH "SEE ALSO" .BR ustreamer (1) .SH BUGS Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR .SH AUTHOR Maxim Devaev .SH HOMEPAGE \fIhttps://pikvm.org/\fR .SH COPYRIGHT GNU General Public License v3.0 ustreamer-4.9/man/ustreamer.1000066400000000000000000000266511414164060100162500ustar00rootroot00000000000000.\" Manpage for ustreamer. .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .TH USTREAMER 1 "version 4.9" "November 2020" .SH NAME ustreamer \- stream MJPG video from any V4L2 device to the network .SH SYNOPSIS .B ustreamer .RI [OPTIONS] .SH DESCRIPTION µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible. .SH USAGE Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR Please note that since µStreamer v2\.0 cross\-domain requests were disabled by default for security reasons\. To enable the old behavior, use the option \fB\-\-allow\-origin=\e*\fR\. For example, the recommended way of running µStreamer with Auvidea B101 on a Raspberry Pi is: \fBustreamer \e\fR .RS \fB\-\-format=uyvy \e\fR # Device input format .nf \fB\-\-encoder=omx \e\fR # Hardware encoding with OpenMAX .nf \fB\-\-workers=3 \e\fR # Maximum workers for OpenMAX .nf \fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected) .nf \fB\-\-dv\-timings \e\fR # Use DV\-timings .nf \fB\-\-drop\-same\-frames=30\fR # Save the traffic\fR .RE .P Please note that to use \fB\-\-drop\-same\-frames\fR for different browsers you need to use some specific URL \fB/stream\fR parameters (see URL \fB/\fR for details)\. .P You can always view the full list of options with \fBustreamer \-\-help\fR\. Some features may not be available on your platform. To find out which features are enabled, use \fBustreamer \-\-features\fR. .SH OPTIONS .SS "Capturing options" .TP .BR \-d\ \fI/dev/path ", " \-\-device\ \fI/dev/path Path to V4L2 device. Default: /dev/video0. .TP .BR \-i\ \fIN ", " \-\-input\ \fIN Input channel. Default: 0. .TP .BR \-r\ \fIWxH ", " \-\-resolution\ \fIWxH Initial image resolution. Default: 640x480. .TP .BR \-m\ \fIfmt ", " \-\-format\ \fIfmt Image format. Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV. .TP .BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd Force TV standard. Available: PAL, NTSC, SECAM; default: disabled. .TP .BR \-I\ \fImethod ", " \-\-io\-method\ \fImethod Set V4L2 IO method (see kernel documentation). Changing of this parameter may increase the performance. Or not. Available: MMAP, USERPTR; default: MMAP. .TP .BR \-f\ \fIN ", " \-\-desired\-fps\ \fIN Desired FPS. Default: maximum possible. .TP .BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes. .TP .BR \-n ", " \-\-persistent Don't re\-initialize device on timeout. Default: disabled. .TP .BR \-t ", " \-\-dv\-timings Enable DV timings querying and events processing to automatic resolution change. Default: disabled. .TP .BR \-b\ \fIN ", " \-\-buffers\ \fIN The number of buffers to receive data from the device. Each buffer may processed using an independent thread. Default: 2 (the number of CPU cores (but not more than 4) + 1). .TP .BR \-w\ \fIN ", " \-\-workers\ \fIN The number of worker threads but not more than buffers. Default: 1 (the number of CPU cores (but not more than 4)). .TP .BR \-q\ \fIN ", " \-\-quality\ \fIN Set quality of JPEG encoding from 1 to 100 (best). Default: 80. Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPG to MJPG to change the quality level for sources that already output MJPG. .TP .BR \-c\ \fItype ", " \-\-encoder\ \fItype Use specified encoder. It may affect the number of workers. CPU ─ Software MJPG encoding (default). OMX ─ GPU hardware accelerated MJPG encoding with OpenMax (required \fBWITH_OMX\fR feature). HW ─ Use pre-encoded MJPG frames directly from camera hardware. NOOP ─ Don't compress MJPG stream (do nothing). .TP .BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,... It doesn't do anything. Still here for compatibility. Required \fBWITH_OMX\fR feature. .TP .BR \-k\ \fIpath ", " \-\-blank\ \fIpath Path to JPEG file that will be shown when the device is disconnected during the streaming. Default: black screen 640x480 with 'NO SIGNAL'. .TP .BR \-K\ \fIsec ", " \-\-last\-as\-blank\ \fIsec Show the last frame received from the camera after it was disconnected, but no more than specified time (or endlessly if 0 is specified). If the device has not yet been online, display 'NO SIGNAL' or the image specified by option \-\-blank. Note: currently this option has no effect on memory sinks. Default: disabled. .TP .BR \-l ", " \-\-slowdown Slowdown capturing to 1 FPS or less when no stream or sink clients are connected. Useful to reduce CPU consumption. Default: disabled. .TP .BR \-\-device\-timeout\ \fIsec Timeout for device querying. Default: 1. .TP .BR \-\-device\-error\-delay\ \fIsec Delay before trying to connect to the device again after an error (timeout for example). Default: 1. .SS "Image control options" .TP .BR \-\-image\-default Reset all image settings below to default. Default: no change. .TP .BR \-\-brightness\ \fIN ", " \fIauto ", " \fIdefault Set brightness. Default: no change. .TP .BR \-\-contrast\ \fIN ", " \fIdefault Set contrast. Default: no change. .TP .BR \-\-saturation\ \fIN ", " \fIdefault Set saturation. Default: no change. .TP .BR \-\-hue\ \fIN ", " \fIauto ", " \fIdefault Set hue. Default: no change. .TP .BR \-\-gamma\ \fIN ", " \fIdefault Set gamma. Default: no change. .TP .BR \-\-sharpness\ \fIN ", " \fIdefault Set sharpness. Default: no change. .TP .BR \-\-backlight\-compensation\ \fIN ", " \fIdefault Set backlight compensation. Default: no change. .TP .BR \-\-white\-balance\ \fIN ", " \fIauto ", " \fIdefault Set white balance. Default: no change. .TP .BR \-\-gain\ \fIN ", " \fIauto ", " \fIdefault Set gain. Default: no change. .TP .BR \-\-color\-effect\ \fIN ", " \fIdefault Set color effect. Default: no change. .TP .BR \-\-flip\-vertical\ \fI1 ", " \fI0 ", " \fIdefault Set vertical flip. Default: no change. .TP .BR \-\-flip\-horizontal\ \fI1 ", " \fI0 ", " \fIdefault Set horizontal flip. Default: no change. .SS "HTTP server options" .TP .BR \-s\ \fIaddress ", " \-\-host\ \fIaddress Listen on Hostname or IP. Default: 127.0.0.1. .TP .BR \-p\ \fIN ", " \-\-port\ \fIN Bind to this TCP port. Default: 8080. .TP .BR \-U\ \fIpath ", " \-\-unix\ \fIpath Bind to UNIX domain socket. Default: disabled. .TP .BR \-d ", " \-\-unix\-rm Try to remove old unix socket file before binding. default: disabled. .TP .BR \-M\ \fImode ", " \-\-unix\-mode\ \fImode Set UNIX socket file permissions (like 777). Default: disabled. .TP .BR \-\-user\ \fIname HTTP basic auth user. Default: disabled. .TP .BR \-\-passwd\ \fIstr HTTP basic auth passwd. Default: empty. .TP .BR \-\-static\ \fIpath Path to dir with static files instead of embedded root index page. Symlinks are not supported for security reasons. Default: disabled. .TP .BR \-e\ \fIN ", " \-\-drop\-same\-frames\ \fIN Don't send identical frames to clients, but no more than specified number. It can significantly reduce the outgoing traffic, but will increase the CPU loading. Don't use this option with analog signal sources or webcams, it's useless. Default: disabled. .TP .BR \-R\ \fIWxH ", " \-\-fake\-resolution\ \fIWxH Override image resolution for the /state. Default: disabled. .TP .BR \-\-tcp\-nodelay Set TCP_NODELAY flag to the client /stream socket. Ignored for \-\-unix. Default: disabled. .TP .BR \-\-allow\-origin\ \fIstr Set Access\-Control\-Allow\-Origin header. Default: disabled. .TP .BR \-\-server\-timeout\ \fIsec Timeout for client connections. Default: 10. .SS "JPEG sink options" With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info. .TP .BR \-\-sink\ \fIname Use the specified shared memory object to sink JPEG frames. Default: disabled. .TP .BR \-\-sink\-mode\ \fImode Set JPEG sink permissions (like 777). Default: 660. .TP .BR \-\-sink\-rm Remove shared memory on stop. Default: disabled. .TP .BR \-\-sink\-client\-ttl\ \fIsec Client TTL. Default: 10. .TP .BR \-\-sink\-timeout\ \fIsec Timeout for lock. Default: 1. .SS "H264 sink options" .TP Available only if \fBWITH_OMX\fR feature enabled. .TP .BR \-\-h264\-sink\ \fIname Use the specified shared memory object to sink H264 frames encoded by MMAL. Default: disabled. .TP .BR \-\-h264\-sink\-mode\ \fImode Set H264 sink permissions (like 777). Default: 660. .TP .BR \-\-h264\-sink\-rm Remove shared memory on stop. Default: disabled. .TP .BR \-\-h264\-sink\-client\-ttl\ \fIsec Client TTL. Default: 10. .TP .BR \-\-h264\-sink\-timeout\ \fIsec Timeout for lock. Default: 1. .TP .BR \-\-h264\-bitrate\ \fIkbps H264 bitrate in Kbps. Default: 5000. .TP .BR \-\-h264\-gop\ \fIN Intarval between keyframes. Default: 30. .SS "Process options" .TP .BR \-\-exit\-on\-parent\-death Exit the program if the parent process is dead. Required \fBHAS_PDEATHSIG\fR feature. Default: disabled. .TP .BR \-\-process\-name\-prefix\ \fIstr Set process name prefix which will be displayed in the process list like '\fIstr: ustreamer \-\-blah\-blah\-blah'\fR. Required \fBWITH_SETPROCTITLE\fR feature. Default: disabled. .TP .BR \-\-notify\-parent Send SIGUSR2 to the parent process when the stream parameters are changed. Checking changes is performed for the online flag and image resolution. Required \fBWITH_SETPROCTITLE\fR feature. .SS "GPIO options" Available only if \fBWITH_GPIO\fR feature enabled. .TP .BR \-\-gpio\-device\ \fI/dev/path Path to GPIO character device. Default: /dev/gpiochip0. .TP .BR \-\-gpio\-consumer\-prefix\ \fIstr Consumer prefix for GPIO outputs. Default: ustreamer. .TP .BR \-\-gpio\-prog\-running\ \fIpin Set 1 on GPIO pin while µStreamer is running. Default: disabled. .TP .BR \-\-gpio\-stream\-online\ \fIpin Set 1 while streaming. Default: disabled. .TP .BR \-\-gpio\-has\-http\-clients\ \fIpin Set 1 while stream has at least one client. Default: disabled. .SS "Logging options" .TP .BR \-\-log\-level\ \fIN Verbosity level of messages from 0 (info) to 3 (debug). Enabling debugging messages can slow down the program. Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug). Default: 0. .TP .BR \-\-perf Enable performance messages (same as \-\-log\-level=1). Default: disabled. .TP .BR \-\-verbose Enable verbose messages and lower (same as \-\-log\-level=2). Default: disabled. .TP .BR \-\-debug Enable debug messages and lower (same as \-\-log\-level=3). Default: disabled. .TP .BR \-\-force\-log\-colors Force color logging. Default: colored if stderr is a TTY. .TP .BR \-\-no\-log\-colors Disable color logging. Default: ditto. .SS "Help options" .TP .BR \-h ", " \-\-help Print this text and exit. .TP .BR \-v ", " \-\-version Print version and exit. .TP .BR \-\-features Print list of supported features. .SH "SEE ALSO" .BR ustreamer-dump (1) .SH BUGS Please file any bugs and issues at \fIhttps://github.com/pikvm/ustreamer/issues\fR .SH AUTHOR Maxim Devaev .SH HOMEPAGE \fIhttps://pikvm.org/\fR .SH COPYRIGHT GNU General Public License v3.0 ustreamer-4.9/pkg/000077500000000000000000000000001414164060100141535ustar00rootroot00000000000000ustreamer-4.9/pkg/arch/000077500000000000000000000000001414164060100150705ustar00rootroot00000000000000ustreamer-4.9/pkg/arch/PKGBUILD000066400000000000000000000024721414164060100162210ustar00rootroot00000000000000# Contributor: Maxim Devaev # Author: Maxim Devaev pkgname=ustreamer pkgver=4.9 pkgrel=1 pkgdesc="Lightweight and fast MJPG-HTTP streamer" url="https://github.com/pikvm/ustreamer" license=(GPL) arch=(i686 x86_64 armv6h armv7h aarch64) depends=(libjpeg libevent libbsd libgpiod) makedepends=(gcc make) source=(${pkgname}::"git+https://github.com/pikvm/ustreamer#commit=v${pkgver}") md5sums=(SKIP) _options="WITH_GPIO=1" if [ -e /usr/bin/python3 ]; then _options="$_options WITH_PYTHON=1" depends+=(python) makedepends+=(python-setuptools) fi if [ -e /opt/vc/include/IL/OMX_Core.h ]; then depends+=(raspberrypi-firmware) makedepends+=(raspberrypi-firmware) _options="$_options WITH_OMX=1" fi if [ -e /usr/include/janus/plugins/plugin.h ];then depends+=(janus-gateway-pikvm) makedepends+=(janus-gateway-pikvm) _options="$_options WITH_JANUS=1" fi # LD does not link mmal with this option # This DOESN'T affect setup.py LDFLAGS="${LDFLAGS//--as-needed/}" export LDFLAGS="${LDFLAGS//,,/,}" build() { cd "$srcdir" rm -rf $pkgname-build cp -r $pkgname $pkgname-build cd $pkgname-build make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS } package() { cd "$srcdir/$pkgname-build" make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" DESTDIR="$pkgdir" PREFIX=/usr install } ustreamer-4.9/pkg/docker/000077500000000000000000000000001414164060100154225ustar00rootroot00000000000000ustreamer-4.9/pkg/docker/Dockerfile.arm.cross000066400000000000000000000014241414164060100213230ustar00rootroot00000000000000FROM balenalib/raspberrypi3-debian:build as build RUN ["cross-build-start"] RUN apt-get update \ && apt-get install -y --no-install-recommends \ gcc \ libjpeg8-dev \ libbsd-dev \ libraspberrypi-dev \ libgpiod-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /build/ustreamer/ COPY . . RUN make -j5 WITH_OMX=1 WITH_GPIO=1 RUN ["cross-build-end"] FROM balenalib/raspberrypi3-debian:run as RUN RUN ["cross-build-start"] RUN apt-get update \ && apt-get install -y --no-install-recommends \ libevent-2.1 \ libevent-pthreads-2.1-6 \ libjpeg8 \ libbsd0 \ libgpiod2 \ && rm -rf /var/lib/apt/lists/* RUN ["cross-build-end"] WORKDIR /ustreamer COPY --from=build /build/ustreamer/ustreamer . EXPOSE 8080 ENTRYPOINT ["./ustreamer", "--host=::"] # vim: syntax=dockerfile ustreamer-4.9/pkg/docker/Dockerfile.arm.native000066400000000000000000000012551414164060100214620ustar00rootroot00000000000000FROM balenalib/raspberrypi3-debian:build as build RUN apt-get update \ && apt-get install -y --no-install-recommends \ gcc \ libjpeg8-dev \ libbsd-dev \ libraspberrypi-dev \ libgpiod-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /build/ustreamer/ COPY . . RUN make -j5 WITH_OMX=1 WITH_GPIO=1 FROM balenalib/raspberrypi3-debian:run as RUN RUN apt-get update \ && apt-get install -y --no-install-recommends \ libevent-2.1 \ libevent-pthreads-2.1-6 \ libjpeg8 \ libbsd0 \ libgpiod2 \ && rm -rf /var/lib/apt/lists/* WORKDIR /ustreamer COPY --from=build /build/ustreamer/ustreamer . EXPOSE 8080 ENTRYPOINT ["./ustreamer", "--host=::"] # vim: syntax=dockerfile ustreamer-4.9/pkg/docker/Dockerfile.x64.native000066400000000000000000000012711414164060100213220ustar00rootroot00000000000000FROM debian:buster-slim as build RUN apt-get update \ && apt-get install -y \ ca-certificates \ make \ gcc \ git \ libevent-dev \ libjpeg62-turbo-dev \ libbsd-dev \ libgpiod-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /build/ustreamer/ COPY . . RUN make -j5 WITH_GPIO=1 FROM debian:buster-slim as run RUN apt-get update \ && apt-get install -y \ ca-certificates \ libevent-2.1 \ libevent-pthreads-2.1-6 \ libjpeg62-turbo \ libbsd0 \ libgpiod2 \ && rm -rf /var/lib/apt/lists/* WORKDIR /ustreamer COPY --from=build /build/ustreamer/ustreamer . #ENV LD_LIBRARY_PATH=/opt/vc/lib EXPOSE 8080 ENTRYPOINT ["./ustreamer", "--host=0.0.0.0"] # vim: syntax=dockerfile ustreamer-4.9/pkg/gentoo/000077500000000000000000000000001414164060100154465ustar00rootroot00000000000000ustreamer-4.9/pkg/gentoo/ustreamer-9999.ebuild000066400000000000000000000011051414164060100212610ustar00rootroot00000000000000# Copyright 2019 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=7 inherit git-r3 DESCRIPTION="uStreamer - Lightweight and fast MJPG-HTTP streamer" HOMEPAGE="https://github.com/pikvm/ustreamer" EGIT_REPO_URI="https://github.com/pikvm/ustreamer.git" LICENSE="GPL-3" SLOT="0" KEYWORDS="~amd64" IUSE="" DEPEND=" >=dev-libs/libevent-2.1.8 >=media-libs/libjpeg-turbo-1.5.3 >=dev-libs/libbsd-0.9.1 " RDEPEND="${DEPEND}" BDEPEND="" src_install() { dobin ustreamer dobin ustreamer-dump doman man/ustreamer.1 doman man/ustreamer-dump.1 } ustreamer-4.9/pkg/openwrt/000077500000000000000000000000001414164060100156515ustar00rootroot00000000000000ustreamer-4.9/pkg/openwrt/Makefile000066400000000000000000000024231414164060100173120ustar00rootroot00000000000000# # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. # include $(TOPDIR)/rules.mk PKG_NAME:=ustreamer PKG_VERSION:=4.9 PKG_RELEASE:=1 PKG_MAINTAINER:=Maxim Devaev PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/pikvm/ustreamer.git PKG_SOURCE_VERSION:=v$(PKG_VERSION) PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_LICENSE:=GPL-3.0 PKG_LICENSE_FILES:=LICENSE include $(INCLUDE_DIR)/package.mk define Package/ustreamer SECTION:=multimedia CATEGORY:=Multimedia TITLE:=uStreamer DEPENDS:=+libpthread +libjpeg +libv4l +libbsd +libevent2 +libevent2-core +libevent2-extra +libevent2-pthreads URL:=https://github.com/pikvm/ustreamer endef define Package/ustreamer/description µStreamer - Lightweight and fast MJPG-HTTP streamer endef define Package/ustreamer/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer $(1)/usr/bin/ $(INSTALL_BIN) $(PKG_BUILD_DIR)/ustreamer-dump $(1)/usr/bin/ $(INSTALL_DIR) $(1)/etc/config $(CP) ./files/ustreamer.config $(1)/etc/config/ustreamer $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ustreamer.init $(1)/etc/init.d/ustreamer endef $(eval $(call BuildPackage,ustreamer)) ustreamer-4.9/pkg/openwrt/files/000077500000000000000000000000001414164060100167535ustar00rootroot00000000000000ustreamer-4.9/pkg/openwrt/files/ustreamer.config000066400000000000000000000005051414164060100221510ustar00rootroot00000000000000 config ustreamer option enabled '0' option device '/dev/video0' option device_timeout '5' option input '0' option resolution '640x480' option format 'YUYV' option quality '80' option desired_fps '0' option encoder 'CPU' option host '::' option port '8080' option static '' option user '' option password '' ustreamer-4.9/pkg/openwrt/files/ustreamer.init000066400000000000000000000025751414164060100216600ustar00rootroot00000000000000#!/bin/sh /etc/rc.common # Copyright (C) 2009-2019 OpenWrt.org START=90 STOP=10 USE_PROCD=1 PROG=/usr/bin/ustreamer getcfg() { config_get value ustreamer $1 $2 return "$value" } start_instance() { config_get_bool enabled ustreamer enabled 0 [ "$enabled" -eq 0 ] && return local options="" options="$options --device='`getcfg device /dev/video0`'" options="$options --device-timeout='`getcfg device_timeout 5`'" options="$options --input='`getcfg input 0`'" options="$options --resolution='`getcfg resolution 640x480`'" options="$options --format='`getcfg format YUYV`'" options="$options --quality='`getcfg quality 80`'" options="$options --desired-fps='`getcfg desired_fps 0`'" options="$options --encoder='`getcfg encoder CPU`'" options="$options --host='`getcfg host '::'`'" local port=`getcfg port 8080` options="$options --port='$port'" options="$options --static='`getcfg static ''`'" options="$options --user='`getcfg user ''`'" options="$options --passwd='`getcfg password ''`'" config-get-bool opt_slowdown ustreamer slowdown 1 [ "$slowdown" -eq 1 ] && options="$options --slowdown" procd_open_instance procd_set_param command "$PROG" $options procd_add_mdns http tcp "$port" daemon=ustreamer procd_close_instance } start_service() { config_load ustreamer config_foreach start_instance ustreamer } service_triggers() { procd_add_reload_trigger ustreamer } ustreamer-4.9/python/000077500000000000000000000000001414164060100147135ustar00rootroot00000000000000ustreamer-4.9/python/Makefile000066400000000000000000000004031414164060100163500ustar00rootroot00000000000000-include ../config.mk DESTDIR ?= PREFIX ?= /usr/local PY ?= python3 # ===== all: $(info == PY_BUILD ustreamer-*.so) @ $(PY) setup.py build install: $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/) clean: rm -rf build ustreamer-4.9/python/setup.py000066400000000000000000000013251414164060100164260ustar00rootroot00000000000000import os from distutils.core import Extension from distutils.core import setup # ===== if __name__ == "__main__": setup( name="ustreamer", version="4.9", description="uStreamer tools", author="Maxim Devaev", author_email="mdevaev@gmail.com", url="https://github.com/pikvm/ustreamer", ext_modules=[ Extension( "ustreamer", libraries=["rt", "m", "pthread"], undef_macros=["NDEBUG"], sources=["src/" + name for name in os.listdir("src") if name.endswith(".c")], depends=["src/" + name for name in os.listdir("src") if name.endswith(".h")], ), ], ) ustreamer-4.9/python/src/000077500000000000000000000000001414164060100155025ustar00rootroot00000000000000ustreamer-4.9/python/src/frame.c000077700000000000000000000000001414164060100223372../../src/libs/frame.custar00rootroot00000000000000ustreamer-4.9/python/src/frame.h000077700000000000000000000000001414164060100223512../../src/libs/frame.hustar00rootroot00000000000000ustreamer-4.9/python/src/memsinksh.h000077700000000000000000000000001414164060100241612../../src/libs/memsinksh.hustar00rootroot00000000000000ustreamer-4.9/python/src/tools.h000077700000000000000000000000001414164060100224652../../src/libs/tools.hustar00rootroot00000000000000ustreamer-4.9/python/src/ustreamer.c000066400000000000000000000165351414164060100176670ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "tools.h" #include "frame.h" #include "memsinksh.h" typedef struct { PyObject_HEAD char *obj; double lock_timeout; double wait_timeout; double drop_same_frames; int fd; memsink_shared_s *mem; uint64_t frame_id; long double frame_ts; frame_s *frame; } MemsinkObject; #define MEM(_next) self->mem->_next #define FRAME(_next) self->frame->_next static void MemsinkObject_destroy_internals(MemsinkObject *self) { if (self->mem != NULL) { memsink_shared_unmap(self->mem); self->mem = NULL; } if (self->fd > 0) { close(self->fd); self->fd = -1; } if (self->frame) { frame_destroy(self->frame); self->frame = NULL; } } static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) { self->lock_timeout = 1; self->wait_timeout = 1; static char *kws[] = {"obj", "lock_timeout", "wait_timeout", "drop_same_frames", NULL}; if (!PyArg_ParseTupleAndKeywords( args, kwargs, "s|ddd", kws, &self->obj, &self->lock_timeout, &self->wait_timeout, &self->drop_same_frames)) { return -1; } # define SET_DOUBLE(_field, _cond) { \ if (!(self->_field _cond)) { \ PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \ return -1; \ } \ } SET_DOUBLE(lock_timeout, > 0); SET_DOUBLE(wait_timeout, > 0); SET_DOUBLE(drop_same_frames, >= 0); # undef SET_DOUBLE self->frame = frame_init(); if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) { PyErr_SetFromErrno(PyExc_OSError); goto error; } if ((self->mem = memsink_shared_map(self->fd)) == NULL) { PyErr_SetFromErrno(PyExc_OSError); goto error; } return 0; error: MemsinkObject_destroy_internals(self); return -1; } static PyObject *MemsinkObject_repr(MemsinkObject *self) { char repr[1024]; snprintf(repr, 1023, "", self->obj); return Py_BuildValue("s", repr); } static void MemsinkObject_dealloc(MemsinkObject *self) { MemsinkObject_destroy_internals(self); PyObject_Del(self); } static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { MemsinkObject_destroy_internals(self); Py_RETURN_NONE; } static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { Py_INCREF(self); return (PyObject *)self; } static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { return PyObject_CallMethod((PyObject *)self, "close", ""); } static int wait_frame(MemsinkObject *self) { long double deadline_ts = get_now_monotonic() + self->wait_timeout; # define RETURN_OS_ERROR { \ Py_BLOCK_THREADS \ PyErr_SetFromErrno(PyExc_OSError); \ return -1; \ } long double now; do { Py_BEGIN_ALLOW_THREADS int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout); now = get_now_monotonic(); if (retval < 0 && errno != EWOULDBLOCK) { RETURN_OS_ERROR; } else if (retval == 0) { if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) { if (self->drop_same_frames > 0) { if ( FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame) && (self->frame_ts + self->drop_same_frames > now) && !memcmp(FRAME(data), MEM(data), MEM(used)) ) { self->frame_id = MEM(id); goto drop; } } Py_BLOCK_THREADS return 0; } if (flock(self->fd, LOCK_UN) < 0) { RETURN_OS_ERROR; } } drop: if (usleep(1000) < 0) { RETURN_OS_ERROR; } Py_END_ALLOW_THREADS if (PyErr_CheckSignals() < 0) { return -1; } } while (now < deadline_ts); # undef RETURN_OS_ERROR return -2; } static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { if (self->mem == NULL || self->fd <= 0) { PyErr_SetString(PyExc_RuntimeError, "Closed"); return NULL; } switch (wait_frame(self)) { case 0: break; case -2: Py_RETURN_NONE; default: return NULL; } frame_set_data(self->frame, MEM(data), MEM(used)); FRAME_COPY_META(self->mem, self->frame); self->frame_id = MEM(id); self->frame_ts = get_now_monotonic(); MEM(last_client_ts) = self->frame_ts; if (flock(self->fd, LOCK_UN) < 0) { return PyErr_SetFromErrno(PyExc_OSError); } PyObject *dict_frame = PyDict_New(); if (dict_frame == NULL) { return NULL; } # define SET_VALUE(_key, _maker) { \ PyObject *_tmp = _maker; \ if (_tmp == NULL) { \ return NULL; \ } \ if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \ Py_DECREF(_tmp); \ return NULL; \ } \ Py_DECREF(_tmp); \ } # define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key))) SET_NUMBER(width, Long, Long); SET_NUMBER(height, Long, Long); SET_NUMBER(format, Long, Long); SET_NUMBER(stride, Long, Long); SET_NUMBER(online, Long, Bool); SET_NUMBER(key, Long, Bool); SET_NUMBER(grab_ts, Double, Float); SET_NUMBER(encode_begin_ts, Double, Float); SET_NUMBER(encode_end_ts, Double, Float); SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used))); # undef SET_NUMBER # undef SET_VALUE return dict_frame; } static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) { return PyBool_FromLong(self->mem != NULL && self->fd > 0); } #define FIELD_GETTER(_field, _from, _to) \ static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \ return Py##_to##_From##_from(self->_field); \ } FIELD_GETTER(obj, String, Unicode) FIELD_GETTER(lock_timeout, Double, Float) FIELD_GETTER(wait_timeout, Double, Float) FIELD_GETTER(drop_same_frames, Double, Float) #undef FIELD_GETTER static PyMethodDef MemsinkObject_methods[] = { # define ADD_METHOD(_name, _method, _flags) \ {.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)} ADD_METHOD("close", close, METH_NOARGS), ADD_METHOD("__enter__", enter, METH_NOARGS), ADD_METHOD("__exit__", exit, METH_VARARGS), ADD_METHOD("wait_frame", wait_frame, METH_NOARGS), ADD_METHOD("is_opened", is_opened, METH_NOARGS), {}, # undef ADD_METHOD }; static PyGetSetDef MemsinkObject_getsets[] = { # define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field} ADD_GETTER(obj), ADD_GETTER(lock_timeout), ADD_GETTER(wait_timeout), ADD_GETTER(drop_same_frames), {}, # undef ADD_GETTER }; static PyTypeObject MemsinkType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "ustreamer.Memsink", .tp_basicsize = sizeof(MemsinkObject), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_new = PyType_GenericNew, .tp_init = (initproc)MemsinkObject_init, .tp_dealloc = (destructor)MemsinkObject_dealloc, .tp_repr = (reprfunc)MemsinkObject_repr, .tp_methods = MemsinkObject_methods, .tp_getset = MemsinkObject_getsets, }; static PyModuleDef ustreamer_Module = { PyModuleDef_HEAD_INIT, .m_name = "ustreamer", .m_size = -1, }; PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction PyObject *module = PyModule_Create(&ustreamer_Module); if (module == NULL) { return NULL; } if (PyType_Ready(&MemsinkType) < 0) { return NULL; } Py_INCREF(&MemsinkType); if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) { return NULL; } return module; } #undef FRAME #undef MEM ustreamer-4.9/src/000077500000000000000000000000001414164060100141615ustar00rootroot00000000000000ustreamer-4.9/src/Makefile000066400000000000000000000043631414164060100156270ustar00rootroot00000000000000DESTDIR ?= PREFIX ?= /usr/local CC ?= gcc CFLAGS ?= -O3 LDFLAGS ?= RPI_VC_HEADERS ?= /opt/vc/include RPI_VC_LIBS ?= /opt/vc/lib # ===== _USTR = ustreamer.bin _DUMP = ustreamer-dump.bin _CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS) _LDFLAGS = $(LDFLAGS) _COMMON_LIBS = -lm -ljpeg -pthread -lrt _USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads _USTR_SRCS = $(shell ls \ libs/*.c \ ustreamer/*.c \ ustreamer/http/*.c \ ustreamer/data/*.c \ ustreamer/encoders/cpu/*.c \ ustreamer/encoders/hw/*.c \ ) _DUMP_LIBS = $(_COMMON_LIBS) _DUMP_SRCS = $(shell ls \ libs/*.c \ dump/*.c \ ) _BUILD = build define optbool $(filter $(shell echo $(1) | tr A-Z a-z), yes on 1) endef ifneq ($(call optbool,$(WITH_OMX)),) _USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS) override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS) _USTR_SRCS += $(shell ls \ ustreamer/encoders/omx/*.c \ ustreamer/h264/*.c \ ) endif ifneq ($(call optbool,$(WITH_GPIO)),) _USTR_LIBS += -lgpiod override _CFLAGS += -DWITH_GPIO _USTR_SRCS += $(shell ls ustreamer/gpio/*.c) endif WITH_PTHREAD_NP ?= 1 ifneq ($(call optbool,$(WITH_PTHREAD_NP)),) override _CFLAGS += -DWITH_PTHREAD_NP endif WITH_SETPROCTITLE ?= 1 ifneq ($(call optbool,$(WITH_SETPROCTITLE)),) ifeq ($(shell uname -s | tr A-Z a-z),linux) _USTR_LIBS += -lbsd endif override _CFLAGS += -DWITH_SETPROCTITLE endif # ===== all: $(_USTR) $(_DUMP) install: all mkdir -p $(DESTDIR)$(PREFIX)/bin install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR)) install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP)) install-strip: install strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR)) strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP)) $(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(info == LD $@) @ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS) $(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o) $(info == LD $@) @ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS) $(_BUILD)/%.o: %.c $(info -- CC $<) @ mkdir -p $(dir $@) || true @ $(CC) $< -o $@ $(_CFLAGS) clean: rm -rf $(_USTR) $(_DUMP) $(_BUILD) _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o) -include $(_OBJS:%.o=%.d) ustreamer-4.9/src/dump/000077500000000000000000000000001414164060100151265ustar00rootroot00000000000000ustreamer-4.9/src/dump/file.c000066400000000000000000000060201414164060100162070ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "file.h" output_file_s *output_file_init(const char *path, bool json) { output_file_s *output; A_CALLOC(output, 1); if (!strcmp(path, "-")) { LOG_INFO("Using output: "); output->fp = stdout; } else { LOG_INFO("Using output: %s", path); if ((output->fp = fopen(path, "wb")) == NULL) { LOG_PERROR("Can't open output file"); goto error; } } output->json = json; return output; error: output_file_destroy(output); return NULL; } void output_file_write(void *v_output, const frame_s *frame) { output_file_s *output = (output_file_s *)v_output; if (output->json) { base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated); fprintf(output->fp, "{\"size\": %zu, \"width\": %u, \"height\": %u," " \"format\": %u, \"stride\": %u, \"online\": %u," " \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf," " \"data\": \"%s\"}\n", frame->used, frame->width, frame->height, frame->format, frame->stride, frame->online, frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts, output->base64_data); } else { fwrite(frame->data, 1, frame->used, output->fp); } fflush(output->fp); } void output_file_destroy(void *v_output) { output_file_s *output = (output_file_s *)v_output; if (output->base64_data) { free(output->base64_data); } if (output->fp && output->fp != stdout) { if (fclose(output->fp) < 0) { LOG_PERROR("Can't close output file"); } } free(output); } ustreamer-4.9/src/dump/file.h000066400000000000000000000040511414164060100162160ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include "../libs/tools.h" #include "../libs/logging.h" #include "../libs/frame.h" #include "../libs/base64.h" typedef struct { const char *path; bool json; FILE *fp; char *base64_data; size_t base64_allocated; } output_file_s; output_file_s *output_file_init(const char *path, bool json); void output_file_write(void *v_output, const frame_s *frame); void output_file_destroy(void *v_output); ustreamer-4.9/src/dump/main.c000066400000000000000000000266221414164060100162260ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 #include #include #include #include "../libs/config.h" #include "../libs/tools.h" #include "../libs/logging.h" #include "../libs/frame.h" #include "../libs/memsink.h" #include "../libs/options.h" #include "file.h" enum _OPT_VALUES { _O_SINK = 's', _O_SINK_TIMEOUT = 't', _O_OUTPUT = 'o', _O_OUTPUT_JSON = 'j', _O_COUNT = 'c', _O_INTERVAL = 'i', _O_HELP = 'h', _O_VERSION = 'v', _O_LOG_LEVEL = 10000, _O_PERF, _O_VERBOSE, _O_DEBUG, _O_FORCE_LOG_COLORS, _O_NO_LOG_COLORS, }; static const struct option _LONG_OPTS[] = { {"sink", required_argument, NULL, _O_SINK}, {"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT}, {"output", required_argument, NULL, _O_OUTPUT}, {"output-json", no_argument, NULL, _O_OUTPUT_JSON}, {"count", required_argument, NULL, _O_COUNT}, {"interval", required_argument, NULL, _O_INTERVAL}, {"log-level", required_argument, NULL, _O_LOG_LEVEL}, {"perf", no_argument, NULL, _O_PERF}, {"verbose", no_argument, NULL, _O_VERBOSE}, {"debug", no_argument, NULL, _O_DEBUG}, {"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS}, {"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS}, {"help", no_argument, NULL, _O_HELP}, {"version", no_argument, NULL, _O_VERSION}, {NULL, 0, NULL, 0}, }; volatile bool global_stop = false; typedef struct { void *v_output; void (*write)(void *v_output, const frame_s *frame); void (*destroy)(void *v_output); } _output_context_s; static void _signal_handler(int signum); static void _install_signal_handlers(void); static int _dump_sink( const char *sink_name, unsigned sink_timeout, long long count, long double interval, _output_context_s *ctx); static void _help(FILE *fp); int main(int argc, char *argv[]) { LOGGING_INIT; A_THREAD_RENAME("main"); char *sink_name = NULL; unsigned sink_timeout = 1; char *output_path = NULL; bool output_json = false; long long count = 0; long double interval = 0; # define OPT_SET(_dest, _value) { \ _dest = _value; \ break; \ } # define OPT_NUMBER(_name, _dest, _min, _max, _base) { \ errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \ if (errno || *_end || _tmp < _min || _tmp > _max) { \ printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \ return 1; \ } \ _dest = _tmp; \ break; \ } # define OPT_LDOUBLE(_name, _dest, _min, _max) { \ errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \ if (errno || *_end || _tmp < _min || _tmp > _max) { \ printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \ return 1; \ } \ _dest = _tmp; \ break; \ } char short_opts[128]; build_short_options(_LONG_OPTS, short_opts, 128); for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) { switch (ch) { case _O_SINK: OPT_SET(sink_name, optarg); case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0); case _O_OUTPUT: OPT_SET(output_path, optarg); case _O_OUTPUT_JSON: OPT_SET(output_json, true); case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0); case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0); case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF); case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE); case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG); case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true); case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false); case _O_HELP: _help(stdout); return 0; case _O_VERSION: puts(VERSION); return 0; case 0: break; default: return 1; } } # undef OPT_LDOUBLE # undef OPT_NUMBER # undef OPT_SET if (sink_name == NULL || sink_name[0] == '\0') { puts("Missing option --sink. See --help for details."); return 1; } _output_context_s ctx; MEMSET_ZERO(ctx); if (output_path && output_path[0] != '\0') { if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) { return 1; } ctx.write = output_file_write; ctx.destroy = output_file_destroy; } _install_signal_handlers(); int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx)); if (ctx.v_output && ctx.destroy) { ctx.destroy(ctx.v_output); } return retval; } static void _signal_handler(int signum) { switch (signum) { case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break; case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break; case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break; default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break; } global_stop = true; } static void _install_signal_handlers(void) { struct sigaction sig_act; MEMSET_ZERO(sig_act); assert(!sigemptyset(&sig_act.sa_mask)); sig_act.sa_handler = _signal_handler; assert(!sigaddset(&sig_act.sa_mask, SIGINT)); assert(!sigaddset(&sig_act.sa_mask, SIGTERM)); assert(!sigaddset(&sig_act.sa_mask, SIGPIPE)); LOG_DEBUG("Installing SIGINT handler ..."); assert(!sigaction(SIGINT, &sig_act, NULL)); LOG_DEBUG("Installing SIGTERM handler ..."); assert(!sigaction(SIGTERM, &sig_act, NULL)); LOG_DEBUG("Installing SIGTERM handler ..."); assert(!sigaction(SIGPIPE, &sig_act, NULL)); } static int _dump_sink( const char *sink_name, unsigned sink_timeout, long long count, long double interval, _output_context_s *ctx) { if (count == 0) { count = -1; } useconds_t interval_us = interval * 1000000; frame_s *frame = frame_init(); memsink_s *sink = NULL; if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) { goto error; } unsigned fps = 0; unsigned fps_accum = 0; long long fps_second = 0; long double last_ts = 0; while (!global_stop) { int error = memsink_client_get(sink, frame); if (error == 0) { const long double now = get_now_monotonic(); const long long now_second = floor_ms(now); char fourcc_str[8]; LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf", frame->used, frame->width, frame->height, fourcc_to_string(frame->format, fourcc_str, 8), frame->stride, frame->online, frame->key, now - frame->grab_ts, (last_ts ? now - last_ts : 0)); last_ts = now; LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf", frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts); if (now_second != fps_second) { fps = fps_accum; fps_accum = 0; fps_second = now_second; LOG_PERF_FPS("A new second has come; captured_fps=%u", fps); } fps_accum += 1; if (ctx->v_output) { ctx->write(ctx->v_output, frame); } if (count >= 0) { --count; if (count <= 0) { break; } } if (interval_us > 0) { usleep(interval_us); } } else if (error == -2) { usleep(1000); } else { goto error; } } int retval = 0; goto ok; error: retval = -1; ok: if (sink) { memsink_destroy(sink); } frame_destroy(frame); LOG_INFO("Bye-bye"); return retval; } static void _help(FILE *fp) { # define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__) SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file"); SAY("═════════════════════════════════════════════════════"); SAY("Version: %s; license: GPLv3", VERSION); SAY("Copyright (C) 2018-2021 Maxim Devaev \n"); SAY("Example:"); SAY("════════"); SAY(" ustreamer-dump --sink test --output - \\"); SAY(" | ffmpeg -use_wallclock_as_timestamps 1 -i pipe: -c:v libx264 test.mp4\n"); SAY("Sink options:"); SAY("═════════════"); SAY(" -s|--sink ──────── Memory sink ID. No default.\n"); SAY(" -t|--sink-timeout ─ Timeout for the upcoming frame. Default: 1.\n"); SAY(" -o|--output ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n"); SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n"); SAY(" -c|--count ───────── Limit the number of frames. Default: 0 (infinite).\n"); SAY(" -i|--interval ───── Delay between reading frames (float). Default: 0.\n"); SAY("Logging options:"); SAY("════════════════"); SAY(" --log-level ──── Verbosity level of messages from 0 (info) to 3 (debug)."); SAY(" Enabling debugging messages can slow down the program."); SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug)."); SAY(" Default: %d.\n", us_log_level); SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n"); SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n"); SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n"); SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n"); SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n"); SAY("Help options:"); SAY("═════════════"); SAY(" -h|--help ─────── Print this text and exit.\n"); SAY(" -v|--version ──── Print version and exit.\n"); # undef SAY } ustreamer-4.9/src/libs/000077500000000000000000000000001414164060100151125ustar00rootroot00000000000000ustreamer-4.9/src/libs/base64.c000066400000000000000000000056551414164060100163550ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "base64.h" static const char _ENCODING_TABLE[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; static const unsigned _MOD_TABLE[] = {0, 2, 1}; void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) { const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0' if (*encoded == NULL || (allocated && *allocated < encoded_size)) { A_REALLOC(*encoded, encoded_size); if (allocated) { *allocated = encoded_size; } } for (unsigned data_index = 0, encoded_index = 0; data_index < size;) { # define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0) OCTET(octet_a); OCTET(octet_b); OCTET(octet_c); # undef OCTET unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; # define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F] ENCODE(3); ENCODE(2); ENCODE(1); ENCODE(0); # undef ENCODE } for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) { (*encoded)[encoded_size - 2 - index] = '='; } (*encoded)[encoded_size - 1] = '\0'; } ustreamer-4.9/src/libs/base64.h000066400000000000000000000033771414164060100163610ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include "tools.h" void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated); ustreamer-4.9/src/libs/config.h000066400000000000000000000035551414164060100165400ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #define VERSION_MAJOR 4 #define VERSION_MINOR 9 #define MAKE_VERSION2(_major, _minor) #_major "." #_minor #define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor) #define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR) #define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR)) ustreamer-4.9/src/libs/frame.c000066400000000000000000000070351414164060100163550ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "frame.h" frame_s *frame_init(void) { frame_s *frame; A_CALLOC(frame, 1); frame_realloc_data(frame, 512 * 1024); return frame; } void frame_destroy(frame_s *frame) { if (frame->data) { free(frame->data); } free(frame); } void frame_realloc_data(frame_s *frame, size_t size) { if (frame->allocated < size) { A_REALLOC(frame->data, size); frame->allocated = size; } } void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) { frame_realloc_data(frame, size); memcpy(frame->data, data, size); frame->used = size; } void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) { size_t new_used = frame->used + size; frame_realloc_data(frame, new_used); memcpy(frame->data + frame->used, data, size); frame->used = new_used; } void frame_copy(const frame_s *src, frame_s *dest) { frame_set_data(dest, src->data, src->used); FRAME_COPY_META(src, dest); } bool frame_compare(const frame_s *a, const frame_s *b) { return ( a->allocated && b->allocated && FRAME_COMPARE_META_USED_NOTS(a, b) && !memcmp(a->data, b->data, b->used) ); } unsigned frame_get_padding(const frame_s *frame) { unsigned bytes_per_pixel = 0; switch (frame->format) { case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break; case V4L2_PIX_FMT_RGB24: bytes_per_pixel = 3; break; // case V4L2_PIX_FMT_H264: case V4L2_PIX_FMT_MJPEG: case V4L2_PIX_FMT_JPEG: bytes_per_pixel = 0; break; default: assert(0 && "Unknown pixelformat"); } if (bytes_per_pixel > 0 && frame->stride > frame->width) { return (frame->stride - frame->width * bytes_per_pixel); } return 0; } const char *fourcc_to_string(unsigned format, char *buf, size_t size) { assert(size >= 8); buf[0] = format & 0x7F; buf[1] = (format >> 8) & 0x7F; buf[2] = (format >> 16) & 0x7F; buf[3] = (format >> 24) & 0x7F; if (format & ((unsigned)1 << 31)) { buf[4] = '-'; buf[5] = 'B'; buf[6] = 'E'; buf[7] = '\0'; } else { buf[4] = '\0'; } return buf; } ustreamer-4.9/src/libs/frame.h000066400000000000000000000070061414164060100163600ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include "tools.h" typedef struct { uint8_t *data; size_t used; size_t allocated; unsigned width; unsigned height; unsigned format; unsigned stride; // Stride is a bytesperline in V4L2 // https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html // https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd bool online; bool key; long double grab_ts; long double encode_begin_ts; long double encode_end_ts; } frame_s; #define FRAME_COPY_META(_src, _dest) { \ _dest->width = _src->width; \ _dest->height = _src->height; \ _dest->format = _src->format; \ _dest->stride = _src->stride; \ _dest->online = _src->online; \ _dest->key = _src->key; \ _dest->grab_ts = _src->grab_ts; \ _dest->encode_begin_ts = _src->encode_begin_ts; \ _dest->encode_end_ts = _src->encode_end_ts; \ } static inline void frame_copy_meta(const frame_s *src, frame_s *dest) { FRAME_COPY_META(src, dest); } #define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \ _a->used == _b->used \ && _a->width == _b->width \ && _a->height == _b->height \ && _a->format == _b->format \ && _a->stride == _b->stride \ && _a->online == _b->online \ && _a->key == _b->key \ ) frame_s *frame_init(void); void frame_destroy(frame_s *frame); void frame_realloc_data(frame_s *frame, size_t size); void frame_set_data(frame_s *frame, const uint8_t *data, size_t size); void frame_append_data(frame_s *frame, const uint8_t *data, size_t size); void frame_copy(const frame_s *src, frame_s *dest); bool frame_compare(const frame_s *a, const frame_s *b); unsigned frame_get_padding(const frame_s *frame); const char *fourcc_to_string(unsigned format, char *buf, size_t size); static inline bool is_jpeg(unsigned format) { return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG); } ustreamer-4.9/src/libs/list.h000066400000000000000000000050341414164060100162400ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #define LIST_STRUCT(...) \ __VA_ARGS__ *prev; \ __VA_ARGS__ *next; #define LIST_ITERATE(_first, _item, ...) { \ for (__typeof__(_first) _item = _first; _item;) { \ __typeof__(_first) _next = _item->next; \ __VA_ARGS__ \ _item = _next; \ } \ } #define LIST_APPEND(_first, _item) { \ if (_first == NULL) { \ _first = _item; \ } else { \ __typeof__(_first) _last = _first; \ for (; _last->next; _last = _last->next); \ _item->prev = _last; \ _last->next = _item; \ } \ } #define LIST_APPEND_C(_first, _item, _count) { \ LIST_APPEND(_first, _item); \ ++(_count); \ } #define LIST_REMOVE(_first, _item) { \ if (_item->prev == NULL) { \ _first = _item->next; \ } else { \ _item->prev->next = _item->next; \ } \ if (_item->next != NULL) { \ _item->next->prev = _item->prev; \ } \ } #define LIST_REMOVE_C(_first, _item, _count) { \ LIST_REMOVE(_first, _item); \ assert((_count) >= 1); \ --(_count); \ } ustreamer-4.9/src/libs/logging.c000066400000000000000000000032311414164060100167030ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "logging.h" enum log_level_t us_log_level; bool us_log_colored; pthread_mutex_t us_log_mutex; ustreamer-4.9/src/libs/logging.h000066400000000000000000000116341414164060100167160ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include "tools.h" #include "threading.h" enum log_level_t { LOG_LEVEL_INFO, LOG_LEVEL_PERF, LOG_LEVEL_VERBOSE, LOG_LEVEL_DEBUG, }; extern enum log_level_t us_log_level; extern bool us_log_colored; extern pthread_mutex_t us_log_mutex; #define LOGGING_INIT { \ us_log_level = LOG_LEVEL_INFO; \ us_log_colored = isatty(2); \ A_MUTEX_INIT(&us_log_mutex); \ } #define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex) #define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex) #define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex) #define COLOR_GRAY "\x1b[30;1m" #define COLOR_RED "\x1b[31;1m" #define COLOR_GREEN "\x1b[32;1m" #define COLOR_YELLOW "\x1b[33;1m" #define COLOR_BLUE "\x1b[34;1m" #define COLOR_CYAN "\x1b[36;1m" #define COLOR_RESET "\x1b[0m" #define SEP_INFO(_ch) { \ LOGGING_LOCK; \ for (int _i = 0; _i < 80; ++_i) { \ fputc(_ch, stderr); \ } \ fputc('\n', stderr); \ fflush(stderr); \ LOGGING_UNLOCK; \ } #define SEP_DEBUG(_ch) { \ if (us_log_level >= LOG_LEVEL_DEBUG) { \ SEP_INFO(_ch); \ } \ } #define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \ char _tname_buf[MAX_THREAD_NAME] = {0}; \ thread_get_name(_tname_buf); \ if (us_log_colored) { \ fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \ " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \ get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \ } else { \ fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \ get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \ } \ fputc('\n', stderr); \ fflush(stderr); \ } #define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \ LOGGING_LOCK; \ LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \ LOGGING_UNLOCK; \ } #define LOG_ERROR(_msg, ...) { \ LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \ } #define LOG_PERROR(_msg, ...) { \ char _perror_buf[1024] = {0}; \ char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \ LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \ } #define LOG_INFO(_msg, ...) { \ LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \ } #define LOG_INFO_NOLOCK(_msg, ...) { \ LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \ } #define LOG_PERF(_msg, ...) { \ if (us_log_level >= LOG_LEVEL_PERF) { \ LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \ } \ } #define LOG_PERF_FPS(_msg, ...) { \ if (us_log_level >= LOG_LEVEL_PERF) { \ LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \ } \ } #define LOG_VERBOSE(_msg, ...) { \ if (us_log_level >= LOG_LEVEL_VERBOSE) { \ LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \ } \ } #define LOG_VERBOSE_PERROR(_msg, ...) { \ if (us_log_level >= LOG_LEVEL_VERBOSE) { \ char _perror_buf[1024] = {0}; \ char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \ LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \ } \ } #define LOG_DEBUG(_msg, ...) { \ if (us_log_level >= LOG_LEVEL_DEBUG) { \ LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \ } \ } ustreamer-4.9/src/libs/memsink.c000066400000000000000000000144101414164060100167210ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "memsink.h" memsink_s *memsink_init( const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) { memsink_s *sink; A_CALLOC(sink, 1); sink->name = name; sink->obj = obj; sink->server = server; sink->rm = rm; sink->client_ttl = client_ttl; sink->timeout = timeout; sink->fd = -1; sink->mem = MAP_FAILED; atomic_init(&sink->has_clients, false); LOG_INFO("Using %s-sink: %s", name, obj); mode_t mask = umask(0); sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode); umask(mask); if (sink->fd == -1) { umask(mask); LOG_PERROR("%s-sink: Can't open shared memory", name); goto error; } if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) { LOG_PERROR("%s-sink: Can't truncate shared memory", name); goto error; } if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) { LOG_PERROR("%s-sink: Can't mmap shared memory", name); goto error; } return sink; error: memsink_destroy(sink); return NULL; } void memsink_destroy(memsink_s *sink) { if (sink->mem != MAP_FAILED) { if (memsink_shared_unmap(sink->mem) < 0) { LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name); } } if (sink->fd >= 0) { if (close(sink->fd) < 0) { LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name); } if (sink->rm && shm_unlink(sink->obj) < 0) { if (errno != ENOENT) { LOG_PERROR("%s-sink: Can't remove shared memory", sink->name); } } } free(sink); } bool memsink_server_check(memsink_s *sink, const frame_s *frame) { // Return true (the need to write to memsink) on any of these conditions: // - EWOULDBLOCK - we have an active client; // - Incorrect magic or version - need to first write; // - We have some active clients by last_client_ts; // - Frame meta differs (like size, format, but not timestamp). assert(sink->server); if (flock(sink->fd, LOCK_EX | LOCK_NB) < 0) { if (errno == EWOULDBLOCK) { atomic_store(&sink->has_clients, true); return true; } LOG_PERROR("%s-sink: Can't lock memory", sink->name); return false; } if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) { return true; } bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()); atomic_store(&sink->has_clients, has_clients); if (flock(sink->fd, LOCK_UN) < 0) { LOG_PERROR("%s-sink: Can't unlock memory", sink->name); return false; } return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));; } int memsink_server_put(memsink_s *sink, const frame_s *frame) { assert(sink->server); const long double now = get_now_monotonic(); if (frame->used > MEMSINK_MAX_DATA) { LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)", sink->name, frame->used, MEMSINK_MAX_DATA); return 0; // -2 } if (flock_timedwait_monotonic(sink->fd, 1) == 0) { LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name); sink->last_id = get_now_id(); sink->mem->id = sink->last_id; memcpy(sink->mem->data, frame->data, frame->used); sink->mem->used = frame->used; FRAME_COPY_META(frame, sink->mem); sink->mem->magic = MEMSINK_MAGIC; sink->mem->version = MEMSINK_VERSION; atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic())); if (flock(sink->fd, LOCK_UN) < 0) { LOG_PERROR("%s-sink: Can't unlock memory", sink->name); return -1; } LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf", sink->name, get_now_monotonic() - now); } else if (errno == EWOULDBLOCK) { LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name); } else { LOG_PERROR("%s-sink: Can't lock memory", sink->name); return -1; } return 0; } int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction assert(!sink->server); // Client only if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) { if (errno == EWOULDBLOCK) { return -2; } LOG_PERROR("%s-sink: Can't lock memory", sink->name); return -1; } int retval = -2; // Not updated if (sink->mem->magic == MEMSINK_MAGIC) { if (sink->mem->version != MEMSINK_VERSION) { LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u", sink->name, sink->mem->version, MEMSINK_VERSION); retval = -1; goto done; } if (sink->mem->id != sink->last_id) { // When updated sink->last_id = sink->mem->id; frame_set_data(frame, sink->mem->data, sink->mem->used); FRAME_COPY_META(sink->mem, frame); retval = 0; } sink->mem->last_client_ts = get_now_monotonic(); } done: if (flock(sink->fd, LOCK_UN) < 0) { LOG_PERROR("%s-sink: Can't unlock memory", sink->name); return -1; } return retval; } ustreamer-4.9/src/libs/memsink.h000066400000000000000000000047531414164060100167370ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "tools.h" #include "logging.h" #include "frame.h" #include "memsinksh.h" typedef struct { const char *name; const char *obj; bool server; bool rm; unsigned client_ttl; // Only for server unsigned timeout; int fd; memsink_shared_s *mem; uint64_t last_id; atomic_bool has_clients; // Only for server } memsink_s; memsink_s *memsink_init( const char *name, const char *obj, bool server, mode_t mode, bool rm, unsigned client_ttl, unsigned timeout); void memsink_destroy(memsink_s *sink); bool memsink_server_check(memsink_s *sink, const frame_s *frame); int memsink_server_put(memsink_s *sink, const frame_s *frame); int memsink_client_get(memsink_s *sink, frame_s *frame); ustreamer-4.9/src/libs/memsinksh.h000066400000000000000000000051271414164060100172660ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE) #define MEMSINK_VERSION ((uint32_t)2) #ifndef CFG_MEMSINK_MAX_DATA # define CFG_MEMSINK_MAX_DATA 33554432 #endif #define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA)) typedef struct { uint64_t magic; uint32_t version; uint64_t id; size_t used; unsigned width; unsigned height; unsigned format; unsigned stride; bool online; bool key; long double grab_ts; long double encode_begin_ts; long double encode_end_ts; long double last_client_ts; uint8_t data[MEMSINK_MAX_DATA]; } memsink_shared_s; INLINE memsink_shared_s *memsink_shared_map(int fd) { memsink_shared_s *mem = mmap( NULL, sizeof(memsink_shared_s), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (mem == MAP_FAILED) { return NULL; } assert(mem != NULL); return mem; } INLINE int memsink_shared_unmap(memsink_shared_s *mem) { assert(mem != NULL); return munmap(mem, sizeof(memsink_shared_s)); } ustreamer-4.9/src/libs/options.c000066400000000000000000000041521414164060100167530ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "options.h" void build_short_options(const struct option opts[], char *short_opts, size_t size) { memset(short_opts, 0, size); for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) { assert(short_index < size - 3); if (isalpha(opts[opt_index].val)) { short_opts[short_index] = opts[opt_index].val; ++short_index; if (opts[opt_index].has_arg == required_argument) { short_opts[short_index] = ':'; ++short_index; } } } } ustreamer-4.9/src/libs/options.h000066400000000000000000000033721414164060100167630ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include void build_short_options(const struct option opts[], char *short_opts, size_t size); ustreamer-4.9/src/libs/process.h000066400000000000000000000101151414164060100167370ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #if defined(__linux__) # define HAS_PDEATHSIG #elif defined(__FreeBSD__) # include # if __FreeBSD_version >= 1102000 # define HAS_PDEATHSIG # endif #endif #ifdef WITH_SETPROCTITLE # include # include # if defined(__linux__) # include # elif (defined(__FreeBSD__) || defined(__DragonFly__)) //# include //# include # elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h # else # error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0 # endif #endif #ifdef HAS_PDEATHSIG # if defined(__linux__) # include # elif defined(__FreeBSD__) # include # endif #endif #ifdef WITH_SETPROCTITLE # include "tools.h" #endif #ifdef HAS_PDEATHSIG # include "logging.h" #endif #ifdef WITH_SETPROCTITLE extern char **environ; #endif #ifdef HAS_PDEATHSIG INLINE int process_track_parent_death(void) { pid_t parent = getppid(); int signum = SIGTERM; # if defined(__linux__) int retval = prctl(PR_SET_PDEATHSIG, signum); # elif defined(__FreeBSD__) int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum); # else # error WTF? # endif if (retval < 0) { LOG_PERROR("Can't set to receive SIGTERM on parent process death"); return -1; } if (kill(parent, 0) < 0) { LOG_PERROR("The parent process %d is already dead", parent); return -1; } return 0; } #endif #ifdef WITH_SETPROCTITLE # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic push INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) { # pragma GCC diagnostic pop char *cmdline = NULL; size_t allocated = 2048; size_t used = 0; A_REALLOC(cmdline, allocated); cmdline[0] = '\0'; for (int index = 0; index < argc; ++index) { size_t arg_len = strlen(argv[index]); if (used + arg_len + 16 >= allocated) { allocated += arg_len + 2048; A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert) } strcat(cmdline, " "); strcat(cmdline, argv[index]); used = strlen(cmdline); // Не считаем вручную, так надежнее } # ifdef __linux__ setproctitle_init(argc, argv, environ); # endif setproctitle("-%s:%s", prefix, cmdline); free(cmdline); } #endif INLINE void process_notify_parent(void) { pid_t parent = getppid(); if (kill(parent, SIGUSR2) < 0) { LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent); } } ustreamer-4.9/src/libs/threading.h000066400000000000000000000111451414164060100172320ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #ifdef WITH_PTHREAD_NP # if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) # include # include # endif #endif #include "tools.h" #ifdef PTHREAD_MAX_NAMELEN_NP # define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP)) #else # define MAX_THREAD_NAME ((size_t)16) #endif #define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg)) #define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL)) #ifdef WITH_PTHREAD_NP # define A_THREAD_RENAME(_fmt, ...) { \ char _new_tname_buf[MAX_THREAD_NAME] = {0}; \ assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \ thread_set_name(_new_tname_buf); \ } #else # define A_THREAD_RENAME(_fmt, ...) #endif #define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL)) #define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex)) #define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex)) #define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex)) #define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL)) #define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond)) #define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__)) #define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); } #ifdef WITH_PTHREAD_NP INLINE void thread_set_name(const char *name) { # if defined(__linux__) pthread_setname_np(pthread_self(), name); # elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) pthread_set_name_np(pthread_self(), name); # elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); # else # error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0 # endif } #endif INLINE void thread_get_name(char *name) { // Always required for logging #ifdef WITH_PTHREAD_NP int retval = -1; # if defined(__linux__) || defined (__NetBSD__) retval = pthread_getname_np(pthread_self(), name, MAX_THREAD_NAME); # elif \ (defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \ || (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \ || defined(__DragonFly__) pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME); if (name[0] != '\0') { retval = 0; } # else # error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0 # endif if (retval < 0) { #endif #if defined(__linux__) pid_t tid = syscall(SYS_gettid); #elif defined(__FreeBSD__) pid_t tid = syscall(SYS_thr_self); #elif defined(__OpenBSD__) pid_t tid = syscall(SYS_getthrid); #elif defined(__NetBSD__) pid_t tid = syscall(SYS__lwp_self); #elif defined(__DragonFly__) pid_t tid = syscall(SYS_lwp_gettid); #else pid_t tid = 0; // Makes cppcheck happy # warning gettid() not implemented #endif assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0); #ifdef WITH_PTHREAD_NP } #endif } ustreamer-4.9/src/libs/tools.h000066400000000000000000000117461414164060100164340ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #define RN "\r\n" #define INLINE inline __attribute__((always_inline)) #define UNUSED __attribute__((unused)) #define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest))))) #define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest))))) #define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj)) #define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0) #define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0])) INLINE const char *bool_to_string(bool flag) { return (flag ? "true" : "false"); } INLINE size_t align_size(size_t size, size_t to) { return ((size + (to - 1)) & ~(to - 1)); } INLINE unsigned min_u(unsigned a, unsigned b) { return (a < b ? a : b); } INLINE unsigned max_u(unsigned a, unsigned b) { return (a > b ? a : b); } INLINE long long floor_ms(long double now) { return (long long)now - (now < (long long)now); // floor() } INLINE uint32_t triple_u32(uint32_t x) { // https://nullprogram.com/blog/2018/07/31/ x ^= x >> 17; x *= UINT32_C(0xED5AD4BB); x ^= x >> 11; x *= UINT32_C(0xAC4C1B51); x ^= x >> 15; x *= UINT32_C(0x31848BAB); x ^= x >> 14; return x; } INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) { struct timespec ts; assert(!clock_gettime(clk_id, &ts)); *sec = ts.tv_sec; *msec = round(ts.tv_nsec / 1.0e6); if (*msec > 999) { *sec += 1; *msec = 0; } } #if defined(CLOCK_MONOTONIC_RAW) # define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW #elif defined(CLOCK_MONOTONIC_FAST) # define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST #else # define X_CLOCK_MONOTONIC CLOCK_MONOTONIC #endif INLINE long double get_now_monotonic(void) { time_t sec; long msec; get_now(X_CLOCK_MONOTONIC, &sec, &msec); return (long double)sec + ((long double)msec) / 1000; } INLINE uint64_t get_now_monotonic_u64(void) { struct timespec ts; assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts)); return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000; } INLINE uint64_t get_now_id(void) { uint64_t now = get_now_monotonic_u64(); return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32); } #undef X_CLOCK_MONOTONIC INLINE long double get_now_real(void) { time_t sec; long msec; get_now(CLOCK_REALTIME, &sec, &msec); return (long double)sec + ((long double)msec) / 1000; } INLINE unsigned get_cores_available(void) { long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN); cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf); return max_u(min_u(cores_sysconf, 4), 1); } INLINE int flock_timedwait_monotonic(int fd, long double timeout) { long double deadline_ts = get_now_monotonic() + timeout; int retval = -1; while (true) { retval = flock(fd, LOCK_EX | LOCK_NB); if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) { break; } if (usleep(1000) < 0) { break; } } return retval; } INLINE char *errno_to_string(int error, char *buf, size_t size) { assert(buf); assert(size > 0); locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL); char *str = "!!! newlocale() error !!!"; strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1); buf[size - 1] = '\0'; if (locale) { freelocale(locale); } return buf; } ustreamer-4.9/src/libs/unjpeg.c000066400000000000000000000065221414164060100165530ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "unjpeg.h" typedef struct { struct jpeg_error_mgr mgr; // Default manager jmp_buf jmp; const frame_s *frame; } _jpeg_error_manager_s; static void _jpeg_error_handler(j_common_ptr jpeg); int unjpeg(const frame_s *src, frame_s *dest, bool decode) { assert(is_jpeg(src->format)); volatile int retval = 0; struct jpeg_decompress_struct jpeg; jpeg_create_decompress(&jpeg); // https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg _jpeg_error_manager_s jpeg_error; jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error); jpeg_error.mgr.error_exit = _jpeg_error_handler; jpeg_error.frame = src; if (setjmp(jpeg_error.jmp) < 0) { retval = -1; goto done; } jpeg_mem_src(&jpeg, src->data, src->used); jpeg_read_header(&jpeg, TRUE); jpeg.out_color_space = JCS_RGB; jpeg_start_decompress(&jpeg); frame_copy_meta(src, dest); dest->format = V4L2_PIX_FMT_RGB24; dest->width = jpeg.output_width; dest->height = jpeg.output_height; dest->stride = jpeg.output_width * jpeg.output_components; // Row stride dest->used = 0; if (decode) { JSAMPARRAY scanlines; scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1); frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2); while (jpeg.output_scanline < jpeg.output_height) { jpeg_read_scanlines(&jpeg, scanlines, 1); frame_append_data(dest, scanlines[0], dest->stride); } jpeg_finish_decompress(&jpeg); } done: jpeg_destroy_decompress(&jpeg); return retval; } static void _jpeg_error_handler(j_common_ptr jpeg) { _jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s *)jpeg->err; char msg[JMSG_LENGTH_MAX]; (*jpeg_error->mgr.format_message)(jpeg, msg); LOG_ERROR("Can't decompress JPEG: %s", msg); longjmp(jpeg_error->jmp, -1); } ustreamer-4.9/src/libs/unjpeg.h000066400000000000000000000035221414164060100165550ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include "logging.h" #include "frame.h" int unjpeg(const frame_s *src, frame_s *dest, bool decode); ustreamer-4.9/src/ustreamer/000077500000000000000000000000001414164060100161705ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/blank.c000066400000000000000000000063331414164060100174300ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "blank.h" static frame_s *_init_internal(void); static frame_s *_init_external(const char *path); frame_s *blank_frame_init(const char *path) { frame_s *blank = NULL; if (path && path[0] != '\0') { blank = _init_external(path); } if (blank) { LOG_INFO("Using external blank placeholder: %s", path); } else { blank = _init_internal(); LOG_INFO("Using internal blank placeholder"); } return blank; } static frame_s *_init_internal(void) { frame_s *blank = frame_init(); frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE); blank->width = BLANK_JPEG_WIDTH; blank->height = BLANK_JPEG_HEIGHT; blank->format = V4L2_PIX_FMT_JPEG; return blank; } static frame_s *_init_external(const char *path) { FILE *fp = NULL; frame_s *blank = frame_init(); blank->format = V4L2_PIX_FMT_JPEG; if ((fp = fopen(path, "rb")) == NULL) { LOG_PERROR("Can't open blank placeholder '%s'", path); goto error; } # define CHUNK_SIZE ((size_t)(100 * 1024)) while (true) { if (blank->used + CHUNK_SIZE >= blank->allocated) { frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2); } size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp); blank->used += readed; if (readed < CHUNK_SIZE) { if (feof(fp)) { break; } else { LOG_PERROR("Can't read blank placeholder"); goto error; } } } # undef CHUNK_SIZE frame_s *decoded = frame_init(); if (unjpeg(blank, decoded, false) < 0) { frame_destroy(decoded); goto error; } blank->width = decoded->width; blank->height = decoded->height; frame_destroy(decoded); goto ok; error: frame_destroy(blank); blank = NULL; ok: if (fp) { fclose(fp); } return blank; } ustreamer-4.9/src/ustreamer/blank.h000066400000000000000000000034771414164060100174430ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include "../libs/tools.h" #include "../libs/logging.h" #include "../libs/frame.h" #include "../libs/unjpeg.h" #include "data/blank_jpeg.h" frame_s *blank_frame_init(const char *path); ustreamer-4.9/src/ustreamer/data/000077500000000000000000000000001414164060100171015ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/data/blank.jpeg000066400000000000000000000330251414164060100210420ustar00rootroot00000000000000 Phttp://ns.adobe.com/xap/1.0/ ,Photoshop 3.08BIM%ُ B~PAdobed}    !"7w#159Au8$2QxV ?Яѯүӯԯկ֯ׯЯѯүӯԯկ֯ׯЯѯүӯԯկ֯ׯЯѯүӯL[ q./)eid3CqVvWIouiuo%TItEtU5q$E_a?|gNuLGq]S uqMc_nrVPQ⦛ #" /񧮻\mF68}[G\6k rYIUW4MzoDu:k0>W Es ~{#6RjnnkLv&.y8COkoW:磵V;kdq>Y WIs5%?hm1,]vǃЏ'vaie1X᳾qGWkgU1-܎֮C-:/sy%[Ft\[Yc ]9έ{Z.~TьU^M2E .뭿kwCzk=REZ'7UuEsd2WMxXm2]Y95{$YQŔZ͟U[KF;sTRW$[E=A"CJ;LVڇ${(ŌnK9f32Li(h`//;gӟHw1sa{z#o+: ؘKhkx䦹I""q cRK5_$w^5u8tuѻ Xu}yqխ1T؋oxIc/ >~-tWYFj:S oOIlUNvK*gMMf≭Ý>wxE>[r+{JvQ[d?TfsLL}U-Z Ds}Eo_M +_٭&un-k8*b3o G +bj#Z#~}8 Hzfyry^aw=Gޛ\^Ɉ~fLv[Lc0kzb]c~=Sxx{3~ݚgKK}[n:)='~y^WWN>wgNy^;3T{;tcqOqqeڰyk}q]qUU_.xWYQQDQE8㧊#:.(8(88yԯWoߗL ||Mzۼ1uG3fl&EmK 4]=)GgJcIx5u,Pӏj{vbdfd_ck{u\|m(o+ ,(x|xX['KWq>*Nq{:r*am_؛ǘQSL][ڇzl/3==,0`=\28[S#,ML0gk_5U_馞96h̷MV4I6v&d:,`kWq4UqET={E5UǵUIU}6w`7d6fK:?!g$WWM_y~9BE~4}GߧO*:Y v?ow]B}.i);C٤+{k\ 9i:!AЛux׺w!λi]swYṯ wnNyL2O-rWso{wM(pgPzջt&#k6|LQ\ؽZUTY+)byyA5 :xc.;g+G[ߵ+ڍ/brZ.:}Μy=瀄h$GߠVgUd-4K\ԭov+msg8QsM?? w_0_96;/mkzN.q2?Qu}=ghy*G. zy9x\[;2J괹♤|q-͕ͼ}<]atgUv75|ͯe"wVڄUPIŭ5Wowk,sC]qIEu.s߹lɁ*@_uN"m?Zڵ޿e!:Njo6tUM~SFΨ~7Hf=]Ͻ6:ww'b1|m#^F gg:!.>`6SG5q W<㫎RL7⾝p{gs_֯WoߗL CO^w>I#HX)b#:H䣞*+**x}MkDyY(yIyw]g7=;K{RQ6^nn;kJNgsU<윗\_gqǗ68#dtݎ+~l2e-N->TUQ\C!(Ğ;09;kcgo_;c!9>NYp(*khSM6pg=}Dچ^XS@[NZ/cUUb7FGV/oE3wʟr>^KˊG1[6vQdm]]%wid;~cmmwmUR\qGo(As9 ?J.72j}6\+$xװ65\ťG AN"Z+a-;|UE2E45-RsU%?߿MOƠqkG}|vMgu9ofpSi]/:\\[˚v{H㛜>^+zx=GqqrRaEEK,nrEcmե^~%4uy+VS]ͅSTqWa}y;䞑к$\ձg`կy5AysylG2WKڿ^rbOoӇ ksO;uKP Sv鍚>w2a>qW5F( WRuaͿ9b1~$qD;.}wcU\G(_^u_&T ;;-׿Jgո5K=kdۣGY~{}j~A`9簻Fk&p5;-neqEseDTqU<r SmX=ˮ7[ӹWlyvܝ|]lrs<QkMT{Ek&ègq[Fj6 brL%k_!gcmމa(Ύ|>3]9׻$, =1OOۺ_.l!?,uZWԵ>#m:;wL*l[M9I(Yd۟1?4Z1|3=i5S:)gڱrSs7,K!^|mݵ_f Xs"/ջרn[~ sÜˮ5|ˊ~kf&y珅|WN .w \\kXp8e%*&)y⪸珗dr|_y-r8̞:{;!c|_5(Яѯүӯԯկ֯ׯЯѯүӯԯկ֯ׯЯѯүӯԯկ֯ׯЯѯүustreamer-4.9/src/ustreamer/data/blank_jpeg.c000066400000000000000000002470341414164060100213530ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "blank_jpeg.h" const unsigned BLANK_JPEG_WIDTH = 640; const unsigned BLANK_JPEG_HEIGHT = 480; const size_t BLANK_JPEG_DATA_SIZE = 13845; const uint8_t BLANK_JPEG_DATA[] = { 0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D, 0x22, 0x57, 0x35, 0x4D, 0x30, 0x4D, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7A, 0x72, 0x65, 0x53, 0x7A, 0x4E, 0x54, 0x63, 0x7A, 0x6B, 0x63, 0x39, 0x64, 0x22, 0x3F, 0x3E, 0x20, 0x3C, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x78, 0x3D, 0x22, 0x61, 0x64, 0x6F, 0x62, 0x65, 0x3A, 0x6E, 0x73, 0x3A, 0x6D, 0x65, 0x74, 0x61, 0x2F, 0x22, 0x20, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x74, 0x6B, 0x3D, 0x22, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x58, 0x4D, 0x50, 0x20, 0x43, 0x6F, 0x72, 0x65, 0x20, 0x35, 0x2E, 0x36, 0x2D, 0x63, 0x31, 0x33, 0x38, 0x20, 0x37, 0x39, 0x2E, 0x31, 0x35, 0x39, 0x38, 0x32, 0x34, 0x2C, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2F, 0x30, 0x39, 0x2F, 0x31, 0x34, 0x2D, 0x30, 0x31, 0x3A, 0x30, 0x39, 0x3A, 0x30, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x3E, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3A, 0x72, 0x64, 0x66, 0x3D, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x77, 0x33, 0x2E, 0x6F, 0x72, 0x67, 0x2F, 0x31, 0x39, 0x39, 0x39, 0x2F, 0x30, 0x32, 0x2F, 0x32, 0x32, 0x2D, 0x72, 0x64, 0x66, 0x2D, 0x73, 0x79, 0x6E, 0x74, 0x61, 0x78, 0x2D, 0x6E, 0x73, 0x23, 0x22, 0x3E, 0x20, 0x3C, 0x72, 0x64, 0x66, 0x3A, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x72, 0x64, 0x66, 0x3A, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x3D, 0x22, 0x22, 0x2F, 0x3E, 0x20, 0x3C, 0x2F, 0x72, 0x64, 0x66, 0x3A, 0x52, 0x44, 0x46, 0x3E, 0x20, 0x3C, 0x2F, 0x78, 0x3A, 0x78, 0x6D, 0x70, 0x6D, 0x65, 0x74, 0x61, 0x3E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x65, 0x6E, 0x64, 0x3D, 0x22, 0x77, 0x22, 0x3F, 0x3E, 0xFF, 0xED, 0x00, 0x2C, 0x50, 0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70, 0x20, 0x33, 0x2E, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4D, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E, 0xFF, 0xDB, 0x00, 0x84, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x50, 0xFF, 0xEE, 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x00, 0x64, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0xE0, 0x02, 0x80, 0x03, 0x00, 0x11, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x7D, 0x00, 0x01, 0x00, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x07, 0x08, 0x09, 0x05, 0x06, 0x02, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x01, 0x04, 0x02, 0x01, 0x03, 0x02, 0x03, 0x04, 0x08, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x03, 0x02, 0x04, 0x05, 0x06, 0x01, 0x07, 0x08, 0x09, 0x11, 0x12, 0x0A, 0x13, 0x14, 0x21, 0x22, 0x15, 0x37, 0x77, 0xB6, 0x16, 0x23, 0x31, 0x35, 0x39, 0x41, 0x75, 0xB4, 0x17, 0x38, 0xB5, 0xB7, 0x18, 0x1A, 0x24, 0x32, 0x51, 0x78, 0x56, 0x97, 0xD4, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x4C, 0xE9, 0x5B, 0x0B, 0x1C, 0xAF, 0x71, 0xF5, 0x2E, 0x2F, 0x29, 0x65, 0x69, 0x92, 0xC6, 0x64, 0xBB, 0x33, 0x43, 0xB0, 0xC8, 0xE3, 0xAF, 0xED, 0xA1, 0xBC, 0xB1, 0xBF, 0xB1, 0xBC, 0xDA, 0x71, 0x56, 0xF7, 0x76, 0x57, 0xB6, 0x97, 0x14, 0x49, 0x6F, 0x75, 0x69, 0x75, 0x6F, 0x25, 0x54, 0x49, 0x1D, 0x74, 0xD5, 0x45, 0x74, 0x55, 0xCD, 0x35, 0x71, 0xCF, 0x1C, 0xF3, 0xC0, 0x24, 0x45, 0xEB, 0x5F, 0xE9, 0x19, 0x61, 0xD4, 0x3F, 0xB6, 0x7C, 0xBB, 0xF1, 0x67, 0x4E, 0xB6, 0xC6, 0x75, 0x4C, 0x95, 0x47, 0x71, 0xDC, 0x5D, 0x53, 0xAA, 0xE3, 0x20, 0xB3, 0xC6, 0x75, 0x94, 0xF5, 0x71, 0x15, 0xBF, 0xF4, 0xE7, 0x4D, 0xC2, 0x63, 0xA0, 0x8A, 0x1B, 0x1D, 0x02, 0xF6, 0x5F, 0x6E, 0x72, 0x56, 0x50, 0x51, 0xF6, 0xF0, 0xD3, 0xD7, 0xF7, 0xE2, 0xA6, 0x9B, 0x0A, 0xEB, 0xA2, 0xC4, 0x23, 0x22, 0x0D, 0xD8, 0xF4, 0xE3, 0xEB, 0xED, 0x2F, 0xB5, 0xBC, 0xE3, 0xF1, 0xA7, 0xAE, 0xBB, 0x17, 0x5C, 0xC7, 0x6D, 0xBA, 0x46, 0xDD, 0xD9, 0x36, 0x38, 0x7D, 0x97, 0x5B, 0xCB, 0x47, 0x5C, 0xB8, 0xDC, 0xC6, 0x36, 0x6B, 0x0C, 0x85, 0x72, 0x59, 0xDD, 0xD1, 0x1C, 0x91, 0x49, 0xCC, 0x55, 0x57, 0x1D, 0x3C, 0xFE, 0x9A, 0xA9, 0xE7, 0x8E, 0x78, 0xE3, 0x9E, 0x39, 0x06, 0xF5, 0xFA, 0xF4, 0x78, 0xE9, 0xD2, 0x3E, 0x34, 0xF9, 0x4D, 0xD5, 0x7A, 0x6F, 0x44, 0x75, 0xBE, 0xBB, 0xD6, 0x3A, 0xBE, 0x6B, 0xA0, 0x30, 0xBB, 0x3E, 0x57, 0x0B, 0xAD, 0x45, 0x73, 0x0D, 0x95, 0xEE, 0x7E, 0xE3, 0xB1, 0x7B, 0x23, 0x15, 0x36, 0x52, 0x6A, 0x6E, 0xAE, 0x6E, 0x6B, 0xE6, 0xEA, 0x4C, 0x76, 0x26, 0xDA, 0x2E, 0x79, 0xE2, 0xAE, 0x38, 0xF8, 0x43, 0x4F, 0xE5, 0xFF, 0x00, 0xC8, 0x6B, 0xE7, 0xA6, 0xFF, 0x00, 0xA5, 0xD7, 0x6F, 0xFA, 0x83, 0xED, 0x57, 0xD7, 0xF8, 0xEB, 0xFE, 0x3A, 0xE7, 0xA3, 0xB5, 0x1C, 0x8C, 0x56, 0x3B, 0xCF, 0x6B, 0x64, 0x71, 0xF5, 0xE4, 0x3E, 0x59, 0x0A, 0xA0, 0xA6, 0xEB, 0x8D, 0x57, 0x49, 0xC4, 0x73, 0x35, 0xA5, 0x1B, 0x1E, 0xD3, 0x25, 0xBC, 0x91, 0xD7, 0x3F, 0xCA, 0x68, 0x6D, 0x31, 0xD6, 0xF2, 0xD3, 0x2C, 0xF5, 0xF3, 0x5D, 0x76, 0xF6, 0xF7, 0x01, 0xDA, 0xEE, 0xC7, 0x83, 0xD0, 0x8F, 0xD3, 0x07, 0x27, 0xFF, 0x00, 0x0E, 0x76, 0x0E, 0xA9, 0xAF, 0xCA, 0xCE, 0xF4, 0xD7, 0x61, 0xAE, 0xD3, 0x69, 0xC6, 0x65, 0x31, 0x98, 0xCE, 0xEE, 0xCB, 0x58, 0xE5, 0xE1, 0xB3, 0xBE, 0xB0, 0xA6, 0x1D, 0xEA, 0xDF, 0x71, 0xC9, 0xE0, 0xBA, 0x47, 0x57, 0xBF, 0xAA, 0x6B, 0xA9, 0x7F, 0x11, 0x8F, 0xB1, 0xB2, 0xFD, 0xA3, 0x67, 0x55, 0x31, 0xCD, 0x2D, 0x9F, 0xDC, 0x8E, 0xD6, 0xAE, 0x43, 0x15, 0xEA, 0xFE, 0xA3, 0x9E, 0x87, 0x9D, 0xD5, 0x2D, 0x3A, 0x2F, 0x73, 0xFA, 0x79, 0xEB, 0x1D, 0x25, 0x84, 0xCA, 0xDF, 0x5B, 0xC1, 0x46, 0xE7, 0xAB, 0x74, 0xDF, 0x5C, 0x5B, 0x59, 0x63, 0x20, 0xB8, 0xB7, 0xBB, 0xB4, 0xBA, 0xBE, 0xCD, 0xEC, 0x5D, 0x39, 0xCE, 0xAD, 0xD9, 0xD8, 0xCB, 0x7B, 0x5A, 0x2E, 0x7E, 0x54, 0xD1, 0x8C, 0xB4, 0xC8, 0xD7, 0x55, 0x5E, 0xD2, 0xF1, 0x4D, 0x32, 0x45, 0x1F, 0x20, 0xF1, 0xFC, 0xCA, 0xF4, 0x2E, 0xEB, 0xAD, 0xBF, 0xAB, 0x6B, 0xF2, 0x77, 0xD3, 0x43, 0x7A, 0xA7, 0xB2, 0xB4, 0x6B, 0xDC, 0x3D, 0xCE, 0xD3, 0x1F, 0x52, 0xF1, 0xB2, 0x45, 0xBA, 0xC1, 0x9C, 0xC2, 0x5A, 0xD1, 0x27, 0x37, 0x55, 0x75, 0x06, 0xEB, 0x45, 0x73, 0x64, 0x32, 0xB9, 0x1B, 0x0F, 0xC3, 0x57, 0x4D, 0x78, 0x3C, 0xBC, 0xB7, 0x59, 0x09, 0xAE, 0x28, 0x96, 0x28, 0xEE, 0xF9, 0xB9, 0xA6, 0x3B, 0x2A, 0xC2, 0x30, 0x12, 0x47, 0x24, 0x52, 0x57, 0x14, 0xB4, 0x57, 0x14, 0xB1, 0x57, 0x54, 0x72, 0x47, 0x25, 0x3C, 0xD1, 0x24, 0x72, 0x51, 0xCF, 0x34, 0xD7, 0x45, 0x74, 0x55, 0xC7, 0x15, 0x51, 0x5D, 0x15, 0x71, 0xCF, 0x1C, 0xF1, 0xCF, 0x1E, 0xFC, 0x72, 0x0F, 0xC0, 0x3A, 0x19, 0xE9, 0xA3, 0xE0, 0x76, 0x6B, 0xD4, 0x03, 0xC8, 0x9B, 0x6E, 0xAE, 0xE7, 0x33, 0x79, 0xAA, 0x75, 0xDE, 0xAB, 0x85, 0x97, 0x75, 0xED, 0x5D, 0xBB, 0x1F, 0x14, 0x32, 0x64, 0xF1, 0x7A, 0xAD, 0xB5, 0xF5, 0x9E, 0x3A, 0x1C, 0x56, 0xBD, 0xC5, 0xE4, 0x17, 0x18, 0xFA, 0xB6, 0x8D, 0x8F, 0x25, 0x7D, 0x1D, 0xBD, 0xA7, 0xDF, 0xA2, 0xB8, 0xE0, 0x8B, 0x89, 0xEE, 0xAA, 0x8E, 0x6A, 0x6D, 0xEA, 0x86, 0xB0, 0x90, 0x37, 0x92, 0x7D, 0xB9, 0xE8, 0xB9, 0xE9, 0x95, 0x9B, 0x8F, 0xC7, 0x5B, 0x0F, 0x0C, 0x74, 0x8F, 0x21, 0x3B, 0x3F, 0x09, 0x69, 0x8C, 0x9F, 0x70, 0xB4, 0xCB, 0xE9, 0x1A, 0x5F, 0x67, 0xDF, 0xEB, 0x52, 0x5E, 0x5B, 0x71, 0x91, 0x86, 0x9D, 0xC3, 0xB1, 0x7B, 0x82, 0xBC, 0xFD, 0xF5, 0xAE, 0xC9, 0x95, 0xB5, 0xC8, 0x53, 0x77, 0xC6, 0x3B, 0x19, 0x0C, 0x90, 0x43, 0x0C, 0x94, 0x53, 0x5D, 0x36, 0x91, 0xD3, 0x6F, 0x10, 0x3F, 0x5B, 0x0F, 0x40, 0xFA, 0x35, 0xFA, 0x99, 0x78, 0xCF, 0xDA, 0x3D, 0xD7, 0xD2, 0xB0, 0x6A, 0x9E, 0x21, 0x6D, 0x5D, 0x51, 0x87, 0x97, 0x2B, 0xB9, 0x6C, 0x18, 0xDD, 0x73, 0x0F, 0xD6, 0x37, 0xBD, 0x51, 0x27, 0x18, 0xDA, 0xB8, 0xC4, 0xCB, 0xD8, 0xFD, 0x51, 0xAF, 0xE4, 0xB9, 0xEB, 0xFD, 0x9B, 0x4F, 0xCE, 0xF3, 0x8E, 0xA7, 0xED, 0x5C, 0x62, 0x6A, 0xAA, 0x6B, 0xDB, 0x88, 0xE5, 0x86, 0xD6, 0xFA, 0x3B, 0xDE, 0x6E, 0xA1, 0xE4, 0x38, 0x81, 0xE8, 0xDF, 0xD6, 0xDD, 0x77, 0xD8, 0xFE, 0xA4, 0x7D, 0x35, 0xD7, 0xDD, 0x89, 0xAA, 0xE8, 0xFD, 0xB3, 0xA1, 0xDE, 0xDB, 0x77, 0x0C, 0x77, 0xD8, 0x3D, 0xC3, 0x56, 0xB1, 0xDA, 0x74, 0xDD, 0x8E, 0x8C, 0x3F, 0x53, 0xEF, 0x97, 0xD8, 0x9C, 0x8C, 0xDA, 0xDE, 0xE1, 0x88, 0xAE, 0x1B, 0x88, 0x63, 0xBE, 0xB2, 0x86, 0xF2, 0xD7, 0x8B, 0xBB, 0x3A, 0x27, 0x86, 0x5A, 0x28, 0xAF, 0x9A, 0x23, 0x96, 0x9F, 0x6A, 0x43, 0xEB, 0xFD, 0x72, 0xFA, 0xCB, 0xAD, 0xFA, 0x93, 0xCF, 0x7D, 0x8F, 0x4E, 0xEA, 0x9E, 0xBE, 0xD2, 0x3A, 0xCB, 0x51, 0x83, 0xAC, 0xFA, 0xDA, 0xFE, 0x0D, 0x5B, 0xAF, 0x75, 0x3C, 0x0E, 0x97, 0xAE, 0x43, 0x7D, 0x7D, 0x8B, 0xBA, 0x92, 0xFA, 0xF6, 0x2C, 0x1E, 0xB9, 0x61, 0x8D, 0xC6, 0x47, 0x77, 0x79, 0x25, 0x3C, 0x55, 0x2C, 0x9C, 0x45, 0xC5, 0x72, 0x73, 0xC7, 0xBD, 0x5C, 0xF3, 0xC8, 0x35, 0xA7, 0xC0, 0x5F, 0x4F, 0xEE, 0xE3, 0xF5, 0x01, 0xED, 0x59, 0xF4, 0x3E, 0xBA, 0xAE, 0xDB, 0x58, 0xD3, 0xF5, 0x98, 0x6D, 0x32, 0x5D, 0x99, 0xDA, 0x59, 0xAB, 0x39, 0xEF, 0x35, 0xED, 0x17, 0x11, 0x7B, 0x24, 0xD4, 0x59, 0x51, 0xC5, 0x94, 0x13, 0x5A, 0xCD, 0x9F, 0xD9, 0xF3, 0x55, 0x5B, 0x4B, 0x46, 0x3B, 0x17, 0x14, 0xD0, 0xD7, 0x73, 0x54, 0x52, 0x57, 0x24, 0xB0, 0x5B, 0x45, 0x3D, 0xC4, 0x41, 0x22, 0xCE, 0xC0, 0xEA, 0x0F, 0x43, 0xCF, 0x4A, 0x3B, 0x4C, 0x56, 0xA9, 0xDC, 0xDA, 0x87, 0xFE, 0x24, 0x7B, 0xD2, 0x28, 0xAC, 0xEF, 0xF2, 0x9A, 0xEE, 0xC5, 0x8C, 0xB0, 0xEE, 0x6E, 0xC0, 0xB8, 0x8E, 0x4B, 0x39, 0x66, 0x8A, 0xEF, 0x33, 0xA2, 0xE5, 0x32, 0x1A, 0xFF, 0x00, 0x4C, 0xE8, 0xF8, 0xA9, 0x69, 0xBC, 0xF9, 0xDA, 0xDA, 0xE4, 0x28, 0xB3, 0xBC, 0xBB, 0x86, 0x68, 0xA4, 0xF7, 0xBC, 0xA6, 0x1F, 0xBF, 0x18, 0x60, 0xAD, 0x2F, 0xD4, 0xD7, 0xD1, 0x2F, 0xB3, 0xAF, 0xE6, 0xD3, 0x3B, 0x67, 0xD3, 0x9F, 0x48, 0xE9, 0xDD, 0x77, 0x31, 0x73, 0x61, 0x04, 0x7B, 0xB6, 0x1B, 0xA1, 0x7A, 0x7F, 0x23, 0x6F, 0x8C, 0x8A, 0x2B, 0x9A, 0xAE, 0xE6, 0xBC, 0xCC, 0xE4, 0x3A, 0xDA, 0xC7, 0x0B, 0xD8, 0x98, 0x4B, 0x68, 0x6B, 0xB5, 0x86, 0x9E, 0x78, 0xC3, 0xDB, 0xE4, 0xA6, 0xB9, 0xA2, 0x49, 0x22, 0x92, 0x8E, 0x22, 0xF9, 0x71, 0x20, 0x63, 0x8F, 0x52, 0xDF, 0x4B, 0xCF, 0x07, 0x35, 0x5F, 0x19, 0x24, 0xF3, 0x77, 0xC3, 0x5E, 0xEE, 0xD6, 0x35, 0x8D, 0x06, 0xE3, 0x9C, 0x75, 0x38, 0x9D, 0x16, 0xF7, 0x74, 0x9F, 0x75, 0xD1, 0xBB, 0x0A, 0xEA, 0xFE, 0xEA, 0x58, 0xEB, 0xC2, 0x75, 0xB6, 0xC7, 0x7D, 0x79, 0x95, 0xDB, 0x71, 0xDB, 0xD5, 0xAD, 0x31, 0xCF, 0x54, 0xD8, 0x8B, 0xD9, 0x6F, 0x78, 0xA7, 0x9B, 0x49, 0x63, 0x93, 0xF0, 0x1C, 0xC1, 0x2F, 0x20, 0xC1, 0x3E, 0x85, 0x7E, 0x2D, 0x74, 0x8F, 0x97, 0x1B, 0x57, 0x96, 0x9D, 0x59, 0xDD, 0xFA, 0x46, 0x1B, 0x6A, 0xC3, 0xE4, 0x3A, 0x53, 0x0B, 0x6F, 0x84, 0xCD, 0x4F, 0x8E, 0xC7, 0x49, 0xB5, 0xE8, 0x99, 0x6C, 0x86, 0xCF, 0x55, 0xBD, 0x1B, 0x4E, 0x85, 0x9F, 0xBB, 0xB3, 0xBA, 0xBB, 0xD6, 0x76, 0x4B, 0x2A, 0xA9, 0xA2, 0xAA, 0x67, 0x83, 0xF4, 0x4D, 0x4D, 0x1F, 0x66, 0xE2, 0x89, 0xAD, 0xEB, 0x92, 0x1A, 0xC3, 0x9D, 0x3E, 0x77, 0x78, 0x45, 0xDA, 0x3E, 0x07, 0xF7, 0x96, 0x5B, 0xA9, 0xBB, 0x02, 0x19, 0x72, 0x9A, 0xFD, 0xF7, 0x17, 0x19, 0xAE, 0xB2, 0xEC, 0x2B, 0x7B, 0x4A, 0xE0, 0xC2, 0x76, 0x0E, 0x9D, 0xCD, 0xCD, 0x51, 0x5B, 0x64, 0xED, 0x3F, 0x54, 0xB1, 0xD9, 0x66, 0xF1, 0xD5, 0x73, 0x4C, 0x19, 0x4C, 0x7D, 0x55, 0xD5, 0x2D, 0x95, 0xD7, 0xF9, 0xD7, 0x04, 0xB6, 0xF3, 0xCC, 0x1A, 0x5A, 0x09, 0x44, 0xFA, 0x03, 0xF8, 0x73, 0xE3, 0x0F, 0x93, 0x7D, 0x45, 0xE4, 0x06, 0x6F, 0xBE, 0xFA, 0x5F, 0x4D, 0xED, 0x0C, 0xB6, 0xAF, 0xD8, 0xFA, 0xC6, 0x2B, 0x5F, 0xBF, 0xD9, 0xAD, 0xAF, 0x26, 0xB8, 0xC5, 0xE3, 0xAF, 0x75, 0x89, 0x6E, 0xEE, 0xAD, 0x2D, 0xAB, 0xB5, 0xBC, 0xB5, 0xF6, 0x86, 0x6B, 0x9A, 0x38, 0xAF, 0x9E, 0x2A, 0xE2, 0xAF, 0xD5, 0xFD, 0x9E, 0xDE, 0xFC, 0xFB, 0x84, 0x62, 0x33, 0x10, 0xC7, 0x6F, 0x97, 0xCA, 0xC1, 0x0D, 0x1C, 0x47, 0x0C, 0x19, 0x2B, 0xE8, 0x62, 0x8E, 0x9F, 0x7F, 0x6A, 0x23, 0x8E, 0xE6, 0x5A, 0x23, 0xA3, 0x8F, 0x7F, 0x7E, 0x7D, 0xA9, 0xA6, 0x9E, 0x38, 0x07, 0x9A, 0x09, 0x48, 0xFA, 0x14, 0x7A, 0x66, 0xF5, 0x9F, 0x79, 0xF4, 0xF7, 0x72, 0x79, 0x05, 0xE4, 0x87, 0x5E, 0x61, 0x77, 0x3D, 0x47, 0xB2, 0xB1, 0x99, 0xDE, 0x9B, 0xEA, 0x5C, 0x5E, 0xC9, 0x88, 0xB0, 0xC8, 0xFE, 0x03, 0x1B, 0x17, 0xCE, 0xDB, 0x7E, 0xEC, 0xDD, 0x66, 0x4C, 0x84, 0x17, 0xB1, 0xE2, 0x76, 0x5B, 0x4C, 0xD5, 0x11, 0x63, 0x30, 0xD9, 0x6B, 0x7A, 0x62, 0xBF, 0xC6, 0x5D, 0x63, 0xEF, 0xF9, 0x8A, 0xBA, 0x7E, 0xE7, 0x1C, 0x82, 0x3D, 0xDE, 0x53, 0x78, 0xF1, 0xB9, 0x78, 0xA7, 0xDF, 0xFD, 0x9F, 0xD0, 0x7B, 0xD4, 0x33, 0x7E, 0xD8, 0xEB, 0xDD, 0x9A, 0xF7, 0x17, 0x67, 0x93, 0x92, 0xD6, 0x4B, 0x4B, 0x7D, 0x9B, 0x5B, 0x96, 0xAF, 0xC5, 0xEA, 0xDB, 0x6E, 0x3A, 0x29, 0x3D, 0xF9, 0xE3, 0x1D, 0xB3, 0xE0, 0x27, 0xB7, 0xBD, 0x8B, 0x8F, 0x7E, 0x79, 0x8F, 0x89, 0xBE, 0xDD, 0x5E, 0xD5, 0xD1, 0x57, 0x1C, 0x06, 0xBF, 0x82, 0x57, 0x7F, 0x4E, 0xFF, 0x00, 0x8E, 0x9E, 0x3E, 0x77, 0x67, 0x4E, 0x79, 0x17, 0x94, 0xEE, 0x5E, 0x8A, 0xE9, 0xBE, 0xDB, 0xC9, 0xE1, 0x3B, 0x33, 0x54, 0xB0, 0xC2, 0xE4, 0x7B, 0x3B, 0xAC, 0x74, 0x9D, 0xF6, 0xFB, 0x11, 0x63, 0x71, 0xAB, 0x4F, 0x71, 0x71, 0x65, 0x8B, 0xBB, 0xDA, 0xB0, 0x79, 0x6B, 0x8C, 0x7D, 0xA4, 0xF7, 0x1C, 0x71, 0x5D, 0x71, 0xC5, 0x55, 0x14, 0x55, 0x5F, 0x1F, 0x2E, 0x78, 0xE7, 0x9F, 0xCC, 0x11, 0x57, 0xCD, 0xC7, 0x1C, 0x59, 0x9C, 0xBC, 0x51, 0x51, 0x44, 0x51, 0x45, 0x93, 0xBF, 0x8E, 0x38, 0xE3, 0xA7, 0x8A, 0x23, 0x8E, 0x3A, 0x2E, 0xA5, 0xA6, 0x8A, 0x28, 0xA2, 0x9E, 0x38, 0xA6, 0x8A, 0x28, 0xA7, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xF6, 0xE3, 0x80, 0x79, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E, 0x0B, 0x0F, 0x7C, 0xC4, 0xF3, 0x8B, 0xAA, 0x7C, 0x4D, 0xED, 0x0F, 0x1B, 0x7A, 0xDB, 0xBC, 0xEC, 0x31, 0x90, 0x75, 0x47, 0x93, 0x95, 0xF6, 0x96, 0x95, 0x9E, 0xDD, 0x33, 0x1F, 0x66, 0x6C, 0x26, 0x9F, 0x96, 0xC1, 0x45, 0xA0, 0xC1, 0x84, 0xE7, 0x6D, 0xB2, 0xBB, 0xA2, 0x4B, 0x09, 0x34, 0x5D, 0x82, 0x3D, 0xBE, 0xEA, 0xD3, 0x29, 0x3C, 0xDC, 0x73, 0x1D, 0x9F, 0x15, 0x45, 0x34, 0xBE, 0xD6, 0xB4, 0xDC, 0x55, 0x48, 0x45, 0x0F, 0xD6, 0x3B, 0xD2, 0xB2, 0xEF, 0xC3, 0xFD, 0xBA, 0x6E, 0xFE, 0xE8, 0xEC, 0x54, 0xF9, 0x2F, 0x17, 0x7B, 0x0B, 0x33, 0xF2, 0x92, 0xCA, 0xC2, 0x9A, 0xEE, 0xFF, 0x00, 0xE0, 0xBE, 0xD1, 0x99, 0x9A, 0xB9, 0xAD, 0xB5, 0x8B, 0xE9, 0x69, 0xAA, 0x5A, 0xEB, 0xD1, 0x33, 0x32, 0x57, 0xED, 0x83, 0xC8, 0x57, 0xCF, 0x34, 0xC3, 0x5F, 0x3C, 0x58, 0x5C, 0x55, 0xC4, 0xDF, 0x84, 0x96, 0xF4, 0x34, 0xC3, 0xD2, 0x8B, 0xFC, 0x46, 0x3C, 0x46, 0xFE, 0x2D, 0xE3, 0x3F, 0xE9, 0xD9, 0x30, 0x74, 0xFF, 0x00, 0xEA, 0x34, 0xD7, 0x72, 0x9B, 0x7F, 0x9E, 0x1E, 0x3B, 0x6A, 0x78, 0x48, 0xA8, 0xB8, 0xCD, 0x6D, 0x1E, 0x3F, 0xE9, 0x3A, 0xEE, 0x22, 0x09, 0x24, 0xA6, 0x18, 0xE6, 0xCA, 0x66, 0xBB, 0x93, 0xB4, 0x71, 0xB8, 0xF8, 0xAB, 0x96, 0xAF, 0xD3, 0x15, 0x12, 0x5D, 0xDC, 0xD1, 0xC7, 0x35, 0x73, 0xF9, 0x53, 0xC7, 0x3E, 0xE0, 0xEA, 0xF7, 0xA8, 0x47, 0x67, 0xD9, 0xFA, 0x4A, 0xFA, 0x63, 0xF5, 0xFF, 0x00, 0x49, 0x78, 0xF7, 0x3C, 0x78, 0x1D, 0xEB, 0x3D, 0x06, 0x2F, 0xA5, 0xB4, 0xCD, 0xA2, 0xCE, 0xDE, 0x2B, 0x7C, 0x95, 0xAE, 0x46, 0xFB, 0x15, 0x7F, 0x9E, 0xED, 0x4E, 0xD8, 0xAE, 0x88, 0xFE, 0xD7, 0x1C, 0x6C, 0xB9, 0x4A, 0xA1, 0xBB, 0x96, 0x39, 0xE9, 0xE7, 0xE5, 0x6B, 0x95, 0xCB, 0x43, 0x35, 0x34, 0xF3, 0x44, 0x3F, 0x00, 0x41, 0x76, 0x69, 0xA6, 0xB9, 0x9A, 0x5B, 0x8B, 0x89, 0x64, 0x9E, 0xE2, 0x79, 0x2B, 0x9A, 0x79, 0xE6, 0xAE, 0xB9, 0x66, 0x9A, 0x69, 0x6B, 0xE6, 0xB9, 0x65, 0x96, 0x5A, 0xF9, 0xAA, 0xB9, 0x24, 0x92, 0xBA, 0xB9, 0xE6, 0xAA, 0xB9, 0xE7, 0x9E, 0x79, 0xE7, 0x9F, 0x7E, 0x41, 0xFC, 0xC1, 0xDF, 0xBF, 0x40, 0x9F, 0x36, 0x76, 0xBE, 0x97, 0xF2, 0x73, 0x13, 0xE3, 0x0E, 0xC5, 0x99, 0xBC, 0xBC, 0xE9, 0xDF, 0x21, 0x6E, 0xAE, 0xB1, 0xD8, 0xEC, 0x3D, 0xD4, 0xF2, 0x4D, 0x65, 0xA9, 0x76, 0xBD, 0xAE, 0x36, 0x7B, 0xBD, 0x73, 0x3F, 0x88, 0x8A, 0x49, 0x79, 0xA2, 0xC3, 0x8D, 0xAE, 0x3B, 0x1E, 0x71, 0x17, 0xF1, 0xC3, 0x47, 0x1F, 0x8B, 0x96, 0x6B, 0x29, 0x24, 0xE7, 0xDA, 0xD6, 0x90, 0x7C, 0x6F, 0xAF, 0xBF, 0x8B, 0x9A, 0xF7, 0x40, 0x79, 0x9F, 0x47, 0x60, 0x69, 0x58, 0xEB, 0x6C, 0x4E, 0xA9, 0xE4, 0x7E, 0xB3, 0x37, 0x65, 0xDD, 0xE3, 0x2D, 0x22, 0xAE, 0x1B, 0x5B, 0x2E, 0xC5, 0xB5, 0xCB, 0x5C, 0xE2, 0xFB, 0x0E, 0xAB, 0x68, 0xFE, 0x1C, 0xC5, 0xF6, 0xF3, 0x97, 0x75, 0x5A, 0x66, 0x26, 0xE7, 0x8A, 0xF9, 0xE7, 0x9B, 0xDC, 0x9C, 0xFC, 0x7C, 0x28, 0xA3, 0x88, 0xF8, 0xA8, 0x38, 0x6C, 0x09, 0x2C, 0x7D, 0x34, 0x7D, 0x9B, 0xA8, 0x6B, 0x9E, 0x41, 0xF7, 0xF7, 0x58, 0x66, 0xB2, 0x56, 0x76, 0x1B, 0x5F, 0x66, 0xF5, 0xBE, 0xB5, 0x95, 0xD2, 0xED, 0xEE, 0xE6, 0xA2, 0x09, 0x33, 0x55, 0x75, 0xF6, 0x5F, 0x31, 0x75, 0xB0, 0xE2, 0x71, 0xBC, 0x49, 0x4F, 0x1F, 0x8B, 0xC9, 0x51, 0x8B, 0xD8, 0xF8, 0xBE, 0xFB, 0x14, 0x55, 0xF7, 0x39, 0xB4, 0xB2, 0x9E, 0x5E, 0x29, 0xAA, 0x88, 0x64, 0xAA, 0x80, 0xD3, 0x5F, 0x58, 0x8F, 0x07, 0x7C, 0x87, 0xE8, 0xCF, 0x2B, 0xBB, 0xCB, 0xBB, 0x36, 0x2D, 0x4F, 0x66, 0xDA, 0x7A, 0x67, 0xB8, 0xBB, 0x2F, 0x69, 0xEC, 0x6D, 0x5F, 0xB5, 0xF1, 0x76, 0x17, 0xB9, 0x8D, 0x67, 0x19, 0x1E, 0xE9, 0x97, 0xB9, 0xCE, 0x53, 0xA5, 0x6C, 0xF9, 0x3B, 0x68, 0xA6, 0x8B, 0x54, 0xCB, 0xEB, 0x72, 0xDE, 0x57, 0x61, 0x69, 0x6F, 0x7D, 0xCC, 0x1C, 0x5E, 0x5A, 0x5A, 0x53, 0x25, 0xAF, 0x32, 0x51, 0x4D, 0x7F, 0x6C, 0x38, 0xE2, 0x0E, 0xBD, 0x7A, 0x12, 0xFF, 0x00, 0x89, 0xEF, 0x8F, 0xBF, 0xE9, 0x3D, 0xCB, 0xFF, 0x00, 0x64, 0xFB, 0x04, 0x19, 0x07, 0xEA, 0x0D, 0xFF, 0x00, 0x11, 0xAD, 0xA3, 0xF8, 0x51, 0xD5, 0x9F, 0xF4, 0x8B, 0xC0, 0x77, 0x77, 0x44, 0xBF, 0xC6, 0x7A, 0x47, 0xFA, 0x2E, 0xE3, 0xBB, 0x13, 0x5D, 0xC2, 0xD9, 0xC3, 0xDC, 0x7B, 0x2E, 0x83, 0xAE, 0x6E, 0x57, 0x12, 0x5D, 0x58, 0x5B, 0xCD, 0x71, 0x91, 0xEF, 0x7E, 0xF1, 0x87, 0x17, 0x4E, 0x1E, 0x4D, 0x82, 0x2E, 0x7D, 0xAD, 0xF2, 0x36, 0x9D, 0x69, 0x8F, 0xC9, 0x5B, 0xC3, 0x24, 0x55, 0x57, 0xC5, 0x17, 0x16, 0x18, 0x1E, 0x63, 0xA7, 0x9E, 0x6B, 0x97, 0xDE, 0xA0, 0x84, 0x2E, 0xD1, 0xB4, 0x6C, 0x9B, 0xBE, 0xC9, 0x9D, 0xDC, 0x37, 0x0C, 0xEE, 0x57, 0x67, 0xDA, 0xB6, 0x7C, 0xAD, 0xF6, 0x73, 0x61, 0xD8, 0x73, 0x97, 0xD7, 0x19, 0x2C, 0xC6, 0x6B, 0x31, 0x92, 0xB8, 0x92, 0xEA, 0xFF, 0x00, 0x25, 0x92, 0xBF, 0xBA, 0x92, 0x5B, 0x8B, 0xBB, 0xCB, 0xBB, 0x89, 0x6A, 0xAE, 0xBA, 0xEB, 0xAB, 0x9A, 0xAA, 0xAB, 0x90, 0x78, 0x20, 0xF4, 0x79, 0xCC, 0x65, 0xAA, 0xC4, 0xC7, 0x80, 0xAB, 0x29, 0x91, 0xAB, 0x05, 0x16, 0x46, 0x6C, 0xC4, 0x58, 0x5E, 0x6F, 0x6E, 0x79, 0xC4, 0xC7, 0x96, 0xB8, 0xB6, 0x82, 0xCA, 0x7C, 0xA4, 0x78, 0xEE, 0x65, 0xFC, 0x1D, 0x19, 0x19, 0xEC, 0xED, 0xA3, 0x8A, 0xB9, 0xF8, 0xA3, 0x89, 0x6A, 0x8A, 0x3A, 0x69, 0xE6, 0xAE, 0x69, 0xA7, 0x8E, 0x38, 0x09, 0x2E, 0x7D, 0x32, 0x7F, 0xBF, 0x7F, 0x26, 0xFF, 0x00, 0x84, 0x9A, 0x9F, 0xF3, 0x8D, 0x40, 0xEB, 0x6E, 0xED, 0xB1, 0xF8, 0xBF, 0xEA, 0xF5, 0x8F, 0xF2, 0xB7, 0xC1, 0xAE, 0xCD, 0x82, 0xCB, 0x47, 0xEF, 0xBF, 0x1B, 0xBB, 0x6B, 0xB2, 0xF0, 0x1A, 0x96, 0x46, 0x3A, 0x60, 0xBC, 0xD8, 0xF0, 0xF6, 0x7A, 0x96, 0xDF, 0x93, 0xD6, 0x75, 0x5E, 0xE0, 0xD1, 0x3F, 0x15, 0x25, 0xBC, 0xD9, 0x7C, 0x3D, 0xD5, 0xBD, 0xBD, 0xBD, 0x86, 0xD1, 0x8B, 0xE2, 0x4A, 0x69, 0xE2, 0x49, 0x7E, 0x15, 0xF3, 0x0D, 0x37, 0x16, 0x17, 0x14, 0x84, 0x28, 0x7C, 0x99, 0xF1, 0xAF, 0xB5, 0x7C, 0x4A, 0xEE, 0x5D, 0xBB, 0xA3, 0xBB, 0x8B, 0x07, 0x56, 0x1B, 0x6D, 0xD5, 0x6E, 0xB8, 0xE6, 0x0B, 0xBB, 0x7F, 0xBB, 0x36, 0x0B, 0x69, 0xC0, 0x5D, 0x55, 0x25, 0x58, 0x5D, 0xBF, 0x55, 0xC8, 0xC9, 0x14, 0x3C, 0x65, 0x35, 0xCC, 0xED, 0xB4, 0x7C, 0xD7, 0x04, 0xBF, 0x1A, 0x24, 0x8A, 0x4A, 0x64, 0xB7, 0x9E, 0x88, 0x6E, 0x61, 0x9A, 0x18, 0xC2, 0x54, 0x7F, 0x4C, 0x97, 0xEE, 0x37, 0xC9, 0xEF, 0xE2, 0xBE, 0x9D, 0xFC, 0xA1, 0x70, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 0x28, 0xF8, 0xED, 0xD1, 0x9B, 0x97, 0x92, 0xDD, 0xDF, 0xD6, 0x5D, 0x13, 0xA0, 0xC1, 0xCC, 0xBB, 0x47, 0x65, 0xED, 0x78, 0xDD, 0x72, 0xD2, 0xE3, 0x98, 0x6A, 0xB8, 0xB7, 0xC3, 0xD8, 0xCF, 0x5F, 0x33, 0xE7, 0x36, 0x4C, 0x84, 0x54, 0x57, 0x1D, 0x75, 0x62, 0xB5, 0x8C, 0x1D, 0xBD, 0xCE, 0x42, 0xEF, 0xE3, 0x57, 0x15, 0x7E, 0x1A, 0xDA, 0xBF, 0x8F, 0xBD, 0x5E, 0xDC, 0x72, 0x12, 0xE5, 0xF5, 0x32, 0xF3, 0xA6, 0xC7, 0xD2, 0xDB, 0x59, 0xF0, 0xCB, 0xC4, 0xDF, 0x18, 0x78, 0x86, 0xDE, 0xEB, 0xAF, 0x26, 0xD1, 0xB7, 0x2D, 0xDF, 0x09, 0xC4, 0xF1, 0xF1, 0x3D, 0xFF, 0x00, 0x4C, 0x68, 0xD7, 0x3F, 0xB2, 0xE0, 0xD3, 0xF3, 0xF7, 0x54, 0x53, 0x54, 0x94, 0xDF, 0xF7, 0x16, 0x56, 0xDA, 0xFE, 0x6C, 0x85, 0xDF, 0x14, 0xF1, 0x71, 0xFF, 0x00, 0xA4, 0x92, 0x5E, 0x7F, 0x3B, 0x9E, 0x2A, 0xE4, 0x30, 0xE7, 0xAF, 0x17, 0x8F, 0x9A, 0x8F, 0x92, 0xFE, 0x35, 0xF4, 0xAF, 0xA8, 0xFF, 0x00, 0x47, 0x51, 0x46, 0x77, 0x1F, 0x8C, 0xD5, 0x75, 0x8B, 0x4D, 0xC7, 0x23, 0x65, 0x6F, 0x4F, 0x17, 0x39, 0x7E, 0xA0, 0xDF, 0x2B, 0x8B, 0x23, 0xA5, 0x67, 0xF2, 0x50, 0xC3, 0xF7, 0x6B, 0x82, 0xFB, 0x49, 0xDA, 0x33, 0x35, 0x59, 0x5D, 0xC5, 0x57, 0x3C, 0xCB, 0x07, 0x19, 0x6A, 0xE9, 0x97, 0x9A, 0x78, 0xB4, 0xE7, 0x8A, 0x42, 0x22, 0xC0, 0x98, 0xFF, 0x00, 0xD3, 0x25, 0xFB, 0x8D, 0xF2, 0x7B, 0xF8, 0xAF, 0xA7, 0x7F, 0x28, 0x5C, 0x02, 0x1E, 0xD9, 0xFF, 0x00, 0xEF, 0xDC, 0xD7, 0xFA, 0xB6, 0x47, 0xFD, 0xE4, 0xC0, 0xF2, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E, 0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC, 0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x06, 0x35, 0xF4, 0x75, 0xF5, 0x2C, 0xD3, 0xFB, 0x1B, 0x50, 0x87, 0xD3, 0x8F, 0xCD, 0x6A, 0xF1, 0x7B, 0x76, 0x89, 0xBA, 0x62, 0x64, 0xD0, 0xFA, 0x83, 0x66, 0xDE, 0x64, 0xA6, 0xF3, 0x17, 0x90, 0xC5, 0x5F, 0xDB, 0xD1, 0x63, 0x6B, 0xD2, 0x7B, 0x75, 0xCD, 0xE5, 0x5C, 0x7C, 0x6D, 0xA4, 0xA3, 0x8E, 0x28, 0xD6, 0x6F, 0x2B, 0x92, 0x99, 0x20, 0x9B, 0x8A, 0x2C, 0x28, 0xAE, 0x9E, 0x78, 0xB0, 0xA6, 0x80, 0xF8, 0x7C, 0x7F, 0xA6, 0xA6, 0xE3, 0xE0, 0x17, 0xAB, 0x8F, 0x89, 0x17, 0x78, 0x58, 0xF2, 0x5B, 0x27, 0x8E, 0x9D, 0x85, 0xDE, 0x11, 0x4B, 0xD5, 0x1B, 0xC4, 0xD4, 0x57, 0x71, 0x3E, 0x2A, 0x4E, 0x71, 0x99, 0x7B, 0xD9, 0x3A, 0xE7, 0x72, 0xB9, 0xA2, 0x2A, 0x61, 0x83, 0x6D, 0xC1, 0xDA, 0xD3, 0x5F, 0xD8, 0x9B, 0xF4, 0xC7, 0x98, 0xB1, 0x8B, 0xF1, 0x51, 0x53, 0x4C, 0x94, 0x5D, 0x5B, 0xDA, 0x87, 0xD9, 0x7A, 0xE9, 0x6C, 0xB8, 0x9D, 0x2F, 0xD5, 0x33, 0xC2, 0x3D, 0xC7, 0x3D, 0x2C, 0x30, 0x60, 0xF5, 0x3D, 0x17, 0xA4, 0xB6, 0x5C, 0xCC, 0xF7, 0x32, 0xC7, 0x05, 0xBC, 0x38, 0x9C, 0x17, 0x91, 0x5B, 0xEE, 0x53, 0x23, 0x2C, 0xF3, 0x4D, 0xFD, 0x4C, 0x30, 0xC7, 0x67, 0x6B, 0x5F, 0x35, 0x55, 0x5F, 0xE9, 0xA6, 0x9E, 0x39, 0xE7, 0x9F, 0xC8, 0x1B, 0x0F, 0xF5, 0x36, 0x68, 0xF9, 0xCC, 0xB7, 0x4D, 0xF8, 0xB7, 0xD9, 0x56, 0x34, 0x49, 0x36, 0xB5, 0xA8, 0x76, 0x26, 0xFF, 0x00, 0xA9, 0xE6, 0x64, 0x86, 0x3A, 0xE5, 0x86, 0x2C, 0x87, 0x60, 0x6B, 0x9A, 0xFE, 0x57, 0x01, 0x71, 0x34, 0xB4, 0x55, 0xCC, 0x71, 0x45, 0x54, 0x3D, 0x7F, 0x7B, 0x45, 0x35, 0x55, 0xC7, 0xB5, 0x55, 0x49, 0xC7, 0x1C, 0x55, 0xC7, 0x3C, 0xFB, 0x54, 0x10, 0xEA, 0x00, 0x1B, 0xDD, 0xE9, 0x87, 0xA2, 0xE7, 0xFB, 0x0F, 0xD4, 0x1B, 0xC4, 0x1C, 0x1E, 0xB9, 0x1C, 0xF2, 0x5E, 0xE2, 0xFB, 0xDB, 0x41, 0xDE, 0xAF, 0x79, 0x82, 0x89, 0xAB, 0xE6, 0x2C, 0x07, 0x5A, 0x66, 0xAD, 0xFB, 0x0B, 0x66, 0x92, 0x5E, 0x60, 0xA6, 0xAA, 0xA8, 0x83, 0xFA, 0x3F, 0xAC, 0x5C, 0xF1, 0x5D, 0x55, 0x7B, 0x51, 0xED, 0x57, 0xEA, 0xE7, 0x8E, 0x39, 0xE4, 0x1D, 0x98, 0xFA, 0x9C, 0xF6, 0x9C, 0x35, 0xDF, 0x6B, 0x78, 0xA1, 0xA4, 0xC1, 0x24, 0x1C, 0xEC, 0x1A, 0xFF, 0x00, 0x5E, 0xF6, 0x56, 0xD3, 0x93, 0x86, 0x9F, 0x87, 0xE2, 0x68, 0xC3, 0x6E, 0x3B, 0x26, 0xB5, 0x89, 0xC1, 0x49, 0x2F, 0xB7, 0x3F, 0x73, 0xEC, 0x4B, 0x7B, 0xA3, 0x64, 0x78, 0x8F, 0xDF, 0x8E, 0x29, 0xF9, 0x51, 0x5F, 0xB7, 0xBF, 0x3E, 0xFE, 0xC1, 0x17, 0x90, 0x7D, 0x36, 0x9B, 0xB9, 0xED, 0xBD, 0x77, 0xB5, 0x60, 0x37, 0x9D, 0x13, 0x64, 0xCC, 0xEA, 0x1B, 0x8E, 0xAD, 0x93, 0xB6, 0xCC, 0xEB, 0x9B, 0x36, 0xBD, 0x90, 0xB9, 0xC5, 0x66, 0xB0, 0xB9, 0x4B, 0x3A, 0xFE, 0xE5, 0xBD, 0xF6, 0x3F, 0x21, 0x67, 0x24, 0x57, 0x16, 0xD3, 0xC7, 0x57, 0xE5, 0xEF, 0x4D, 0x5F, 0xAA, 0x9E, 0x79, 0xA7, 0x9F, 0x7E, 0x39, 0xE7, 0x8E, 0x42, 0x45, 0x7E, 0x34, 0x7D, 0x47, 0xDD, 0xDF, 0xA7, 0xD9, 0xE3, 0xF5, 0x4F, 0x2A, 0x3A, 0xA7, 0x59, 0xEF, 0x0C, 0x04, 0x76, 0x3F, 0xB3, 0x6F, 0x77, 0x5D, 0x42, 0xBB, 0x7D, 0x0F, 0xB0, 0x2E, 0x69, 0xAA, 0x9A, 0x29, 0x92, 0xFF, 0x00, 0x3B, 0x87, 0xAA, 0xDA, 0xFB, 0x43, 0xD9, 0xA4, 0x96, 0x1E, 0x2B, 0x8A, 0xBB, 0x7B, 0x6B, 0x5C, 0x0D, 0x15, 0xFC, 0xF8, 0xAF, 0x99, 0x39, 0xE6, 0x9E, 0x69, 0x90, 0x3A, 0x21, 0xB8, 0xF8, 0x41, 0xE9, 0xB5, 0xEA, 0xFD, 0xD0, 0x9B, 0x17, 0x75, 0x78, 0x85, 0x8E, 0xD7, 0xBA, 0x77, 0xB8, 0x21, 0xAE, 0xFA, 0x9A, 0xB2, 0xFA, 0xCE, 0xBB, 0x69, 0xA3, 0x5D, 0xE1, 0xB7, 0xD9, 0xED, 0xE8, 0xC9, 0x73, 0xAD, 0xF7, 0x77, 0x59, 0xE1, 0xB9, 0xAF, 0x09, 0x77, 0x1E, 0x6E, 0x4E, 0x79, 0xAA, 0x4C, 0xB5, 0x8D, 0x32, 0x4F, 0x2D, 0x72, 0x57, 0x73, 0x6F, 0x7B, 0x77, 0x4D, 0x12, 0xC3, 0x28, 0x70, 0xFB, 0xD1, 0x67, 0x50, 0xD8, 0x7A, 0xF7, 0xD5, 0xBB, 0xAA, 0x74, 0x1D, 0xBB, 0x1D, 0x26, 0x23, 0x6B, 0xD1, 0xEF, 0xFC, 0x82, 0xD4, 0x36, 0x7C, 0x4C, 0xD5, 0x51, 0x5C, 0xD8, 0xBD, 0x87, 0x5A, 0xEA, 0x8E, 0xCB, 0xC2, 0xE6, 0xB1, 0xD2, 0xD7, 0x1D, 0x55, 0xC7, 0x54, 0x96, 0x59, 0x2B, 0x29, 0x62, 0xAB, 0x9A, 0x79, 0xE6, 0x9E, 0x79, 0xA7, 0xF2, 0xE7, 0x9E, 0x01, 0xED, 0xFD, 0x41, 0xBF, 0xE2, 0x35, 0xB4, 0x7F, 0x0A, 0x3A, 0xB3, 0xFE, 0x91, 0x78, 0x0E, 0xDB, 0xFA, 0xD6, 0x63, 0x2E, 0x3B, 0x67, 0xD2, 0x2B, 0xAA, 0xBB, 0x13, 0x47, 0x8F, 0x8B, 0x9D, 0x5B, 0x05, 0x9A, 0xF1, 0xDF, 0xB5, 0xAF, 0x2B, 0xB1, 0xF6, 0x9E, 0xDA, 0x8D, 0x2F, 0x62, 0xD2, 0x72, 0x5A, 0xBE, 0x2E, 0xE6, 0x3A, 0xED, 0x7D, 0xA1, 0xAA, 0xCE, 0x9C, 0x96, 0xFF, 0x00, 0x8E, 0xE7, 0x8A, 0xF8, 0xE3, 0xED, 0xFC, 0x79, 0xE3, 0x9E, 0x3D, 0xB8, 0xF6, 0xE7, 0x80, 0x84, 0x68, 0x24, 0x81, 0xA0, 0xFD, 0x47, 0xFD, 0xDF, 0xA0, 0xE8, 0x9A, 0x56, 0x89, 0x67, 0xE3, 0x7F, 0x55, 0x64, 0x2D, 0x34, 0xAD, 0x4B, 0x5C, 0xD4, 0xAD, 0x6F, 0xEE, 0x76, 0x9D, 0xBA, 0x2B, 0x9B, 0xDB, 0x6D, 0x73, 0x0F, 0x67, 0x87, 0x82, 0xEE, 0xE2, 0x38, 0xA9, 0xFB, 0x51, 0xCD, 0x73, 0x15, 0x9F, 0x15, 0xD7, 0x4D, 0x3F, 0xA7, 0x8A, 0xAA, 0xE7, 0x8E, 0x3F, 0x20, 0x77, 0xBB, 0xC6, 0x5F, 0x30, 0xF6, 0x5F, 0x39, 0xBD, 0x36, 0xBB, 0x9B, 0xBE, 0xB6, 0xCD, 0x3B, 0x07, 0xA2, 0xE6, 0x2F, 0xF4, 0xBE, 0xFF, 0x00, 0xD5, 0xEB, 0xC0, 0xEB, 0xB7, 0xD7, 0xF9, 0x1C, 0x6D, 0x10, 0x6B, 0x7A, 0x8E, 0x4E, 0x08, 0x2E, 0xA8, 0xB9, 0xC9, 0x71, 0xC5, 0xD7, 0x32, 0xDC, 0xD3, 0x3F, 0x3C, 0xD7, 0x4F, 0x3F, 0xA7, 0x8E, 0x78, 0xFC, 0x81, 0xC3, 0xEF, 0xA6, 0x4F, 0xF7, 0xEF, 0xE4, 0xDF, 0xF0, 0x93, 0x53, 0xFE, 0x71, 0xA8, 0x1C, 0xA7, 0xF2, 0xB3, 0xB8, 0xBB, 0x1B, 0xA0, 0x3D, 0x50, 0x7C, 0x9C, 0xED, 0xFE, 0xA6, 0xD9, 0xAF, 0xB5, 0x0D, 0xFF, 0x00, 0x47, 0xF2, 0xC7, 0xBA, 0x72, 0xD8, 0x1C, 0xD5, 0x85, 0x7F, 0x9D, 0x12, 0x71, 0xD8, 0x3B, 0x1C, 0x17, 0x76, 0x17, 0xD6, 0xF5, 0x7B, 0xC1, 0x91, 0xC3, 0xE5, 0xAC, 0x66, 0x96, 0xD6, 0xF6, 0xD2, 0x6A, 0x6B, 0x82, 0xEE, 0xD6, 0x69, 0x22, 0x92, 0x9A, 0xA8, 0xAF, 0x9E, 0x39, 0x09, 0x24, 0x66, 0xF1, 0xBE, 0x3F, 0xFD, 0x40, 0x3E, 0x16, 0xF1, 0x9C, 0xC1, 0x51, 0xAF, 0x75, 0xAF, 0x98, 0x7D, 0x3D, 0x67, 0xF6, 0x68, 0x86, 0x79, 0x2A, 0x92, 0xE7, 0x47, 0xDC, 0x2E, 0x20, 0xAA, 0x7A, 0xB0, 0x79, 0x39, 0x78, 0xA2, 0x5C, 0xC6, 0x5B, 0xA5, 0x3B, 0x32, 0xAB, 0x4A, 0xEA, 0xB4, 0xB9, 0xE2, 0x99, 0xA4, 0xC7, 0xDC, 0xD1, 0xCD, 0x7C, 0x71, 0x2D, 0xCD, 0x95, 0xCD, 0xBC, 0xE1, 0xFE, 0x7D, 0x3C, 0x5D, 0x61, 0xBE, 0x74, 0xBE, 0x9F, 0xE6, 0x67, 0x55, 0xF6, 0x76, 0xB5, 0x91, 0xD4, 0x37, 0xDD, 0x1B, 0xBC, 0x35, 0x7C, 0x0E, 0xCD, 0xAF, 0x65, 0x22, 0xFB, 0x77, 0x56, 0x17, 0xF6, 0xDA, 0x84, 0xB5, 0xD3, 0x55, 0x15, 0xD3, 0xCD, 0x50, 0xDD, 0xD8, 0xDE, 0xDB, 0x49, 0x1D, 0xC5, 0xAD, 0xCC, 0x35, 0x57, 0x6F, 0x77, 0x6B, 0x2C, 0x73, 0x43, 0x5D, 0x71, 0x49, 0x45, 0x75, 0x04, 0x2E, 0x73, 0xFF, 0x00, 0xDF, 0xB9, 0xAF, 0xF5, 0x6C, 0x8F, 0xFB, 0xC9, 0x81, 0x2A, 0xDF, 0x40, 0x5F, 0x1C, 0x75, 0x4E, 0x94, 0xE9, 0xEE, 0xEB, 0xF5, 0x22, 0xEE, 0xEE, 0x6D, 0xF0, 0x1A, 0xE6, 0x3F, 0x5A, 0xDA, 0xB5, 0xDE, 0xBF, 0xCC, 0x65, 0x21, 0xE7, 0xED, 0xE2, 0x3A, 0xFB, 0x4E, 0xA2, 0xBC, 0x9F, 0x6A, 0x6F, 0x36, 0x74, 0x55, 0x4D, 0x7F, 0x7E, 0xBC, 0xA6, 0x53, 0x13, 0x46, 0x1E, 0xCE, 0xA8, 0x7E, 0x37, 0x3C, 0x55, 0x8E, 0xBE, 0x83, 0x8E, 0x2A, 0xA6, 0xE7, 0x8E, 0x2A, 0x0F, 0xE9, 0xDE, 0x1E, 0x5E, 0x7D, 0x3E, 0xBE, 0x48, 0xF6, 0x66, 0xC3, 0xDC, 0x3D, 0xD9, 0xD6, 0x5D, 0xCF, 0xBD, 0xF6, 0x36, 0xD3, 0xC6, 0x3A, 0x9C, 0xDE, 0xC5, 0x77, 0x77, 0xE4, 0x16, 0x27, 0xF1, 0x11, 0x62, 0x31, 0x96, 0x98, 0x7C, 0x6D, 0xB5, 0xAE, 0x23, 0x5E, 0xED, 0xBC, 0x46, 0x0B, 0x15, 0x67, 0x67, 0x8E, 0xB1, 0x8A, 0x3A, 0x21, 0xB4, 0xB5, 0x82, 0x2E, 0x3E, 0x3C, 0xD5, 0xF1, 0xF9, 0xD5, 0x55, 0x55, 0x07, 0x4A, 0x3C, 0x1C, 0xF2, 0x83, 0xD3, 0x33, 0xC9, 0xDE, 0xB9, 0xD9, 0xBC, 0x01, 0xF1, 0xA6, 0xCF, 0x6D, 0xB4, 0xEB, 0xD8, 0xBA, 0xC7, 0x76, 0xA6, 0x4E, 0xB1, 0xEC, 0x28, 0xF7, 0x69, 0xA9, 0xBB, 0xD1, 0x76, 0x5C, 0x8C, 0x96, 0x9B, 0x7D, 0x96, 0xBB, 0xB0, 0x76, 0x06, 0xC7, 0xB5, 0x66, 0x2E, 0xE4, 0xB5, 0xBD, 0xDA, 0xF9, 0x9E, 0x8B, 0x5E, 0x2E, 0xF9, 0xAE, 0xD2, 0x89, 0x39, 0x96, 0xDE, 0x8A, 0x63, 0x86, 0xAE, 0x63, 0x08, 0x4F, 0xF9, 0x71, 0xE3, 0x7E, 0xD9, 0xE2, 0x57, 0x91, 0x3D, 0xA5, 0xD0, 0x5B, 0x85, 0x17, 0x12, 0x5E, 0x68, 0x5B, 0x2D, 0xD5, 0xA6, 0x1B, 0x2F, 0x34, 0x15, 0x41, 0x1E, 0xCF, 0xA8, 0xDE, 0xFB, 0x64, 0x35, 0x1D, 0xAA, 0xD7, 0x8E, 0x69, 0xA6, 0x3E, 0x60, 0xCF, 0xEB, 0xD7, 0x36, 0xF7, 0x15, 0x53, 0x47, 0x35, 0x71, 0x0C, 0xD5, 0x57, 0x17, 0x3C, 0xFC, 0xE3, 0xAB, 0x8E, 0x02, 0x52, 0x1F, 0x4C, 0x97, 0xEE, 0x37, 0xC9, 0xEF, 0xE2, 0xBE, 0x9D, 0xFC, 0xA1, 0x70, 0x08, 0x7B, 0x67, 0xFF, 0x00, 0xBF, 0x73, 0x5F, 0xEA, 0xD9, 0x1F, 0xF7, 0x93, 0x03, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x57, 0xE8, 0x6F, 0xDF, 0x97, 0x4C, 0xFF, 0x00, 0x15, 0xFA, 0xEF, 0xF9, 0xBF, 0x0E, 0x09, 0x43, 0xFD, 0x4F, 0xFF, 0x00, 0xDC, 0x5E, 0x17, 0x7F, 0xAB, 0x77, 0xF7, 0xFB, 0x3E, 0x9D, 0x04, 0x49, 0x23, 0x92, 0x48, 0xA4, 0xA2, 0x58, 0xAB, 0xAE, 0x29, 0x62, 0xAE, 0x99, 0x23, 0x92, 0x3A, 0xB9, 0xA2, 0x48, 0xE4, 0xA3, 0x9E, 0x2A, 0xA2, 0xBA, 0x2B, 0xA7, 0x9E, 0x2A, 0xA2, 0xBA, 0x2A, 0xE3, 0x8E, 0x78, 0xE7, 0x8E, 0x7D, 0xF8, 0xE4, 0x13, 0x4D, 0xF4, 0x6B, 0xF5, 0x44, 0xC0, 0x79, 0x59, 0xAF, 0xEA, 0xFE, 0x28, 0x79, 0x49, 0x79, 0x8D, 0xCC, 0x77, 0xE6, 0x85, 0x5D, 0x9E, 0x67, 0xAA, 0x37, 0x3D, 0xA2, 0x3B, 0x4B, 0x89, 0x7B, 0x52, 0xCB, 0x51, 0xA7, 0x9C, 0x8E, 0x36, 0x5E, 0x6E, 0x6E, 0xB8, 0xE7, 0x9E, 0x3B, 0x6B, 0x4A, 0xB3, 0xB7, 0xAA, 0x4E, 0x67, 0xA3, 0xE3, 0x73, 0x93, 0xC7, 0xC3, 0x55, 0xCF, 0x3C, 0xD7, 0x3C, 0x77, 0x92, 0x48, 0x1C, 0xDB, 0xFA, 0x96, 0xBF, 0xE7, 0x33, 0xA5, 0xFF, 0x00, 0xFA, 0xC7, 0xAF, 0xFF, 0x00, 0xDD, 0x5E, 0xD8, 0x07, 0x49, 0xFD, 0x3D, 0xBC, 0x99, 0xE8, 0xBF, 0x55, 0x8F, 0x08, 0x72, 0x5E, 0x08, 0x79, 0x29, 0x99, 0x8F, 0x8E, 0xEA, 0xD6, 0xF4, 0x5B, 0x6D, 0x52, 0xFE, 0x3B, 0xC9, 0xAC, 0x6D, 0x76, 0x4D, 0xAB, 0x09, 0xAB, 0x53, 0x4F, 0xF4, 0x1B, 0xB7, 0x7A, 0xFE, 0xE6, 0xF3, 0xEF, 0x71, 0x91, 0xDB, 0xB4, 0xF8, 0xAC, 0x2D, 0x2A, 0xCA, 0xD3, 0xF0, 0x92, 0x4E, 0x2E, 0xE0, 0xAA, 0x6B, 0x88, 0xEB, 0xB4, 0xBC, 0xAA, 0x9E, 0x42, 0x39, 0xDE, 0x64, 0xFA, 0x56, 0xF9, 0x75, 0xE1, 0xB6, 0xD1, 0x99, 0x83, 0x64, 0xEB, 0xBC, 0xF7, 0x62, 0xF5, 0x85, 0xBD, 0xC5, 0xD4, 0xB8, 0x0E, 0xE6, 0xEB, 0xAC, 0x16, 0x53, 0x3F, 0xA7, 0x64, 0xB0, 0xD1, 0xD7, 0x5D, 0x56, 0xB7, 0x5B, 0x1C, 0x58, 0xF8, 0xAF, 0xAF, 0x74, 0x1C, 0xBF, 0x36, 0xDC, 0x71, 0xF8, 0x8B, 0x2C, 0xA7, 0xDB, 0xA6, 0x99, 0xA9, 0x93, 0x8B, 0x69, 0xEE, 0xE0, 0xA6, 0x9B, 0x8A, 0xC3, 0x4D, 0xBA, 0x9B, 0xA1, 0xFB, 0xAB, 0xBE, 0x33, 0xD6, 0xFA, 0xCF, 0x4C, 0x75, 0x56, 0xFD, 0xD9, 0xD9, 0xAB, 0x8B, 0xB8, 0x2C, 0xBF, 0x0B, 0xA5, 0xEA, 0xF9, 0x7C, 0xF4, 0x76, 0x93, 0x4F, 0xF9, 0xD3, 0x5E, 0x56, 0xFA, 0xC6, 0xD6, 0x5B, 0x0C, 0x35, 0xA4, 0x71, 0xFB, 0xC9, 0x2D, 0xC5, 0xDC, 0xB0, 0x41, 0x0C, 0x54, 0xD5, 0x24, 0x95, 0xD3, 0x45, 0x3C, 0xD5, 0xC0, 0x4C, 0x2B, 0xD3, 0x9F, 0xC0, 0xDE, 0xB7, 0xF4, 0x90, 0xEA, 0x0D, 0xFF, 0x00, 0xCC, 0x8F, 0x34, 0xF7, 0x4D, 0x5B, 0x01, 0xDA, 0x97, 0x9A, 0xA4, 0xF8, 0xAB, 0xDA, 0x22, 0xC8, 0xDB, 0xE4, 0xF0, 0xFD, 0x65, 0xAB, 0x5C, 0xCB, 0x16, 0x47, 0x9D, 0x1F, 0x57, 0x9A, 0x0F, 0x7E, 0x77, 0x4E, 0xCF, 0xDC, 0xEF, 0xB1, 0xD0, 0x45, 0x37, 0xEC, 0xFA, 0x67, 0xF9, 0xD7, 0x15, 0x16, 0x56, 0x1C, 0xCB, 0x1D, 0x57, 0x13, 0xDD, 0x84, 0x55, 0xFC, 0xE9, 0xF2, 0xCB, 0x63, 0xF3, 0x5B, 0xC9, 0xBE, 0xC6, 0xEF, 0xDC, 0xED, 0xAD, 0xC6, 0x27, 0x1D, 0xB0, 0x5E, 0xC1, 0x89, 0xD1, 0xF5, 0x9B, 0x89, 0xA8, 0x9E, 0xBD, 0x4F, 0xAF, 0xF0, 0x31, 0xF3, 0x63, 0xAB, 0x60, 0xAB, 0x92, 0x2F, 0x78, 0x2B, 0xBE, 0xA6, 0xD3, 0x8A, 0xAE, 0xAF, 0xAB, 0x8F, 0xFA, 0xB9, 0x72, 0x37, 0x57, 0x12, 0x51, 0xC7, 0x14, 0xD7, 0xC7, 0x1C, 0x06, 0xA2, 0x03, 0xB0, 0x9E, 0x93, 0x1E, 0x9A, 0x3A, 0x07, 0xA8, 0x66, 0xC5, 0xDA, 0xB0, 0xF6, 0x07, 0x74, 0x73, 0xA0, 0xE3, 0x7A, 0xEB, 0x01, 0x1D, 0x76, 0xBA, 0x7E, 0xA1, 0x5E, 0x3A, 0x7E, 0xCB, 0xCC, 0x65, 0x33, 0x50, 0xCD, 0x6F, 0x88, 0xDA, 0x79, 0xB3, 0xCE, 0x63, 0xEE, 0xF1, 0x74, 0x68, 0x38, 0x2C, 0x97, 0x11, 0xD3, 0x7D, 0x54, 0x7C, 0x49, 0x73, 0x75, 0x35, 0x74, 0xDB, 0x71, 0x5D, 0x9F, 0x32, 0xD1, 0x72, 0x0D, 0x27, 0xF2, 0x9F, 0xC2, 0xDF, 0x21, 0xBC, 0x3E, 0xEC, 0x9C, 0x97, 0x5C, 0xF7, 0x1F, 0x5F, 0x67, 0x71, 0x9C, 0xC7, 0x97, 0xAB, 0x19, 0xAB, 0xEE, 0x36, 0x38, 0xCB, 0xEB, 0xDD, 0x23, 0x7F, 0xB7, 0x9E, 0xB9, 0xF9, 0xC5, 0x64, 0x74, 0xDD, 0x8E, 0x2B, 0x7E, 0x6C, 0x32, 0xBC, 0x65, 0x2D, 0xA0, 0xE6, 0x4E, 0x2D, 0x3E, 0x54, 0xDF, 0xDB, 0x55, 0xC5, 0x51, 0x5C, 0x43, 0x14, 0xD1, 0xD7, 0x1D, 0x21, 0x28, 0x7F, 0xA7, 0x97, 0xC4, 0x9E, 0xE8, 0xE8, 0x1D, 0x1F, 0xBD, 0x3B, 0xF7, 0xBA, 0x30, 0x39, 0x8E, 0xB1, 0xD6, 0x3B, 0x6B, 0x1B, 0xA5, 0x63, 0xB4, 0xAD, 0x67, 0x6F, 0x82, 0xE7, 0x5F, 0xCB, 0xE4, 0xF0, 0x1A, 0x7F, 0x3B, 0x1E, 0x63, 0x21, 0xD8, 0x39, 0xAC, 0x3E, 0x4E, 0x9B, 0x59, 0x70, 0xF8, 0x19, 0x28, 0xCE, 0xD1, 0x16, 0x2A, 0x6B, 0xAA, 0x68, 0x92, 0xE6, 0x0F, 0xC4, 0xDC, 0x53, 0x4D, 0x36, 0xB5, 0xDB, 0xCD, 0x70, 0x1C, 0xDF, 0xF4, 0xF6, 0xEC, 0x8D, 0x67, 0xB8, 0x3D, 0x7D, 0xB2, 0xBD, 0xA3, 0xA5, 0xD7, 0x44, 0xDA, 0x86, 0xF9, 0xDB, 0x5E, 0x58, 0xEC, 0xDA, 0xC5, 0xDD, 0x11, 0xD3, 0x17, 0x19, 0x1C, 0x0E, 0x53, 0x40, 0xED, 0x5B, 0x8C, 0x4E, 0x5A, 0xA8, 0xE9, 0xFC, 0xA8, 0x97, 0x2F, 0x63, 0x55, 0x17, 0x55, 0xF1, 0xEF, 0xCF, 0x3C, 0x57, 0x2F, 0x3E, 0xFC, 0xF3, 0xCF, 0xE7, 0xC8, 0x62, 0x7F, 0xA8, 0x37, 0xFC, 0x46, 0xB6, 0x8F, 0xE1, 0x47, 0x56, 0x7F, 0xD2, 0x2F, 0x01, 0xD5, 0x6F, 0x45, 0xFF, 0x00, 0x33, 0xFA, 0x77, 0xCA, 0x9F, 0x18, 0x72, 0x3E, 0x9B, 0x5E, 0x4B, 0xCB, 0x8A, 0xBD, 0xD8, 0xF1, 0xBA, 0x96, 0x7F, 0x47, 0xD3, 0x31, 0x5B, 0x15, 0xCC, 0x36, 0x90, 0x76, 0xB7, 0x51, 0x64, 0x6D, 0xEE, 0xE6, 0xA3, 0x5D, 0xC2, 0x5D, 0xFD, 0xC8, 0x25, 0xA3, 0x77, 0xEB, 0xAB, 0x69, 0x64, 0xA2, 0xDE, 0x3B, 0x7E, 0x63, 0xBC, 0x8F, 0x1B, 0x6D, 0x6D, 0x77, 0x6D, 0x55, 0x52, 0xDA, 0x5C, 0xCB, 0x10, 0x71, 0x87, 0xCE, 0xFF, 0x00, 0x47, 0x6F, 0x28, 0xFC, 0x41, 0xDC, 0x73, 0x39, 0x0D, 0x3F, 0x4A, 0xDA, 0xFB, 0xBB, 0xA2, 0x2E, 0xB2, 0x37, 0x32, 0x6A, 0x7D, 0x91, 0xA2, 0xE0, 0xEF, 0x36, 0x5C, 0xA6, 0x2B, 0x15, 0x24, 0xDE, 0xF6, 0x78, 0xFE, 0xCB, 0xD7, 0xB0, 0x36, 0x93, 0xE4, 0x35, 0x5C, 0xC5, 0xA5, 0x12, 0x47, 0x0C, 0x97, 0xB5, 0x41, 0x4E, 0x22, 0xF2, 0x5A, 0xA9, 0xFC, 0x3C, 0xFC, 0x49, 0x5D, 0x56, 0xD1, 0x07, 0x34, 0x3A, 0xEF, 0xA8, 0xBB, 0x53, 0xB7, 0x36, 0x28, 0xF5, 0x1E, 0xAD, 0xEB, 0x8D, 0xE3, 0xB0, 0xF6, 0x79, 0x2E, 0xA0, 0xB2, 0xFD, 0x85, 0xA6, 0x6A, 0xF9, 0x9D, 0x8F, 0x25, 0x0D, 0xC5, 0xCD, 0x72, 0xC7, 0x0D, 0x17, 0x76, 0xB8, 0xAB, 0x3B, 0x99, 0x2C, 0xA9, 0xE6, 0xAB, 0x79, 0x39, 0xE6, 0xB9, 0xB8, 0xA2, 0x8A, 0x69, 0x8E, 0xBA, 0xAA, 0xE7, 0x8E, 0x28, 0xAB, 0x9E, 0x02, 0x72, 0x5E, 0x13, 0xF4, 0x07, 0x60, 0x78, 0x5B, 0xE9, 0x1B, 0xD9, 0xFA, 0x3F, 0x91, 0x7C, 0x6B, 0xBA, 0x06, 0xCD, 0xFD, 0x02, 0xEF, 0xAD, 0xCF, 0x33, 0x63, 0x79, 0xB1, 0xE3, 0x24, 0xB6, 0xD5, 0xAC, 0xF7, 0x0D, 0x72, 0xFE, 0x3C, 0x3E, 0x2B, 0x61, 0xCC, 0xF1, 0x2D, 0x18, 0x3B, 0x7C, 0xDD, 0x55, 0xF1, 0x45, 0x32, 0x45, 0x05, 0xCD, 0xC4, 0x34, 0xC9, 0x35, 0x11, 0xF1, 0x2D, 0x52, 0x73, 0x55, 0x14, 0x87, 0x25, 0xBE, 0x99, 0x3F, 0xDF, 0xBF, 0x93, 0x7F, 0xC2, 0x4D, 0x4F, 0xF9, 0xC6, 0xA0, 0x71, 0x6B, 0xD4, 0x47, 0xFE, 0x7D, 0x7C, 0xCB, 0xFF, 0x00, 0xEC, 0xE7, 0x76, 0xFF, 0x00, 0xDC, 0x4D, 0x80, 0x1F, 0x19, 0xE2, 0x67, 0x95, 0x9D, 0xB1, 0xE1, 0xAF, 0x75, 0xEB, 0x1D, 0xDD, 0xD4, 0x39, 0x6F, 0xC2, 0x66, 0x70, 0xB3, 0x53, 0x69, 0x9E, 0xC0, 0x5D, 0xD7, 0x2F, 0x3A, 0xFE, 0xF3, 0xAA, 0x5C, 0x5C, 0x5B, 0xCB, 0x9A, 0xD3, 0x76, 0x7B, 0x48, 0xF9, 0xE3, 0x9B, 0x9C, 0x3E, 0x5E, 0x2B, 0x7A, 0x78, 0xF9, 0xD3, 0xED, 0x3D, 0xAC, 0xF4, 0x47, 0x71, 0x05, 0x71, 0xCF, 0x14, 0x72, 0x52, 0x16, 0x1F, 0xF8, 0x61, 0xE4, 0xE7, 0x45, 0xF9, 0x95, 0xD5, 0x16, 0x9E, 0x45, 0xF4, 0xCC, 0x18, 0xFB, 0x4B, 0xED, 0xBA, 0x2C, 0x6E, 0x0F, 0xB1, 0xF1, 0x72, 0x45, 0x63, 0x1E, 0xE3, 0xAC, 0x6D, 0x9A, 0xD5, 0xA5, 0x5E, 0xFA, 0x7E, 0xEB, 0x25, 0xAD, 0x34, 0xCB, 0x75, 0x79, 0x81, 0x87, 0x2B, 0xCD, 0x56, 0x53, 0xD7, 0xFD, 0x5D, 0xCD, 0x85, 0xCC, 0x53, 0xC3, 0xFD, 0x54, 0xB4, 0x71, 0xC0, 0x57, 0x61, 0xD1, 0x7D, 0x0F, 0xB8, 0x79, 0x3B, 0xE4, 0x9E, 0x91, 0xD0, 0xBA, 0x24, 0x5C, 0xD5, 0xB1, 0xF6, 0x67, 0x60, 0xD5, 0xAF, 0xC3, 0x79, 0xCC, 0x35, 0xDC, 0x41, 0x84, 0xC5, 0xF3, 0x79, 0x73, 0x79, 0xB1, 0x6C, 0xD7, 0xD1, 0x47, 0xCD, 0x32, 0x57, 0x8C, 0xD5, 0xF5, 0xEB, 0x4B, 0xAC, 0x8D, 0xCF, 0x14, 0xF3, 0xF2, 0xE6, 0xDE, 0xDA, 0xBF, 0x8F, 0xBD, 0x5E, 0xDC, 0x72, 0x12, 0x62, 0xF5, 0xD8, 0xEE, 0xFD, 0x4F, 0xC5, 0x6F, 0x15, 0xFA, 0x1B, 0xD3, 0x87, 0xA3, 0xE7, 0x8F, 0x0B, 0x8E, 0xCC, 0xEA, 0xF8, 0x1B, 0xAD, 0xC3, 0x1F, 0x6B, 0x73, 0x4F, 0x19, 0x3B, 0x0E, 0xA3, 0xEB, 0xF9, 0xAD, 0xEC, 0x75, 0x4B, 0x1C, 0xBD, 0x50, 0xF1, 0x0D, 0x53, 0xDD, 0x76, 0x1E, 0xE9, 0x8D, 0x9A, 0xFA, 0xEA, 0xE3, 0x9E, 0x3E, 0x77, 0x32, 0x61, 0xAE, 0x3E, 0xEF, 0x1C, 0xF1, 0x71, 0x57, 0x35, 0x04, 0x46, 0x01, 0xB0, 0xBE, 0x28, 0xF9, 0x09, 0xB2, 0xF8, 0xAB, 0xE4, 0x57, 0x52, 0xF7, 0xFE, 0xAB, 0xF7, 0xE5, 0xC8, 0x75, 0xBE, 0xDD, 0x61, 0x97, 0xBF, 0xC6, 0xDB, 0xCF, 0xCD, 0xBF, 0x39, 0xFD, 0x62, 0xE3, 0x89, 0x31, 0x9B, 0x7E, 0xB1, 0x24, 0xBE, 0xFC, 0x71, 0x44, 0x3B, 0x2E, 0xAF, 0x7D, 0x77, 0x63, 0x55, 0x5C, 0xFB, 0xF1, 0x47, 0x13, 0xFC, 0xBF, 0xB6, 0x9E, 0x01, 0x28, 0x5F, 0x5E, 0x8F, 0x1C, 0x75, 0x5F, 0x26, 0x3C, 0x62, 0xEA, 0x0F, 0x50, 0xBE, 0x93, 0xE2, 0x1D, 0x86, 0x8D, 0x4F, 0x59, 0xD7, 0x78, 0xD9, 0xB2, 0xD8, 0xC8, 0x38, 0xAE, 0x6D, 0x8F, 0xA3, 0x7B, 0x02, 0xB8, 0x32, 0x7A, 0xB6, 0x7A, 0xE2, 0x98, 0x63, 0x9A, 0xE2, 0xA9, 0x74, 0x7D, 0x9B, 0x35, 0x4F, 0xCE, 0x2F, 0xD3, 0xF8, 0x7B, 0x6C, 0xC5, 0xDC, 0x93, 0x73, 0xC7, 0x16, 0xDE, 0xD4, 0x87, 0xAB, 0xF4, 0xC9, 0x7E, 0xE3, 0x7C, 0x9E, 0xFE, 0x2B, 0xE9, 0xDF, 0xCA, 0x17, 0x00, 0x8D, 0x2E, 0x6F, 0xC0, 0xCF, 0x39, 0x65, 0xCC, 0xE5, 0xE5, 0x8B, 0xC3, 0x1F, 0x2B, 0xE4, 0x8A, 0x4C, 0x9D, 0xFC, 0x91, 0xC9, 0x1F, 0x8E, 0xBD, 0xBF, 0x5C, 0x72, 0x47, 0x5D, 0xD4, 0xB5, 0x51, 0x5D, 0x15, 0xD3, 0xA7, 0xF3, 0x4D, 0x74, 0x57, 0x4F, 0x3C, 0x73, 0xC7, 0x3C, 0x73, 0xED, 0xCF, 0x00, 0xC0, 0x7D, 0x99, 0xD3, 0x5D, 0xBF, 0xD2, 0xB9, 0x5C, 0x7E, 0x0B, 0xB9, 0x3A, 0xA7, 0xB2, 0x7A, 0x97, 0x39, 0x96, 0xC7, 0xFE, 0xD6, 0xC5, 0xE1, 0xBB, 0x33, 0x46, 0xD9, 0xF4, 0x3C, 0xAE, 0x4B, 0x15, 0xF8, 0x99, 0xAC, 0xFF, 0x00, 0x69, 0xE3, 0xF1, 0xDB, 0x4E, 0x2F, 0x15, 0x77, 0x79, 0x8F, 0xFC, 0x5D, 0xB4, 0x91, 0x7D, 0xE8, 0xE8, 0xAA, 0x3F, 0xB9, 0x1D, 0x54, 0xFC, 0xBE, 0x54, 0xF3, 0xC7, 0x01, 0x8D, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xB9, 0xAC, 0x6C, 0x39, 0x1D, 0x4B, 0x64, 0xD7, 0xB6, 0xBC, 0x47, 0x30, 0xD3, 0x96, 0xD6, 0x73, 0x98, 0x9D, 0x87, 0x17, 0x55, 0xC4, 0x5F, 0x7E, 0xDE, 0x9C, 0x8E, 0x16, 0xFE, 0xDF, 0x25, 0x65, 0xCC, 0xF0, 0xFC, 0xA9, 0xFB, 0xD0, 0xF1, 0x73, 0x6D, 0x4F, 0xCA, 0x9F, 0x7E, 0x3E, 0x54, 0xFB, 0xF1, 0xEE, 0x0D, 0xC8, 0xF3, 0x1F, 0xD4, 0x3B, 0xC9, 0x1F, 0x3B, 0x2D, 0xBA, 0xFA, 0xD7, 0xBF, 0xB2, 0xBA, 0x96, 0x4A, 0x1E, 0xB2, 0x9F, 0x67, 0xB8, 0xD5, 0xB8, 0xD6, 0x35, 0x4B, 0x3D, 0x6B, 0x98, 0x64, 0xDB, 0xA3, 0xC0, 0x47, 0x99, 0xE6, 0xF6, 0xAB, 0x59, 0xA5, 0xE6, 0xF3, 0x8A, 0xE9, 0xD6, 0xED, 0x7E, 0xDF, 0x15, 0x7B, 0x7D, 0xBF, 0x6A, 0xBD, 0xBF, 0xF7, 0x7E, 0x41, 0xA3, 0x60, 0xF5, 0xF5, 0xFD, 0x83, 0x39, 0xA9, 0xE7, 0xB0, 0xBB, 0x46, 0xB1, 0x97, 0xC9, 0x6B, 0xFB, 0x26, 0xB9, 0x95, 0xC7, 0xE7, 0x70, 0x19, 0xEC, 0x35, 0xE5, 0xC6, 0x3B, 0x2D, 0x85, 0xCC, 0xE2, 0x6E, 0xE2, 0xBE, 0xC6, 0x65, 0x71, 0x97, 0xF6, 0xB2, 0x45, 0x73, 0x65, 0x90, 0xC7, 0xDE, 0xC1, 0x44, 0xB0, 0xCB, 0x1D, 0x54, 0xD7, 0x1C, 0x94, 0x71, 0x55, 0x3C, 0xF1, 0xCF, 0x1C, 0x72, 0x0D, 0xB6, 0xF3, 0x53, 0xCD, 0xCE, 0xCC, 0xF3, 0x9F, 0x6D, 0xEA, 0xFD, 0xF7, 0xB6, 0xB1, 0x58, 0x1B, 0x3D, 0xCB, 0xAE, 0xFA, 0x87, 0x03, 0xD5, 0x37, 0xF9, 0x9C, 0x15, 0x13, 0x5B, 0xD3, 0xB9, 0x57, 0x84, 0xCE, 0x6C, 0x79, 0xD9, 0x76, 0xDC, 0x9D, 0x85, 0x7C, 0xFE, 0x13, 0x1B, 0x97, 0xCB, 0x5D, 0x6C, 0x72, 0x73, 0x3C, 0x16, 0xBC, 0x51, 0x6B, 0x4D, 0x54, 0x7B, 0xC7, 0x45, 0x14, 0xF3, 0xC5, 0x14, 0x86, 0xA6, 0x6B, 0xBB, 0x26, 0xC3, 0xA8, 0x67, 0x71, 0x5B, 0x46, 0xA5, 0x9E, 0xCD, 0x6A, 0xFB, 0x36, 0x0A, 0xF6, 0x0C, 0x9E, 0x0F, 0x62, 0xD7, 0x72, 0x97, 0xD8, 0x4C, 0xEE, 0x1B, 0x25, 0x6B, 0x5F, 0xCE, 0xDB, 0x21, 0x8A, 0xCB, 0xE3, 0x67, 0xB6, 0xC8, 0x63, 0xAF, 0x6D, 0xEB, 0xE3, 0xDE, 0x89, 0x61, 0x92, 0x89, 0x28, 0xE7, 0xF3, 0xE3, 0x9E, 0x01, 0xDC, 0xCE, 0x8E, 0xFA, 0x87, 0x7C, 0xE4, 0xEA, 0xFC, 0x3E, 0x33, 0x5D, 0xEC, 0x3C, 0x6F, 0x57, 0x77, 0xD6, 0x3B, 0x1D, 0x6F, 0xCD, 0xAF, 0xED, 0xCD, 0xD7, 0x03, 0x92, 0xD7, 0xF7, 0xDB, 0x88, 0x61, 0x86, 0x58, 0xEC, 0x68, 0x9F, 0x63, 0xD3, 0x32, 0xB8, 0x6C, 0x25, 0xE5, 0x70, 0x7B, 0xC7, 0xC4, 0xB3, 0xDD, 0x62, 0x2E, 0x6F, 0x2E, 0xA9, 0x8F, 0xDE, 0x59, 0xB9, 0x9A, 0xBA, 0xE6, 0xA8, 0x33, 0x2E, 0xD3, 0xF5, 0x30, 0x79, 0x3F, 0x7F, 0x87, 0x9E, 0xD7, 0x50, 0xE8, 0x2E, 0x8D, 0xD6, 0xF3, 0x52, 0xF3, 0xF0, 0x8B, 0x2F, 0x99, 0xBA, 0xDE, 0x76, 0x9B, 0x6B, 0x58, 0xAA, 0xA2, 0x4A, 0x64, 0xAE, 0x1C, 0x4D, 0xBE, 0x7B, 0x5A, 0xF9, 0xDE, 0x53, 0x55, 0x54, 0xD5, 0x15, 0x72, 0x4F, 0x5C, 0x54, 0xD5, 0x4F, 0xEB, 0x8A, 0x4E, 0x39, 0xF6, 0xE0, 0x38, 0xAD, 0xE4, 0xFF, 0x00, 0x99, 0xDE, 0x4A, 0xF9, 0x8B, 0xB4, 0xD1, 0xB4, 0xF9, 0x03, 0xDA, 0x59, 0xDD, 0xD7, 0xF0, 0x53, 0x4D, 0x2E, 0xBF, 0xAC, 0x51, 0xCC, 0x38, 0x6D, 0x17, 0x53, 0xA2, 0x6F, 0x78, 0xFE, 0xDE, 0xB1, 0xA5, 0xE1, 0xE2, 0xB3, 0xD7, 0xF1, 0x73, 0x7E, 0x1B, 0xE3, 0x0C, 0xB7, 0x9C, 0x41, 0x5E, 0x42, 0xF2, 0x88, 0xE8, 0xE6, 0xEA, 0xE2, 0x7A, 0xF8, 0xF9, 0xF2, 0x1A, 0xBA, 0x00, 0x32, 0x7F, 0x4E, 0x77, 0x4F, 0x6A, 0x78, 0xFB, 0xD8, 0x38, 0x3E, 0xD4, 0xE9, 0x8D, 0xE3, 0x39, 0xD7, 0xBB, 0xFE, 0xB9, 0x24, 0x95, 0xE2, 0xB6, 0x2C, 0x0C, 0xF1, 0xD1, 0x3D, 0x31, 0x4F, 0x4F, 0xDB, 0xBA, 0xB0, 0xBE, 0xB3, 0xBA, 0x8A, 0xE7, 0x1B, 0x98, 0xC4, 0x5F, 0xC5, 0xFA, 0x2E, 0x6C, 0xAF, 0x21, 0x9E, 0xD2, 0xE6, 0x3F, 0xD3, 0x2C, 0x75, 0xD3, 0xF9, 0x03, 0xBE, 0x5A, 0x57, 0xD4, 0xB5, 0xE5, 0x3E, 0x23, 0x03, 0x6D, 0x8F, 0xDE, 0x3A, 0x3B, 0xA4, 0x77, 0x4C, 0xE5, 0xB7, 0xC2, 0x2A, 0xB6, 0x1C, 0x6C, 0x9B, 0x96, 0xA1, 0xCD, 0xFC, 0x11, 0xDB, 0x5B, 0xC5, 0xC4, 0xD9, 0x1C, 0x4D, 0x19, 0xBC, 0xED, 0x9F, 0x39, 0x49, 0xEE, 0x28, 0x96, 0x59, 0x64, 0xB5, 0xE6, 0xD6, 0xDB, 0x9F, 0xB9, 0xC5, 0x31, 0xDB, 0xC5, 0xC5, 0x3F, 0xA8, 0x34, 0xF3, 0xCC, 0x7F, 0x5A, 0xFF, 0x00, 0x31, 0x7C, 0xBE, 0xD5, 0x33, 0x3D, 0x69, 0x35, 0xE6, 0xB1, 0xD3, 0x1D, 0x53, 0x9F, 0xB5, 0xE7, 0x1F, 0x9F, 0xD4, 0x3A, 0xB6, 0xDF, 0x29, 0x67, 0x94, 0xDA, 0xB1, 0x72, 0x53, 0xED, 0x73, 0x8C, 0xDB, 0x37, 0x2C, 0xBE, 0x4B, 0x21, 0x9C, 0xBE, 0xC6, 0x5E, 0x7C, 0xAB, 0x8E, 0xE2, 0xD2, 0xC7, 0xF6, 0x6D, 0x95, 0xDD, 0xB5, 0x5F, 0x66, 0xE6, 0x09, 0xE9, 0xF9, 0xF3, 0x58, 0x73, 0xDB, 0xC6, 0xCF, 0x22, 0xFB, 0x2F, 0xC5, 0x1E, 0xE3, 0xD5, 0xBB, 0xD7, 0xA8, 0x6E, 0xF1, 0x16, 0x5B, 0xFE, 0x9F, 0x06, 0x7E, 0xDF, 0x0B, 0x73, 0x9D, 0xC4, 0xC3, 0x9C, 0xC5, 0xD1, 0x1E, 0xCB, 0xAE, 0xE5, 0x35, 0x7C, 0xA7, 0x17, 0x18, 0xCB, 0x8A, 0xE3, 0x8A, 0x7E, 0x6B, 0xC5, 0x66, 0x26, 0xE2, 0x8E, 0x79, 0xE7, 0x8F, 0x85, 0x7C, 0xF1, 0x57, 0xF9, 0x03, 0xD6, 0xF2, 0x93, 0xCA, 0x4E, 0xD7, 0xF3, 0x0B, 0xB5, 0xEE, 0xFB, 0xA3, 0xBA, 0x2E, 0xF0, 0x77, 0xDB, 0xC5, 0xF6, 0x0F, 0x0B, 0xAF, 0x5C, 0x5C, 0x6B, 0xD8, 0x58, 0x70, 0x38, 0xEA, 0xF1, 0xD8, 0x18, 0x65, 0x83, 0x1D, 0xC7, 0x18, 0xE8, 0x25, 0x96, 0x2A, 0x26, 0xA2, 0x29, 0x79, 0xE2, 0xAA, 0xB8, 0xE7, 0x8F, 0x97, 0xB7, 0x1F, 0x97, 0xBF, 0xF6, 0x86, 0x00, 0xC7, 0x64, 0x72, 0x18, 0x7C, 0x85, 0x86, 0x5F, 0x11, 0x7F, 0x79, 0x8B, 0xCA, 0xE2, 0xEF, 0x2D, 0x72, 0x38, 0xCC, 0x9E, 0x3A, 0xEA, 0x7B, 0x1C, 0x86, 0x3B, 0x21, 0x63, 0x3C, 0x77, 0x36, 0x57, 0xF6, 0x17, 0xB6, 0xD2, 0x45, 0x73, 0x67, 0x79, 0x67, 0x73, 0x15, 0x32, 0x45, 0x2C, 0x75, 0x53, 0x5C, 0x75, 0xD3, 0xC5, 0x54, 0xF3, 0xC7, 0x3C, 0x71, 0xCF, 0x01, 0xDB, 0xBF, 0x1E, 0xBE, 0xA0, 0x2F, 0x3A, 0x7A, 0x5F, 0x13, 0x8E, 0xD6, 0x77, 0x8B, 0x8D, 0x17, 0xC8, 0x4D, 0x7E, 0xC2, 0x3A, 0x2D, 0xE3, 0xBD, 0xED, 0x0C, 0x56, 0x52, 0x1D, 0xF6, 0x3B, 0x48, 0x21, 0xB9, 0xA6, 0x18, 0xA3, 0xDE, 0x35, 0x8C, 0xB6, 0x1A, 0x5C, 0x94, 0xF5, 0x5C, 0x4B, 0x1D, 0x53, 0x5C, 0xE5, 0xEC, 0xF2, 0xD7, 0x52, 0xD1, 0x17, 0xC7, 0xEE, 0x53, 0x55, 0x5C, 0xD7, 0xC0, 0x6C, 0x9E, 0x67, 0xEA, 0x67, 0xF2, 0x42, 0x7C, 0x65, 0xDC, 0x5A, 0xFF, 0x00, 0x8E, 0x7D, 0x23, 0x8C, 0xCC, 0x57, 0x45, 0x1C, 0x58, 0xDF, 0xE6, 0x72, 0xDB, 0xE6, 0x77, 0x19, 0x6F, 0x27, 0x12, 0xD1, 0xCC, 0x95, 0x5D, 0xE2, 0x6C, 0xB3, 0x1A, 0xED, 0xD5, 0xE5, 0x15, 0x43, 0xC5, 0x54, 0xF1, 0x4D, 0x17, 0xB0, 0x73, 0x4D, 0x5C, 0xF1, 0x57, 0xBF, 0x3C, 0x71, 0xCD, 0x35, 0x07, 0x1F, 0x7C, 0xB3, 0xF5, 0x0C, 0xF2, 0xCB, 0xCD, 0x5B, 0xC8, 0x69, 0xEF, 0x4E, 0xCF, 0xBE, 0xC8, 0xEA, 0x96, 0x17, 0x3C, 0x5E, 0x61, 0xFA, 0xDB, 0x59, 0xB6, 0x8B, 0x55, 0xEB, 0x9C, 0x4D, 0xCD, 0x14, 0xF1, 0xC4, 0x77, 0x54, 0x6B, 0x58, 0xCE, 0x68, 0xA3, 0x33, 0x91, 0x83, 0x9E, 0x6B, 0xE6, 0x2B, 0xDC, 0xA4, 0xB7, 0xF7, 0xD0, 0xD3, 0x2D, 0x74, 0x47, 0x35, 0x31, 0xD5, 0xF0, 0xE0, 0x3C, 0x9F, 0x0F, 0xBC, 0xE5, 0xEF, 0xBF, 0x06, 0xB6, 0x4D, 0xC7, 0x6B, 0xE8, 0x4C, 0x86, 0xB1, 0x8E, 0xCC, 0x6F, 0x58, 0x3B, 0x0D, 0x7B, 0x3D, 0x2E, 0xCD, 0xAD, 0xDB, 0x6C, 0x90, 0xD7, 0x8D, 0xC7, 0x5F, 0xD5, 0x92, 0xB7, 0x8E, 0xD6, 0x0B, 0x99, 0xA2, 0xA2, 0xDA, 0x5F, 0xC5, 0x55, 0xEF, 0x55, 0x7C, 0x7B, 0xF3, 0xCF, 0x1C, 0x7B, 0x7E, 0x5F, 0x9F, 0xB8, 0x6B, 0xDF, 0x6A, 0xF6, 0x56, 0xD1, 0xDC, 0x9D, 0x99, 0xBF, 0xF6, 0xD6, 0xED, 0x35, 0x9D, 0xC6, 0xE1, 0xD9, 0x7B, 0x8E, 0xC5, 0xBD, 0x6D, 0x13, 0xE3, 0xEC, 0xE8, 0xC7, 0xD8, 0x4D, 0x9F, 0xDA, 0x32, 0xB7, 0x59, 0x9C, 0xAC, 0x96, 0x76, 0x31, 0x73, 0x54, 0x76, 0x76, 0xB5, 0xDE, 0xDE, 0x57, 0xCD, 0x11, 0x53, 0xCF, 0x3C, 0x51, 0x4F, 0xB7, 0x1F, 0xE4, 0x0F, 0x81, 0x06, 0xED, 0xF8, 0x2B, 0xE7, 0x97, 0x73, 0x78, 0x0D, 0xDA, 0xB7, 0x3D, 0x8B, 0xD5, 0x92, 0xDA, 0xE6, 0xB0, 0xBB, 0x06, 0x3B, 0x9C, 0x46, 0xF9, 0xD7, 0x19, 0xEB, 0x9B, 0xD8, 0xF5, 0x2D, 0xE3, 0x1F, 0x0C, 0x77, 0x5C, 0xE2, 0x6B, 0xC9, 0xC5, 0x67, 0x25, 0x13, 0x5A, 0xE5, 0xF5, 0xFB, 0xDB, 0xAA, 0xA7, 0xB0, 0xBD, 0x8B, 0xDA, 0x7B, 0x7E, 0x6B, 0x96, 0x2F, 0x7A, 0xA0, 0x9E, 0x78, 0xE4, 0x0F, 0x94, 0xF1, 0x5F, 0xCC, 0xBE, 0xE1, 0xF0, 0xDF, 0xB3, 0x76, 0x1E, 0xDD, 0xE9, 0x58, 0xB4, 0xBB, 0x5D, 0xDF, 0x62, 0xC1, 0x64, 0x75, 0xB9, 0x32, 0x7B, 0x5E, 0xAB, 0x67, 0xB5, 0x7E, 0xCB, 0xC4, 0xE5, 0xB2, 0x76, 0x79, 0x5C, 0x84, 0x58, 0x5A, 0x2F, 0xA4, 0xA3, 0xF6, 0x6D, 0xCD, 0xDC, 0xD6, 0x11, 0x47, 0x24, 0xD4, 0x73, 0xF7, 0x2A, 0x87, 0x8E, 0x63, 0xF7, 0xF8, 0xD7, 0x5F, 0x15, 0x07, 0xC0, 0x79, 0x19, 0xE4, 0x4F, 0x6A, 0x79, 0x55, 0xDB, 0xBB, 0x3F, 0x77, 0x77, 0x2E, 0x72, 0x2C, 0xF6, 0xF7, 0xB5, 0xD3, 0x8B, 0x86, 0xFA, 0x7B, 0x3B, 0x4A, 0x31, 0xB8, 0xAB, 0x2B, 0x2C, 0x2E, 0x2E, 0xD3, 0x0F, 0x8C, 0xC6, 0xE1, 0xB1, 0x30, 0xD5, 0x55, 0xB6, 0x2F, 0x1D, 0x6B, 0x67, 0x65, 0x4F, 0xB4, 0x51, 0xFB, 0x71, 0x54, 0xB5, 0x57, 0x2D, 0x5F, 0x29, 0x24, 0xAE, 0xAA, 0x83, 0x07, 0x00, 0x0E, 0x8F, 0xF5, 0x2F, 0xAA, 0xA7, 0x96, 0xFD, 0x3B, 0xE3, 0x8C, 0xFE, 0x29, 0x60, 0x73, 0x3A, 0x2E, 0xC5, 0xD2, 0x97, 0x18, 0x3D, 0xBB, 0x57, 0x93, 0x5A, 0xDE, 0xF4, 0x7C, 0x76, 0xD7, 0x2F, 0xF4, 0x67, 0x78, 0xAF, 0x23, 0x26, 0xC1, 0xAF, 0x7E, 0x3A, 0xF6, 0x6A, 0x65, 0xAB, 0x11, 0x34, 0x99, 0x7B, 0x9E, 0x62, 0x8A, 0xAE, 0x2A, 0xE2, 0x1E, 0x26, 0xE6, 0x9A, 0x3D, 0xA9, 0xE2, 0x9A, 0x69, 0x0F, 0x99, 0xF0, 0xFB, 0xD4, 0x9F, 0xC9, 0xEF, 0x06, 0xB5, 0xBD, 0xC7, 0x54, 0xE8, 0x4C, 0xAE, 0x9B, 0x8D, 0xC4, 0xEF, 0x59, 0xCB, 0x1D, 0x87, 0x60, 0xA7, 0x66, 0xD4, 0x6C, 0xF6, 0x4B, 0x89, 0x72, 0x38, 0xEB, 0x0E, 0x71, 0xB6, 0xBC, 0xDB, 0x4D, 0x75, 0x3C, 0x5F, 0x86, 0x86, 0x8B, 0x6A, 0xB9, 0xF7, 0xA6, 0x9E, 0x3F, 0x55, 0x5C, 0xFB, 0xF3, 0xCF, 0xF6, 0x7B, 0x06, 0xE1, 0x7F, 0xE6, 0x0D, 0xF5, 0x1A, 0xFF, 0x00, 0xF2, 0x8E, 0xA8, 0xFF, 0x00, 0xF5, 0x66, 0x23, 0xFF, 0x00, 0xEB, 0x07, 0x3E, 0x7C, 0xBE, 0xF3, 0x5F, 0xBD, 0x3C, 0xE1, 0xDD, 0x75, 0x8D, 0xFB, 0xBE, 0x72, 0x3A, 0xE6, 0x47, 0x61, 0xD4, 0x75, 0x7F, 0xE8, 0x7E, 0x1A, 0x4D, 0x6B, 0x5D, 0xB6, 0xD6, 0xED, 0x23, 0xC3, 0x7E, 0xD6, 0xC8, 0x66, 0xBE, 0x13, 0xDA, 0xDA, 0xCB, 0x2D, 0x33, 0xDC, 0x7E, 0x3B, 0x27, 0x2F, 0x3F, 0x73, 0x9E, 0x7D, 0xFE, 0x3E, 0xDC, 0x7F, 0x90, 0x35, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD3, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD4, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD5, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD6, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD7, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD1, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD2, 0xAF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD9, }; ustreamer-4.9/src/ustreamer/data/blank_jpeg.h000066400000000000000000000034161414164060100213520ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include extern const unsigned BLANK_JPEG_WIDTH; extern const unsigned BLANK_JPEG_HEIGHT; extern const size_t BLANK_JPEG_DATA_SIZE; extern const uint8_t BLANK_JPEG_DATA[]; ustreamer-4.9/src/ustreamer/data/index.html000066400000000000000000000037651414164060100211110ustar00rootroot00000000000000 uStreamer

µStreamer v%VERSION%


  • /state
    Get JSON structure with the state of the server.

  • /snapshot
    Get a current actual image from the server.

  • /stream
    Get a live stream. Query params:

    • key=abc123
      The user-defined key, which is part of cookie stream_client, which allows
      the stream client to determine its identifier and view statistics using /state.

    • extra_headers=1
      Add X-UStreamer-* headers to the /stream handle (like with the /snapshot).

    • advance_headers=1
      Enable workaround for the Chromium/Blink bug #527446.

    • dual_final_frames=1
      Enable workaround for the Safari/WebKit bug when using option --drop-same-frames.
      Without this option, when the frame series is completed, WebKit-based browsers
      renders the last frame with a delay.

    • zero_data=1
      Disables the actual sending of JPEG data and leaves only response headers.

  • The mjpg-streamer compatibility layer:



Sources & docs ustreamer-4.9/src/ustreamer/data/index_html.c000066400000000000000000000075421414164060100214100ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "index_html.h" const char *const HTML_INDEX_PAGE = " \ \ \ \ \ \ uStreamer \ \ \ \ \

µStreamer v" VERSION "

\
\
    \
  • \ /state
    \ Get JSON structure with the state of the server. \
  • \
    \
  • \ /snapshot
    \ Get a current actual image from the server. \
  • \
    \
  • \ /stream
    \ Get a live stream. Query params:
    \
    \
      \
    • \ key=abc123
      \ The user-defined key, which is part of cookie stream_client, which allows
      \ the stream client to determine its identifier and view statistics using /state. \
    • \
      \
    • \ extra_headers=1
      \ Add X-UStreamer-* headers to the /stream handle \ (like with the /snapshot). \
    • \
      \
    • \ advance_headers=1
      \ Enable workaround for the Chromium/Blink bug \ #527446. \
    • \
      \
    • \ dual_final_frames=1
      \ Enable workaround for the Safari/WebKit bug when using option --drop-same-frames.
      \ Without this option, when the frame series is completed, WebKit-based browsers
      \ renders the last frame with a delay. \
    • \
      \
    • \ zero_data=1
      \ Disables the actual sending of JPEG data and leaves only response headers. \
    • \
    \
  • \
    \
  • \ The mjpg-streamer compatibility layer:
    \
    \ \
  • \
\
\
\ Sources & docs \ \ \ "; ustreamer-4.9/src/ustreamer/data/index_html.h000066400000000000000000000032371414164060100214120ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include "../../libs/config.h" extern const char *const HTML_INDEX_PAGE; ustreamer-4.9/src/ustreamer/device.c000066400000000000000000000663751414164060100176140ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "device.h" static const struct { const char *name; const v4l2_std_id standard; } _STANDARDS[] = { {"UNKNOWN", V4L2_STD_UNKNOWN}, {"PAL", V4L2_STD_PAL}, {"NTSC", V4L2_STD_NTSC}, {"SECAM", V4L2_STD_SECAM}, }; static const struct { const char *name; const unsigned format; } _FORMATS[] = { {"YUYV", V4L2_PIX_FMT_YUYV}, {"UYVY", V4L2_PIX_FMT_UYVY}, {"RGB565", V4L2_PIX_FMT_RGB565}, {"RGB24", V4L2_PIX_FMT_RGB24}, {"MJPEG", V4L2_PIX_FMT_MJPEG}, {"JPEG", V4L2_PIX_FMT_JPEG}, }; static const struct { const char *name; const enum v4l2_memory io_method; } _IO_METHODS[] = { {"MMAP", V4L2_MEMORY_MMAP}, {"USERPTR", V4L2_MEMORY_USERPTR}, }; static int _device_open_check_cap(device_s *dev); static int _device_open_dv_timings(device_s *dev); static int _device_apply_dv_timings(device_s *dev); static int _device_open_format(device_s *dev, bool first); static void _device_open_hw_fps(device_s *dev); static void _device_open_jpeg_quality(device_s *dev); static int _device_open_io_method(device_s *dev); static int _device_open_io_method_mmap(device_s *dev); static int _device_open_io_method_userptr(device_s *dev); static int _device_open_queue_buffers(device_s *dev); static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height); static void _device_apply_controls(device_s *dev); static int _device_query_control( device_s *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet); static void _device_set_control( device_s *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet); static const char *_format_to_string_nullable(unsigned format); static const char *_format_to_string_supported(unsigned format); static const char *_standard_to_string(v4l2_std_id standard); static const char *_io_method_to_string_supported(enum v4l2_memory io_method); #define RUN(_next) dev->run->_next device_s *device_init(void) { device_runtime_s *run; A_CALLOC(run, 1); run->fd = -1; device_s *dev; A_CALLOC(dev, 1); dev->path = "/dev/video0"; dev->width = 640; dev->height = 480; dev->format = V4L2_PIX_FMT_YUYV; dev->jpeg_quality = 80; dev->standard = V4L2_STD_UNKNOWN; dev->io_method = V4L2_MEMORY_MMAP; dev->n_bufs = get_cores_available() + 1; dev->min_frame_size = 128; dev->timeout = 1; dev->run = run; return dev; } void device_destroy(device_s *dev) { free(dev->run); free(dev); } int device_parse_format(const char *str) { for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) { if (!strcasecmp(str, _FORMATS[index].name)) { return _FORMATS[index].format; } } return FORMAT_UNKNOWN; } v4l2_std_id device_parse_standard(const char *str) { for (unsigned index = 1; index < ARRAY_LEN(_STANDARDS); ++index) { if (!strcasecmp(str, _STANDARDS[index].name)) { return _STANDARDS[index].standard; } } return STANDARD_UNKNOWN; } int device_parse_io_method(const char *str) { for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) { if (!strcasecmp(str, _IO_METHODS[index].name)) { return _IO_METHODS[index].io_method; } } return IO_METHOD_UNKNOWN; } int device_open(device_s *dev) { if ((RUN(fd) = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) { LOG_PERROR("Can't open device"); goto error; } LOG_INFO("Device fd=%d opened", RUN(fd)); if (_device_open_check_cap(dev) < 0) { goto error; } if (_device_open_dv_timings(dev) < 0) { goto error; } if (_device_open_format(dev, true) < 0) { goto error; } _device_open_hw_fps(dev); _device_open_jpeg_quality(dev); if (_device_open_io_method(dev) < 0) { goto error; } if (_device_open_queue_buffers(dev) < 0) { goto error; } _device_apply_controls(dev); LOG_DEBUG("Device fd=%d initialized", RUN(fd)); return 0; error: device_close(dev); return -1; } void device_close(device_s *dev) { RUN(persistent_timeout_reported) = false; if (RUN(hw_bufs)) { LOG_DEBUG("Releasing device buffers ..."); for (unsigned index = 0; index < RUN(n_bufs); ++index) { # define HW(_next) RUN(hw_bufs)[index]._next # ifdef WITH_OMX if (HW(vcsm_handle) > 0) { vcsm_free(HW(vcsm_handle)); HW(vcsm_handle) = -1; } if (HW(dma_fd) >= 0) { close(HW(dma_fd)); HW(dma_fd) = -1; } # endif if (dev->io_method == V4L2_MEMORY_MMAP) { if (HW(raw.allocated) > 0 && HW(raw.data) != MAP_FAILED) { if (munmap(HW(raw.data), HW(raw.allocated)) < 0) { LOG_PERROR("Can't unmap device buffer %u", index); } } } else { // V4L2_MEMORY_USERPTR if (HW(raw.data)) { free(HW(raw.data)); } } A_MUTEX_DESTROY(&HW(grabbed_mutex)); # undef HW } RUN(n_bufs) = 0; free(RUN(hw_bufs)); RUN(hw_bufs) = NULL; } if (RUN(fd) >= 0) { LOG_DEBUG("Closing device ..."); if (close(RUN(fd)) < 0) { LOG_PERROR("Can't close device fd=%d", RUN(fd)); } else { LOG_INFO("Device fd=%d closed", RUN(fd)); } RUN(fd) = -1; } } #ifdef WITH_OMX int device_export_to_vcsm(device_s *dev) { # define DMA_FD RUN(hw_bufs[index].dma_fd) # define VCSM_HANDLE RUN(hw_bufs[index].vcsm_handle) for (unsigned index = 0; index < RUN(n_bufs); ++index) { struct v4l2_exportbuffer exp; MEMSET_ZERO(exp); exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; exp.index = index; LOG_DEBUG("Calling ioctl(VIDIOC_EXPBUF) for buffer index=%u ...", index); if (xioctl(RUN(fd), VIDIOC_EXPBUF, &exp) < 0) { LOG_PERROR("Unable to export device buffer index=%u", index); goto error; } DMA_FD = exp.fd; LOG_DEBUG("Importing DMA buffer fd=%d into VCSM ...", DMA_FD); int vcsm_handle = vcsm_import_dmabuf(DMA_FD, "v4l2_buf"); if (vcsm_handle <= 0) { LOG_PERROR("Unable to import DMA buffer fd=%d into VCSM", DMA_FD); goto error; } VCSM_HANDLE = vcsm_handle; } return 0; error: for (unsigned index = 0; index < RUN(n_bufs); ++index) { if (VCSM_HANDLE > 0) { vcsm_free(VCSM_HANDLE); VCSM_HANDLE = -1; } if (DMA_FD >= 0) { close(DMA_FD); DMA_FD = -1; } } return -1; # undef VCSM_HANDLE # undef DMA_FD } #endif int device_switch_capturing(device_s *dev, bool enable) { if (enable != RUN(capturing)) { enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF")); if (xioctl(RUN(fd), (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) { LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop")); if (enable) { return -1; } } RUN(capturing) = enable; LOG_INFO("Capturing %s", (enable ? "started" : "stopped")); } return 0; } int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error) { int retval; # define INIT_FD_SET(_set) \ fd_set _set; FD_ZERO(&_set); FD_SET(RUN(fd), &_set); INIT_FD_SET(read_fds); INIT_FD_SET(write_fds); INIT_FD_SET(error_fds); # undef INIT_FD_SET struct timeval timeout; timeout.tv_sec = dev->timeout; timeout.tv_usec = 0; LOG_DEBUG("Calling select() on video device ..."); retval = select(RUN(fd) + 1, &read_fds, &write_fds, &error_fds, &timeout); if (retval > 0) { *has_read = FD_ISSET(RUN(fd), &read_fds); *has_write = FD_ISSET(RUN(fd), &write_fds); *has_error = FD_ISSET(RUN(fd), &error_fds); } else { *has_read = false; *has_write = false; *has_error = false; } LOG_DEBUG("Device select() --> %d", retval); if (retval > 0) { RUN(persistent_timeout_reported) = false; } else if (retval == 0) { if (dev->persistent) { if (!RUN(persistent_timeout_reported)) { LOG_ERROR("Persistent device timeout (unplugged)"); RUN(persistent_timeout_reported) = true; } } else { // Если устройство не персистентное, то таймаут является ошибкой retval = -1; } } return retval; } int device_grab_buffer(device_s *dev, hw_buffer_s **hw) { *hw = NULL; struct v4l2_buffer buf_info; MEMSET_ZERO(buf_info); buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_info.memory = dev->io_method; LOG_DEBUG("Grabbing device buffer ..."); if (xioctl(RUN(fd), VIDIOC_DQBUF, &buf_info) < 0) { LOG_PERROR("Unable to grab device buffer"); return -1; } LOG_DEBUG("Grabbed new frame in device buffer: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused); if (buf_info.index >= RUN(n_bufs)) { LOG_ERROR("V4L2 error: grabbed invalid device buffer: index=%u, n_bufs=%u", buf_info.index, RUN(n_bufs)); return -1; } // Workaround for broken, corrupted frames: // Under low light conditions corrupted frames may get captured. // The good thing is such frames are quite small compared to the regular frames. // For example a VGA (640x480) webcam frame is normally >= 8kByte large, // corrupted frames are smaller. if (buf_info.bytesused < dev->min_frame_size) { LOG_DEBUG("Dropped too small frame sized %d bytes, assuming it was broken", buf_info.bytesused); LOG_DEBUG("Releasing device buffer index=%u (broken frame) ...", buf_info.index); if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) { LOG_PERROR("Unable to release device buffer index=%u (broken frame)", buf_info.index); return -1; } return -2; } # define HW(_next) RUN(hw_bufs)[buf_info.index]._next A_MUTEX_LOCK(&HW(grabbed_mutex)); if (HW(grabbed)) { LOG_ERROR("V4L2 error: grabbed device buffer is already used: index=%u, bytesused=%u", buf_info.index, buf_info.bytesused); A_MUTEX_UNLOCK(&HW(grabbed_mutex)); return -1; } HW(grabbed) = true; A_MUTEX_UNLOCK(&HW(grabbed_mutex)); HW(raw.used) = buf_info.bytesused; HW(raw.width) = RUN(width); HW(raw.height) = RUN(height); HW(raw.format) = RUN(format); HW(raw.stride) = RUN(stride); HW(raw.online) = true; memcpy(&HW(buf_info), &buf_info, sizeof(struct v4l2_buffer)); HW(raw.grab_ts) = get_now_monotonic(); # undef HW *hw = &RUN(hw_bufs[buf_info.index]); return buf_info.index; } int device_release_buffer(device_s *dev, hw_buffer_s *hw) { const unsigned index = hw->buf_info.index; LOG_DEBUG("Releasing device buffer index=%u ...", index); A_MUTEX_LOCK(&hw->grabbed_mutex); if (xioctl(RUN(fd), VIDIOC_QBUF, &hw->buf_info) < 0) { LOG_PERROR("Unable to release device buffer index=%u", index); A_MUTEX_UNLOCK(&hw->grabbed_mutex); return -1; } hw->grabbed = false; A_MUTEX_UNLOCK(&hw->grabbed_mutex); return 0; } int device_consume_event(device_s *dev) { struct v4l2_event event; LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ..."); if (xioctl(RUN(fd), VIDIOC_DQEVENT, &event) == 0) { switch (event.type) { case V4L2_EVENT_SOURCE_CHANGE: LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed"); return -1; case V4L2_EVENT_EOS: LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)"); return 0; } } else { LOG_PERROR("Got some V4L2 device event, but where is it? "); } return 0; } static int _device_open_check_cap(device_s *dev) { struct v4l2_capability cap; MEMSET_ZERO(cap); LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ..."); if (xioctl(RUN(fd), VIDIOC_QUERYCAP, &cap) < 0) { LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)"); return -1; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { LOG_ERROR("Video capture not supported by the device"); return -1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { LOG_ERROR("Device does not support streaming IO"); return -1; } int input = dev->input; // Needs a pointer to int for ioctl() LOG_INFO("Using input channel: %d", input); if (xioctl(RUN(fd), VIDIOC_S_INPUT, &input) < 0) { LOG_ERROR("Can't set input channel"); return -1; } if (dev->standard != V4L2_STD_UNKNOWN) { LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard)); if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) { LOG_ERROR("Can't set video standard"); return -1; } } else { LOG_DEBUG("Using TV standard: DEFAULT"); } return 0; } static int _device_open_dv_timings(device_s *dev) { _device_apply_resolution(dev, dev->width, dev->height); if (dev->dv_timings) { LOG_DEBUG("Using DV timings"); if (_device_apply_dv_timings(dev) < 0) { return -1; } struct v4l2_event_subscription sub; MEMSET_ZERO(sub); sub.type = V4L2_EVENT_SOURCE_CHANGE; LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ..."); if (xioctl(RUN(fd), VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE"); return -1; } } return 0; } static int _device_apply_dv_timings(device_s *dev) { struct v4l2_dv_timings dv; MEMSET_ZERO(dv); LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ..."); if (xioctl(RUN(fd), VIDIOC_QUERY_DV_TIMINGS, &dv) == 0) { LOG_INFO("Got new DV timings: resolution=%ux%u, pixclk=%llu", dv.bt.width, dv.bt.height, (unsigned long long)dv.bt.pixelclock); // Issue #11 LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ..."); if (xioctl(RUN(fd), VIDIOC_S_DV_TIMINGS, &dv) < 0) { LOG_PERROR("Failed to set DV timings"); return -1; } if (_device_apply_resolution(dev, dv.bt.width, dv.bt.height) < 0) { return -1; } } else { LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ..."); if (xioctl(RUN(fd), VIDIOC_QUERYSTD, &dev->standard) == 0) { LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard)); if (xioctl(RUN(fd), VIDIOC_S_STD, &dev->standard) < 0) { LOG_PERROR("Can't set video standard"); return -1; } } } return 0; } static int _device_open_format(device_s *dev, bool first) { const unsigned stride = align_size(RUN(width), 32) << 1; struct v4l2_format fmt; MEMSET_ZERO(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = RUN(width); fmt.fmt.pix.height = RUN(height); fmt.fmt.pix.pixelformat = dev->format; fmt.fmt.pix.field = V4L2_FIELD_ANY; fmt.fmt.pix.bytesperline = stride; // Set format LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ..."); if (xioctl(RUN(fd), VIDIOC_S_FMT, &fmt) < 0) { LOG_PERROR("Unable to set pixelformat=%s, stride=%u, resolution=%ux%u", _format_to_string_supported(dev->format), stride, RUN(width), RUN(height)); return -1; } // Check resolution bool retry = false; if (fmt.fmt.pix.width != RUN(width) || fmt.fmt.pix.height != RUN(height)) { LOG_ERROR("Requested resolution=%ux%u is unavailable", RUN(width), RUN(height)); retry = true; } if (_device_apply_resolution(dev, fmt.fmt.pix.width, fmt.fmt.pix.height) < 0) { return -1; } if (first && retry) { return _device_open_format(dev, false); } LOG_INFO("Using resolution: %ux%u", RUN(width), RUN(height)); // Check format if (fmt.fmt.pix.pixelformat != dev->format) { LOG_ERROR("Could not obtain the requested pixelformat=%s; driver gave us %s", _format_to_string_supported(dev->format), _format_to_string_supported(fmt.fmt.pix.pixelformat)); char *format_str; if ((format_str = (char *)_format_to_string_nullable(fmt.fmt.pix.pixelformat)) != NULL) { LOG_INFO("Falling back to pixelformat=%s", format_str); } else { char fourcc_str[8]; LOG_ERROR("Unsupported pixelformat=%s (fourcc)", fourcc_to_string(fmt.fmt.pix.pixelformat, fourcc_str, 8)); return -1; } } RUN(format) = fmt.fmt.pix.pixelformat; LOG_INFO("Using pixelformat: %s", _format_to_string_supported(RUN(format))); RUN(stride) = fmt.fmt.pix.bytesperline; RUN(raw_size) = fmt.fmt.pix.sizeimage; // Only for userptr return 0; } static void _device_open_hw_fps(device_s *dev) { RUN(hw_fps) = 0; struct v4l2_streamparm setfps; MEMSET_ZERO(setfps); setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; LOG_DEBUG("Calling ioctl(VIDIOC_G_PARM) ..."); if (xioctl(RUN(fd), VIDIOC_G_PARM, &setfps) < 0) { if (errno == ENOTTY) { // Quiet message for Auvidea B101 LOG_INFO("Querying HW FPS changing is not supported"); } else { LOG_PERROR("Unable to query HW FPS changing"); } return; } if (!(setfps.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) { LOG_INFO("Changing HW FPS is not supported"); return; } # define SETFPS_TPF(_next) setfps.parm.capture.timeperframe._next MEMSET_ZERO(setfps); setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; SETFPS_TPF(numerator) = 1; SETFPS_TPF(denominator) = (dev->desired_fps == 0 ? 255 : dev->desired_fps); if (xioctl(RUN(fd), VIDIOC_S_PARM, &setfps) < 0) { LOG_PERROR("Unable to set HW FPS"); return; } if (SETFPS_TPF(numerator) != 1) { LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator)); return; } if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай LOG_ERROR("Invalid HW FPS denominator: 0"); return; } RUN(hw_fps) = SETFPS_TPF(denominator); if (dev->desired_fps != RUN(hw_fps)) { LOG_INFO("Using HW FPS: %u -> %u (coerced)", dev->desired_fps, RUN(hw_fps)); } else { LOG_INFO("Using HW FPS: %u", RUN(hw_fps)); } # undef SETFPS_TPF } static void _device_open_jpeg_quality(device_s *dev) { unsigned quality = 0; if (is_jpeg(RUN(format))) { struct v4l2_jpegcompression comp; MEMSET_ZERO(comp); if (xioctl(RUN(fd), VIDIOC_G_JPEGCOMP, &comp) < 0) { LOG_ERROR("Device does not support setting of HW encoding quality parameters"); } else { comp.quality = dev->jpeg_quality; if (xioctl(RUN(fd), VIDIOC_S_JPEGCOMP, &comp) < 0) { LOG_ERROR("Unable to change MJPG quality for JPEG source with HW pass-through encoder"); } else { quality = dev->jpeg_quality; } } } RUN(jpeg_quality) = quality; } static int _device_open_io_method(device_s *dev) { LOG_INFO("Using IO method: %s", _io_method_to_string_supported(dev->io_method)); switch (dev->io_method) { case V4L2_MEMORY_MMAP: return _device_open_io_method_mmap(dev); case V4L2_MEMORY_USERPTR: return _device_open_io_method_userptr(dev); default: assert(0 && "Unsupported IO method"); } return -1; } static int _device_open_io_method_mmap(device_s *dev) { struct v4l2_requestbuffers req; MEMSET_ZERO(req); req.count = dev->n_bufs; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_MMAP ..."); if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) { LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_MMAP", dev->path); return -1; } if (req.count < 1) { LOG_ERROR("Insufficient buffer memory: %u", req.count); return -1; } else { LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count); } LOG_DEBUG("Allocating device buffers ..."); A_CALLOC(RUN(hw_bufs), req.count); for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) { struct v4l2_buffer buf_info; MEMSET_ZERO(buf_info); buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_info.memory = V4L2_MEMORY_MMAP; buf_info.index = RUN(n_bufs); LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %u ...", RUN(n_bufs)); if (xioctl(RUN(fd), VIDIOC_QUERYBUF, &buf_info) < 0) { LOG_PERROR("Can't VIDIOC_QUERYBUF"); return -1; } # define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next # ifdef WITH_OMX HW(dma_fd) = -1; HW(vcsm_handle) = -1; # endif A_MUTEX_INIT(&HW(grabbed_mutex)); LOG_DEBUG("Mapping device buffer %u ...", RUN(n_bufs)); if ((HW(raw.data) = mmap( NULL, buf_info.length, PROT_READ | PROT_WRITE, MAP_SHARED, RUN(fd), buf_info.m.offset )) == MAP_FAILED) { LOG_PERROR("Can't map device buffer %u", RUN(n_bufs)); return -1; } HW(raw.allocated) = buf_info.length; # undef HW } return 0; } static int _device_open_io_method_userptr(device_s *dev) { struct v4l2_requestbuffers req; MEMSET_ZERO(req); req.count = dev->n_bufs; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_USERPTR; LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) for V4L2_MEMORY_USERPTR ..."); if (xioctl(RUN(fd), VIDIOC_REQBUFS, &req) < 0) { LOG_PERROR("Device '%s' doesn't support V4L2_MEMORY_USERPTR", dev->path); return -1; } if (req.count < 1) { LOG_ERROR("Insufficient buffer memory: %u", req.count); return -1; } else { LOG_INFO("Requested %u device buffers, got %u", dev->n_bufs, req.count); } LOG_DEBUG("Allocating device buffers ..."); A_CALLOC(RUN(hw_bufs), req.count); const unsigned page_size = getpagesize(); const unsigned buf_size = align_size(RUN(raw_size), page_size); for (RUN(n_bufs) = 0; RUN(n_bufs) < req.count; ++RUN(n_bufs)) { # define HW(_next) RUN(hw_bufs)[RUN(n_bufs)]._next assert(HW(raw.data) = aligned_alloc(page_size, buf_size)); memset(HW(raw.data), 0, buf_size); HW(raw.allocated) = buf_size; # undef HW } return 0; } static int _device_open_queue_buffers(device_s *dev) { for (unsigned index = 0; index < RUN(n_bufs); ++index) { struct v4l2_buffer buf_info; MEMSET_ZERO(buf_info); buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf_info.memory = dev->io_method; buf_info.index = index; if (dev->io_method == V4L2_MEMORY_USERPTR) { buf_info.m.userptr = (unsigned long)RUN(hw_bufs)[index].raw.data; buf_info.length = RUN(hw_bufs)[index].raw.allocated; } LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %u ...", index); if (xioctl(RUN(fd), VIDIOC_QBUF, &buf_info) < 0) { LOG_PERROR("Can't VIDIOC_QBUF"); return -1; } } return 0; } static int _device_apply_resolution(device_s *dev, unsigned width, unsigned height) { // Тут VIDEO_MIN_* не используются из-за странностей минимального разрешения при отсутствии сигнала // у некоторых устройств, например Auvidea B101 if ( width == 0 || width > VIDEO_MAX_WIDTH || height == 0 || height > VIDEO_MAX_HEIGHT ) { LOG_ERROR("Requested forbidden resolution=%ux%u: min=1x1, max=%ux%u", width, height, VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT); return -1; } RUN(width) = width; RUN(height) = height; return 0; } static void _device_apply_controls(device_s *dev) { # define SET_CID_VALUE(_cid, _field, _value, _quiet) { \ struct v4l2_queryctrl query; \ if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \ _device_set_control(dev, &query, #_field, _cid, _value, _quiet); \ } \ } # define SET_CID_DEFAULT(_cid, _field, _quiet) { \ struct v4l2_queryctrl query; \ if (_device_query_control(dev, &query, #_field, _cid, _quiet) == 0) { \ _device_set_control(dev, &query, #_field, _cid, query.default_value, _quiet); \ } \ } # define CONTROL_MANUAL_CID(_cid, _field) { \ if (dev->ctl._field.mode == CTL_MODE_VALUE) { \ SET_CID_VALUE(_cid, _field, dev->ctl._field.value, false); \ } else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \ SET_CID_DEFAULT(_cid, _field, false); \ } \ } # define CONTROL_AUTO_CID(_cid_auto, _cid_manual, _field) { \ if (dev->ctl._field.mode == CTL_MODE_VALUE) { \ SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); \ SET_CID_VALUE(_cid_manual, _field, dev->ctl._field.value, false); \ } else if (dev->ctl._field.mode == CTL_MODE_AUTO) { \ SET_CID_VALUE(_cid_auto, _field##_auto, 1, false); \ } else if (dev->ctl._field.mode == CTL_MODE_DEFAULT) { \ SET_CID_VALUE(_cid_auto, _field##_auto, 0, true); /* Reset inactive flag */ \ SET_CID_DEFAULT(_cid_manual, _field, false); \ SET_CID_DEFAULT(_cid_auto, _field##_auto, false); \ } \ } CONTROL_AUTO_CID (V4L2_CID_AUTOBRIGHTNESS, V4L2_CID_BRIGHTNESS, brightness); CONTROL_MANUAL_CID ( V4L2_CID_CONTRAST, contrast); CONTROL_MANUAL_CID ( V4L2_CID_SATURATION, saturation); CONTROL_AUTO_CID (V4L2_CID_HUE_AUTO, V4L2_CID_HUE, hue); CONTROL_MANUAL_CID ( V4L2_CID_GAMMA, gamma); CONTROL_MANUAL_CID ( V4L2_CID_SHARPNESS, sharpness); CONTROL_MANUAL_CID ( V4L2_CID_BACKLIGHT_COMPENSATION, backlight_compensation); CONTROL_AUTO_CID (V4L2_CID_AUTO_WHITE_BALANCE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, white_balance); CONTROL_AUTO_CID (V4L2_CID_AUTOGAIN, V4L2_CID_GAIN, gain); CONTROL_MANUAL_CID ( V4L2_CID_COLORFX, color_effect); CONTROL_MANUAL_CID ( V4L2_CID_ROTATE, rotate); CONTROL_MANUAL_CID ( V4L2_CID_VFLIP, flip_vertical); CONTROL_MANUAL_CID ( V4L2_CID_HFLIP, flip_horizontal); # undef CONTROL_AUTO_CID # undef CONTROL_MANUAL_CID # undef SET_CID_DEFAULT # undef SET_CID_VALUE } static int _device_query_control( device_s *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, bool quiet) { // cppcheck-suppress redundantPointerOp MEMSET_ZERO(*query); query->id = cid; if (xioctl(RUN(fd), VIDIOC_QUERYCTRL, query) < 0 || query->flags & V4L2_CTRL_FLAG_DISABLED) { if (!quiet) { LOG_ERROR("Changing control %s is unsupported", name); } return -1; } return 0; } static void _device_set_control( device_s *dev, struct v4l2_queryctrl *query, const char *name, unsigned cid, int value, bool quiet) { if (value < query->minimum || value > query->maximum || value % query->step != 0) { if (!quiet) { LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u", value, name, query->minimum, query->maximum, query->default_value, query->step); } return; } struct v4l2_control ctl; MEMSET_ZERO(ctl); ctl.id = cid; ctl.value = value; if (xioctl(RUN(fd), VIDIOC_S_CTRL, &ctl) < 0) { if (!quiet) { LOG_PERROR("Can't set control %s", name); } } else if (!quiet) { LOG_INFO("Applying control %s: %d", name, ctl.value); } } static const char *_format_to_string_nullable(unsigned format) { for (unsigned index = 0; index < ARRAY_LEN(_FORMATS); ++index) { if (format == _FORMATS[index].format) { return _FORMATS[index].name; } } return NULL; } static const char *_format_to_string_supported(unsigned format) { const char *format_str = _format_to_string_nullable(format); return (format_str == NULL ? "unsupported" : format_str); } static const char *_standard_to_string(v4l2_std_id standard) { for (unsigned index = 0; index < ARRAY_LEN(_STANDARDS); ++index) { if (standard == _STANDARDS[index].standard) { return _STANDARDS[index].name; } } return _STANDARDS[0].name; } static const char *_io_method_to_string_supported(enum v4l2_memory io_method) { for (unsigned index = 0; index < ARRAY_LEN(_IO_METHODS); ++index) { if (io_method == _IO_METHODS[index].io_method) { return _IO_METHODS[index].name; } } return "unsupported"; } # undef RUN ustreamer-4.9/src/ustreamer/device.h000066400000000000000000000107541414164060100176070ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_OMX # include #endif #include "../libs/tools.h" #include "../libs/logging.h" #include "../libs/threading.h" #include "../libs/frame.h" #include "xioctl.h" #define VIDEO_MIN_WIDTH ((unsigned)160) #define VIDEO_MAX_WIDTH ((unsigned)10240) #define VIDEO_MIN_HEIGHT ((unsigned)120) #define VIDEO_MAX_HEIGHT ((unsigned)4320) #define VIDEO_MAX_FPS ((unsigned)120) #define STANDARD_UNKNOWN V4L2_STD_UNKNOWN #define STANDARDS_STR "PAL, NTSC, SECAM" #define FORMAT_UNKNOWN -1 #define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG" #define IO_METHOD_UNKNOWN -1 #define IO_METHODS_STR "MMAP, USERPTR" typedef struct { frame_s raw; struct v4l2_buffer buf_info; # ifdef WITH_OMX int dma_fd; int vcsm_handle; # endif pthread_mutex_t grabbed_mutex; bool grabbed; } hw_buffer_s; typedef struct { int fd; unsigned width; unsigned height; unsigned format; unsigned stride; unsigned hw_fps; unsigned jpeg_quality; size_t raw_size; unsigned n_bufs; hw_buffer_s *hw_bufs; bool capturing; bool persistent_timeout_reported; } device_runtime_s; typedef enum { CTL_MODE_NONE = 0, CTL_MODE_VALUE, CTL_MODE_AUTO, CTL_MODE_DEFAULT, } control_mode_e; typedef struct { control_mode_e mode; int value; } control_s; typedef struct { control_s brightness; control_s contrast; control_s saturation; control_s hue; control_s gamma; control_s sharpness; control_s backlight_compensation; control_s white_balance; control_s gain; control_s color_effect; control_s rotate; control_s flip_vertical; control_s flip_horizontal; } controls_s; typedef struct { char *path; unsigned input; unsigned width; unsigned height; unsigned format; unsigned jpeg_quality; v4l2_std_id standard; enum v4l2_memory io_method; bool dv_timings; unsigned n_bufs; unsigned desired_fps; size_t min_frame_size; bool persistent; unsigned timeout; controls_s ctl; device_runtime_s *run; } device_s; device_s *device_init(void); void device_destroy(device_s *dev); int device_parse_format(const char *str); v4l2_std_id device_parse_standard(const char *str); int device_parse_io_method(const char *str); int device_open(device_s *dev); void device_close(device_s *dev); #ifdef WITH_OMX int device_export_to_vcsm(device_s *dev); #endif int device_switch_capturing(device_s *dev, bool enable); int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error); int device_grab_buffer(device_s *dev, hw_buffer_s **hw); int device_release_buffer(device_s *dev, hw_buffer_s *hw); int device_consume_event(device_s *dev); ustreamer-4.9/src/ustreamer/encoder.c000066400000000000000000000201041414164060100177500ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "encoder.h" static const struct { const char *name; const encoder_type_e type; } _ENCODER_TYPES[] = { {"CPU", ENCODER_TYPE_CPU}, {"HW", ENCODER_TYPE_HW}, # ifdef WITH_OMX {"OMX", ENCODER_TYPE_OMX}, # endif {"NOOP", ENCODER_TYPE_NOOP}, }; static void *_worker_job_init(void *v_enc); static void _worker_job_destroy(void *v_job); static bool _worker_run_job(worker_s *wr); #define ER(_next) enc->run->_next encoder_s *encoder_init(void) { encoder_runtime_s *run; A_CALLOC(run, 1); run->type = ENCODER_TYPE_CPU; run->quality = 80; A_MUTEX_INIT(&run->mutex); encoder_s *enc; A_CALLOC(enc, 1); enc->type = run->type; enc->n_workers = get_cores_available(); enc->run = run; return enc; } void encoder_destroy(encoder_s *enc) { # ifdef WITH_OMX if (ER(omxs)) { for (unsigned index = 0; index < ER(n_omxs); ++index) { if (ER(omxs[index])) { omx_encoder_destroy(ER(omxs[index])); } } free(ER(omxs)); } # endif A_MUTEX_DESTROY(&ER(mutex)); free(enc->run); free(enc); } encoder_type_e encoder_parse_type(const char *str) { for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) { if (!strcasecmp(str, _ENCODER_TYPES[index].name)) { return _ENCODER_TYPES[index].type; } } return ENCODER_TYPE_UNKNOWN; } const char *encoder_type_to_string(encoder_type_e type) { for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) { if (_ENCODER_TYPES[index].type == type) { return _ENCODER_TYPES[index].name; } } return _ENCODER_TYPES[0].name; } workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) { # define DR(_next) dev->run->_next encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type); unsigned quality = dev->jpeg_quality; unsigned n_workers = min_u(enc->n_workers, DR(n_bufs)); bool cpu_forced = false; if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) { LOG_INFO("Switching to HW encoder: the input is (M)JPEG ..."); type = ENCODER_TYPE_HW; } if (type == ENCODER_TYPE_HW) { if (!is_jpeg(DR(format))) { LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ..."); goto use_cpu; } quality = DR(jpeg_quality); n_workers = 1; } # ifdef WITH_OMX else if (type == ENCODER_TYPE_OMX) { if (align_size(DR(width), 32) != DR(width)) { LOG_INFO("Switching to CPU encoder: OMX can't handle width=%u ...", DR(width)); goto use_cpu; } LOG_DEBUG("Preparing OMX encoder ..."); if (n_workers > OMX_MAX_ENCODERS) { LOG_INFO("OMX encoder sets limit for worker threads: %u", OMX_MAX_ENCODERS); n_workers = OMX_MAX_ENCODERS; } if (ER(omxs) == NULL) { A_CALLOC(ER(omxs), OMX_MAX_ENCODERS); } // Начинаем с нуля и доинициализируем на следующих заходах при необходимости for (; ER(n_omxs) < n_workers; ++ER(n_omxs)) { if ((ER(omxs[ER(n_omxs)]) = omx_encoder_init()) == NULL) { LOG_ERROR("Can't initialize OMX encoder, falling back to CPU"); goto force_cpu; } } frame_s frame; MEMSET_ZERO(frame); frame.width = DR(width); frame.height = DR(height); frame.format = DR(format); frame.stride = DR(stride); for (unsigned index = 0; index < ER(n_omxs); ++index) { int omx_error = omx_encoder_prepare(ER(omxs[index]), &frame, quality); if (omx_error == -2) { goto use_cpu; } else if (omx_error < 0) { goto force_cpu; } } } # endif else if (type == ENCODER_TYPE_NOOP) { n_workers = 1; quality = 0; } goto ok; # ifdef WITH_OMX force_cpu: LOG_ERROR("Forced CPU encoder permanently"); cpu_forced = true; # endif use_cpu: type = ENCODER_TYPE_CPU; quality = dev->jpeg_quality; ok: if (type == ENCODER_TYPE_NOOP) { LOG_INFO("Using JPEG NOOP encoder"); } else if (quality == 0) { LOG_INFO("Using JPEG quality: encoder default"); } else { LOG_INFO("Using JPEG quality: %u%%", quality); } A_MUTEX_LOCK(&ER(mutex)); ER(type) = type; ER(quality) = quality; if (cpu_forced) { ER(cpu_forced) = true; } A_MUTEX_UNLOCK(&ER(mutex)); long double desired_interval = 0; if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) { desired_interval = (long double)1 / dev->desired_fps; } return workers_pool_init( "JPEG", "jw", n_workers, desired_interval, _worker_job_init, (void *)enc, _worker_job_destroy, _worker_run_job); # undef DR } void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) { A_MUTEX_LOCK(&ER(mutex)); *type = ER(type); *quality = ER(quality); A_MUTEX_UNLOCK(&ER(mutex)); } static void *_worker_job_init(void *v_enc) { encoder_job_s *job; A_CALLOC(job, 1); job->enc = (encoder_s *)v_enc; job->dest = frame_init(); return (void *)job; } static void _worker_job_destroy(void *v_job) { encoder_job_s *job = (encoder_job_s *)v_job; frame_destroy(job->dest); free(job); } #undef ER static bool _worker_run_job(worker_s *wr) { encoder_job_s *job = (encoder_job_s *)wr->job; frame_s *src = &job->hw->raw; frame_s *dest = job->dest; # define ER(_next) job->enc->run->_next LOG_DEBUG("Worker %s compressing JPEG from buffer %u ...", wr->name, job->hw->buf_info.index); assert(ER(type) != ENCODER_TYPE_UNKNOWN); assert(src->used > 0); frame_copy_meta(src, dest); dest->format = V4L2_PIX_FMT_JPEG; dest->stride = 0; dest->encode_begin_ts = get_now_monotonic(); dest->used = 0; if (ER(type) == ENCODER_TYPE_CPU) { LOG_VERBOSE("Compressing buffer using CPU"); cpu_encoder_compress(src, dest, ER(quality)); } else if (ER(type) == ENCODER_TYPE_HW) { LOG_VERBOSE("Compressing buffer using HW (just copying)"); hw_encoder_compress(src, dest); } # ifdef WITH_OMX else if (ER(type) == ENCODER_TYPE_OMX) { LOG_VERBOSE("Compressing buffer using OMX"); if (omx_encoder_compress(ER(omxs[wr->number]), src, dest) < 0) { goto error; } } # endif else if (ER(type) == ENCODER_TYPE_NOOP) { LOG_VERBOSE("Compressing buffer using NOOP (do nothing)"); usleep(5000); // Просто чтобы работала логика desired_fps } dest->encode_end_ts = get_now_monotonic(); LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u", job->dest->used, job->dest->encode_end_ts - job->dest->encode_begin_ts, wr->name, job->hw->buf_info.index); return true; # ifdef WITH_OMX error: LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf_info.index); LOG_ERROR("Error while compressing buffer, falling back to CPU"); A_MUTEX_LOCK(&ER(mutex)); ER(cpu_forced) = true; A_MUTEX_UNLOCK(&ER(mutex)); return false; # endif # undef ER } ustreamer-4.9/src/ustreamer/encoder.h000066400000000000000000000062151414164060100177640ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include "../libs/tools.h" #include "../libs/threading.h" #include "../libs/logging.h" #include "../libs/frame.h" #include "device.h" #include "workers.h" #include "encoders/cpu/encoder.h" #include "encoders/hw/encoder.h" #ifdef WITH_OMX # include "encoders/omx/encoder.h" # define ENCODER_TYPES_OMX_HINT ", OMX" #else # define ENCODER_TYPES_OMX_HINT "" #endif #define ENCODER_TYPES_STR \ "CPU, HW" \ ENCODER_TYPES_OMX_HINT \ ", NOOP" typedef enum { ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main() ENCODER_TYPE_CPU, ENCODER_TYPE_HW, # ifdef WITH_OMX ENCODER_TYPE_OMX, # endif ENCODER_TYPE_NOOP, } encoder_type_e; typedef struct { encoder_type_e type; unsigned quality; bool cpu_forced; pthread_mutex_t mutex; # ifdef WITH_OMX unsigned n_omxs; omx_encoder_s **omxs; # endif } encoder_runtime_s; typedef struct { encoder_type_e type; unsigned n_workers; encoder_runtime_s *run; } encoder_s; typedef struct { encoder_s *enc; hw_buffer_s *hw; char *dest_role; frame_s *dest; } encoder_job_s; encoder_s *encoder_init(void); void encoder_destroy(encoder_s *enc); encoder_type_e encoder_parse_type(const char *str); const char *encoder_type_to_string(encoder_type_e type); workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev); void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality); int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest); ustreamer-4.9/src/ustreamer/encoders/000077500000000000000000000000001414164060100177725ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/encoders/cpu/000077500000000000000000000000001414164060100205615ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/encoders/cpu/encoder.c000066400000000000000000000212331414164060100223450ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # This source file based on code of MJPG-Streamer. # # # # Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard # # Copyright (C) 2006 Gabriel A. Devenyi # # Copyright (C) 2007 Tom Stöveken # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "encoder.h" typedef struct { struct jpeg_destination_mgr mgr; // Default manager JOCTET *buf; // Start of buffer frame_s *frame; } _jpeg_dest_manager_s; static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame); static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame); static void _jpeg_init_destination(j_compress_ptr jpeg); static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg); static void _jpeg_term_destination(j_compress_ptr jpeg); void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) { // This function based on compress_image_to_jpeg() from mjpg-streamer struct jpeg_compress_struct jpeg; struct jpeg_error_mgr jpeg_error; jpeg.err = jpeg_std_error(&jpeg_error); jpeg_create_compress(&jpeg); _jpeg_set_dest_frame(&jpeg, dest); jpeg.image_width = src->width; jpeg.image_height = src->height; jpeg.input_components = 3; jpeg.in_color_space = JCS_RGB; jpeg_set_defaults(&jpeg); jpeg_set_quality(&jpeg, quality, TRUE); jpeg_start_compress(&jpeg, TRUE); # define WRITE_SCANLINES(_format, _func) \ case _format: { _func(&jpeg, src); break; } switch (src->format) { // https://www.fourcc.org/yuv.php WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv); WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy); WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565); WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24); default: assert(0 && "Unsupported input format for CPU encoder"); } # undef WRITE_SCANLINES jpeg_finish_compress(&jpeg); jpeg_destroy_compress(&jpeg); assert(dest->used > 0); } static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) { if (jpeg->dest == NULL) { assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)( (j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s) ))); } _jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; dest->mgr.init_destination = _jpeg_init_destination; dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer; dest->mgr.term_destination = _jpeg_term_destination; dest->frame = frame; frame->used = 0; } #define YUV_R(_y, _, _v) (((_y) + (359 * (_v))) >> 8) #define YUV_G(_y, _u, _v) (((_y) - (88 * (_u)) - (183 * (_v))) >> 8) #define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8) #define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x))) static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) { uint8_t *line_buf; A_CALLOC(line_buf, frame->width * 3); const unsigned padding = frame_get_padding(frame); const uint8_t *data = frame->data; unsigned z = 0; while (jpeg->next_scanline < frame->height) { uint8_t *ptr = line_buf; for (unsigned x = 0; x < frame->width; ++x) { int y = (!z ? data[0] << 8 : data[2] << 8); int u = data[1] - 128; int v = data[3] - 128; int r = YUV_R(y, u, v); int g = YUV_G(y, u, v); int b = YUV_B(y, u, v); *(ptr++) = NORM_COMPONENT(r); *(ptr++) = NORM_COMPONENT(g); *(ptr++) = NORM_COMPONENT(b); if (z++) { z = 0; data += 4; } } data += padding; JSAMPROW scanlines[1] = {line_buf}; jpeg_write_scanlines(jpeg, scanlines, 1); } free(line_buf); } static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) { uint8_t *line_buf; A_CALLOC(line_buf, frame->width * 3); const unsigned padding = frame_get_padding(frame); const uint8_t *data = frame->data; unsigned z = 0; while (jpeg->next_scanline < frame->height) { uint8_t *ptr = line_buf; for (unsigned x = 0; x < frame->width; ++x) { int y = (!z ? data[1] << 8 : data[3] << 8); int u = data[0] - 128; int v = data[2] - 128; int r = YUV_R(y, u, v); int g = YUV_G(y, u, v); int b = YUV_B(y, u, v); *(ptr++) = NORM_COMPONENT(r); *(ptr++) = NORM_COMPONENT(g); *(ptr++) = NORM_COMPONENT(b); if (z++) { z = 0; data += 4; } } data += padding; JSAMPROW scanlines[1] = {line_buf}; jpeg_write_scanlines(jpeg, scanlines, 1); } free(line_buf); } #undef NORM_COMPONENT #undef YUV_B #undef YUV_G #undef YUV_R static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) { uint8_t *line_buf; A_CALLOC(line_buf, frame->width * 3); const unsigned padding = frame_get_padding(frame); const uint8_t *data = frame->data; while (jpeg->next_scanline < frame->height) { uint8_t *ptr = line_buf; for (unsigned x = 0; x < frame->width; ++x) { unsigned int two_byte = (data[1] << 8) + data[0]; *(ptr++) = data[1] & 248; // Red *(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green *(ptr++) = (data[0] & 31) * 8; // Blue data += 2; } data += padding; JSAMPROW scanlines[1] = {line_buf}; jpeg_write_scanlines(jpeg, scanlines, 1); } free(line_buf); } static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) { const unsigned padding = frame_get_padding(frame); uint8_t *data = frame->data; while (jpeg->next_scanline < frame->height) { JSAMPROW scanlines[1] = {data}; jpeg_write_scanlines(jpeg, scanlines, 1); data += (jpeg->next_scanline * frame->width * 3) + padding; } } #define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096) static void _jpeg_init_destination(j_compress_ptr jpeg) { _jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; // Allocate the output buffer - it will be released when done with image assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)( (j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET) ))); dest->mgr.next_output_byte = dest->buf; dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE; } static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) { // Called whenever local jpeg buffer fills up _jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE); dest->mgr.next_output_byte = dest->buf; dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE; return TRUE; } static void _jpeg_term_destination(j_compress_ptr jpeg) { // Called by jpeg_finish_compress after all data has been written. // Usually needs to flush buffer. _jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest; size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer; // Write any data remaining in the buffer. frame_append_data(dest->frame, dest->buf, final); } #undef JPEG_OUTPUT_BUFFER_SIZE ustreamer-4.9/src/ustreamer/encoders/cpu/encoder.h000066400000000000000000000035241414164060100223550ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include "../../../libs/tools.h" #include "../../../libs/frame.h" void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality); ustreamer-4.9/src/ustreamer/encoders/hw/000077500000000000000000000000001414164060100204105ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/encoders/hw/encoder.c000066400000000000000000000061061414164060100221760ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # This source file based on code of MJPG-Streamer. # # # # Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard # # Copyright (C) 2006 Gabriel A. Devenyi # # Copyright (C) 2007 Tom Stöveken # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "encoder.h" void _copy_plus_huffman(const frame_s *src, frame_s *dest); static bool _is_huffman(const uint8_t *data); void hw_encoder_compress(const frame_s *src, frame_s *dest) { assert(is_jpeg(src->format)); _copy_plus_huffman(src, dest); } void _copy_plus_huffman(const frame_s *src, frame_s *dest) { if (!_is_huffman(src->data)) { const uint8_t *src_ptr = src->data; const uint8_t *src_end = src->data + src->used; while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) { src_ptr += 1; } if (src_ptr >= src_end) { dest->used = 0; // Error return; } const size_t paste = src_ptr - src->data; frame_set_data(dest, src->data, paste); frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE)); frame_append_data(dest, src_ptr, src->used - paste); } else { frame_set_data(dest, src->data, src->used); } } static bool _is_huffman(const uint8_t *data) { unsigned count = 0; while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) { if (count++ > 2048) { return false; } if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) { return true; } data += 1; } return false; } ustreamer-4.9/src/ustreamer/encoders/hw/encoder.h000066400000000000000000000034421414164060100222030ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include "../../../libs/frame.h" #include "huffman.h" void hw_encoder_compress(const frame_s *src, frame_s *dest); ustreamer-4.9/src/ustreamer/encoders/hw/huffman.h000066400000000000000000000110051414164060100222020ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # This source file based on code of MJPG-Streamer. # # # # Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard # # Copyright (C) 2006 Gabriel A. Devenyi # # Copyright (C) 2007 Tom Stöveken # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include static const uint8_t HUFFMAN_TABLE[] = { 0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, }; ustreamer-4.9/src/ustreamer/encoders/omx/000077500000000000000000000000001414164060100205755ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/encoders/omx/component.c000066400000000000000000000133751414164060100227540ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "component.h" static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled); static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted); int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port) { OMX_ERRORTYPE error; LOG_DEBUG("Enabling OMX port %u ...", port); if ((error = OMX_SendCommand(*comp, OMX_CommandPortEnable, port, NULL)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't enable OMX port %u", port); return -1; } return _omx_component_wait_port_changed(comp, port, OMX_TRUE); } int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port) { OMX_ERRORTYPE error; LOG_DEBUG("Disabling OMX port %u ...", port); if ((error = OMX_SendCommand(*comp, OMX_CommandPortDisable, port, NULL)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't disable OMX port %u", port); return -1; } return _omx_component_wait_port_changed(comp, port, OMX_FALSE); } int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port) { OMX_ERRORTYPE error; // cppcheck-suppress redundantPointerOp OMX_INIT_STRUCTURE(*portdef); portdef->nPortIndex = port; LOG_DEBUG("Fetching OMX port %u definition ...", port); if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't get OMX port %u definition", port); return -1; } return 0; } int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef) { OMX_ERRORTYPE error; LOG_DEBUG("Writing OMX port %u definition ...", portdef->nPortIndex); if ((error = OMX_SetParameter(*comp, OMX_IndexParamPortDefinition, portdef)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't set OMX port %u definition", portdef->nPortIndex); return -1; } return 0; } int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state) { const char *state_str = omx_state_to_string(state); OMX_ERRORTYPE error; LOG_DEBUG("Switching component state to %s ...", state_str); int retries = 50; do { error = OMX_SendCommand(*comp, OMX_CommandStateSet, state, NULL); if (error == OMX_ErrorNone) { return _omx_component_wait_state_changed(comp, state); } else if (error == OMX_ErrorInsufficientResources && retries) { // Иногда железо не инициализируется, хз почему, просто ретраим, со второй попытки сработает if (retries > 45) { LOG_VERBOSE("Can't switch OMX component state to %s, need to retry: %s", state_str, omx_error_to_string(error)); } else { LOG_ERROR_OMX(error, "Can't switch OMX component state to %s, need to retry", state_str); } retries -= 1; usleep(8000); } else { break; } } while (retries); LOG_ERROR_OMX(error, "Can't switch OMX component state to %s", state_str); return -1; } static int _omx_component_wait_port_changed(OMX_HANDLETYPE *comp, OMX_U32 port, OMX_BOOL enabled) { OMX_ERRORTYPE error; OMX_PARAM_PORTDEFINITIONTYPE portdef; OMX_INIT_STRUCTURE(portdef); portdef.nPortIndex = port; int retries = 50; do { if ((error = OMX_GetParameter(*comp, OMX_IndexParamPortDefinition, &portdef)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't get OMX port %u definition for waiting", port); return -1; } if (portdef.bEnabled != enabled) { LOG_DEBUG("Waiting for OMX %s port %u", (enabled ? "enabling" : "disabling"), port); retries -= 1; usleep(8000); } } while (portdef.bEnabled != enabled && retries); LOG_DEBUG("OMX port %u %s", port, (enabled ? "enabled" : "disabled")); return (portdef.bEnabled == enabled ? 0 : -1); } static int _omx_component_wait_state_changed(OMX_HANDLETYPE *comp, OMX_STATETYPE wanted) { OMX_ERRORTYPE error; OMX_STATETYPE state; int retries = 50; do { if ((error = OMX_GetState(*comp, &state)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Failed to get OMX component state"); return -1; } if (state != wanted) { LOG_DEBUG("Waiting when OMX component state changes to %s", omx_state_to_string(wanted)); retries -= 1; usleep(8000); } } while (state != wanted && retries); LOG_DEBUG("Switched OMX component state to %s", omx_state_to_string(wanted)) return (state == wanted ? 0 : -1); } ustreamer-4.9/src/ustreamer/encoders/omx/component.h000066400000000000000000000047511414164060100227570ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include "../../../libs/logging.h" #include "formatters.h" #define OMX_INIT_STRUCTURE(_var) { \ memset(&(_var), 0, sizeof(_var)); \ (_var).nSize = sizeof(_var); \ (_var).nVersion.nVersion = OMX_VERSION; \ (_var).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \ (_var).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \ (_var).nVersion.s.nRevision = OMX_VERSION_REVISION; \ (_var).nVersion.s.nStep = OMX_VERSION_STEP; \ } int omx_component_enable_port(OMX_HANDLETYPE *comp, OMX_U32 port); int omx_component_disable_port(OMX_HANDLETYPE *comp, OMX_U32 port); int omx_component_get_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef, OMX_U32 port); int omx_component_set_portdef(OMX_HANDLETYPE *comp, OMX_PARAM_PORTDEFINITIONTYPE *portdef); int omx_component_set_state(OMX_HANDLETYPE *comp, OMX_STATETYPE state); ustreamer-4.9/src/ustreamer/encoders/omx/encoder.c000066400000000000000000000330241414164060100223620ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "encoder.h" static const OMX_U32 _INPUT_PORT = 340; static const OMX_U32 _OUTPUT_PORT = 341; static int _omx_init_component(omx_encoder_s *omx); static int _omx_init_disable_ports(omx_encoder_s *omx); static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame); static int _omx_setup_output(omx_encoder_s *omx, unsigned quality); static int _omx_encoder_clear_ports(omx_encoder_s *omx); static OMX_ERRORTYPE _omx_event_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1, UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data); static OMX_ERRORTYPE _omx_input_required_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf); static OMX_ERRORTYPE _omx_output_available_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf); omx_encoder_s *omx_encoder_init(void) { // Some theory: // - http://www.fourcc.org/yuv.php // - https://kwasi-ich.de/blog/2017/11/26/omx/ // - https://github.com/hopkinskong/rpi-omx-jpeg-encode/blob/master/jpeg_bench.cpp // - https://github.com/kwasmich/OMXPlayground/blob/master/omxJPEGEnc.c // - https://github.com/gagle/raspberrypi-openmax-jpeg/blob/master/jpeg.c // - https://www.raspberrypi.org/forums/viewtopic.php?t=154790 // - https://bitbucket.org/bensch128/omxjpegencode/src/master/jpeg_encoder.cpp // - http://home.nouwen.name/RaspberryPi/documentation/ilcomponents/image_encode.html LOG_INFO("Initializing OMX encoder ..."); omx_encoder_s *omx; A_CALLOC(omx, 1); if (vcos_semaphore_create(&omx->handler_sem, "handler_sem", 0) != VCOS_SUCCESS) { LOG_ERROR("Can't create VCOS semaphore"); goto error; } omx->i_handler_sem = true; if (_omx_init_component(omx) < 0) { goto error; } if (_omx_init_disable_ports(omx) < 0) { goto error; } return omx; error: omx_encoder_destroy(omx); return NULL; } void omx_encoder_destroy(omx_encoder_s *omx) { LOG_INFO("Destroying OMX encoder ..."); omx_component_set_state(&omx->comp, OMX_StateIdle); _omx_encoder_clear_ports(omx); omx_component_set_state(&omx->comp, OMX_StateLoaded); if (omx->i_handler_sem) { vcos_semaphore_delete(&omx->handler_sem); } if (omx->i_encoder) { OMX_ERRORTYPE error; if ((error = OMX_FreeHandle(omx->comp)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't free OMX.broadcom.image_encode"); } } free(omx); } int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality) { if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) { LOG_ERROR("%u %u", frame->width, frame->stride); LOG_ERROR("OMX encoder can't handle unaligned width"); return -2; } if (omx_component_set_state(&omx->comp, OMX_StateIdle) < 0) { return -1; } if (_omx_encoder_clear_ports(omx) < 0) { return -1; } if (_omx_setup_input(omx, frame) < 0) { return -1; } if (_omx_setup_output(omx, quality) < 0) { return -1; } if (omx_component_set_state(&omx->comp, OMX_StateExecuting) < 0) { return -1; } return 0; } int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest) { # define IN(_next) omx->input_buf->_next # define OUT(_next) omx->output_buf->_next OMX_ERRORTYPE error; if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder"); return -1; } dest->width = align_size(src->width, 32); dest->used = 0; omx->output_available = false; omx->input_required = true; size_t slice_size = (IN(nAllocLen) < src->used ? IN(nAllocLen) : src->used); size_t pos = 0; while (true) { if (omx->failed) { return -1; } if (omx->output_available) { omx->output_available = false; frame_append_data(dest, OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen)); if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) { OUT(nFlags) = 0; break; } if ((error = OMX_FillThisBuffer(omx->comp, omx->output_buf)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Failed to request filling of the output buffer on encoder"); return -1; } } if (omx->input_required) { omx->input_required = false; if (pos == src->used) { continue; } memcpy(IN(pBuffer), src->data + pos, slice_size); IN(nOffset) = 0; IN(nFilledLen) = slice_size; pos += slice_size; if (pos + slice_size > src->used) { slice_size = src->used - pos; } if ((error = OMX_EmptyThisBuffer(omx->comp, omx->input_buf)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Failed to request emptying of the input buffer on encoder"); return -1; } } if (vcos_my_semwait("", &omx->handler_sem, 1) < 0) { return -1; } } # undef OUT # undef IN return 0; } static int _omx_init_component(omx_encoder_s *omx) { OMX_ERRORTYPE error; OMX_CALLBACKTYPE callbacks; MEMSET_ZERO(callbacks); callbacks.EventHandler = _omx_event_handler; callbacks.EmptyBufferDone = _omx_input_required_handler; callbacks.FillBufferDone = _omx_output_available_handler; LOG_DEBUG("Initializing OMX.broadcom.image_encode ..."); if ((error = OMX_GetHandle(&omx->comp, "OMX.broadcom.image_encode", omx, &callbacks)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't initialize OMX.broadcom.image_encode"); return -1; } omx->i_encoder = true; return 0; } static int _omx_init_disable_ports(omx_encoder_s *omx) { OMX_ERRORTYPE error; OMX_INDEXTYPE types[] = { OMX_IndexParamAudioInit, OMX_IndexParamVideoInit, OMX_IndexParamImageInit, OMX_IndexParamOtherInit, }; OMX_PORT_PARAM_TYPE ports; OMX_INIT_STRUCTURE(ports); if ((error = OMX_GetParameter(omx->comp, OMX_IndexParamImageInit, &ports)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't OMX_GetParameter(OMX_IndexParamImageInit)"); return -1; } for (unsigned index = 0; index < 4; ++index) { if ((error = OMX_GetParameter(omx->comp, types[index], &ports)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't OMX_GetParameter(types[%u])", index); return -1; } for (OMX_U32 port = ports.nStartPortNumber; port < ports.nStartPortNumber + ports.nPorts; ++port) { if (omx_component_disable_port(&omx->comp, port) < 0) { return -1; } } } return 0; } static int _omx_setup_input(omx_encoder_s *omx, const frame_s *frame) { LOG_DEBUG("Setting up OMX JPEG input port ..."); OMX_ERRORTYPE error; OMX_PARAM_PORTDEFINITIONTYPE portdef; if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) { LOG_ERROR("... first"); return -1; } # define IFMT(_next) portdef.format.image._next IFMT(nFrameWidth) = align_size(frame->width, 32); IFMT(nFrameHeight) = frame->height; IFMT(nStride) = align_size(frame->width, 32) << 1; IFMT(nSliceHeight) = align_size(frame->height, 16); IFMT(bFlagErrorConcealment) = OMX_FALSE; IFMT(eCompressionFormat) = OMX_IMAGE_CodingUnused; portdef.nBufferSize = ((frame->width * frame->height) << 1) * 2; switch (frame->format) { // https://www.fourcc.org/yuv.php // Also see comments inside OMX_IVCommon.h case V4L2_PIX_FMT_YUYV: IFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr; break; case V4L2_PIX_FMT_UYVY: IFMT(eColorFormat) = OMX_COLOR_FormatCbYCrY; break; case V4L2_PIX_FMT_RGB565: IFMT(eColorFormat) = OMX_COLOR_Format16bitRGB565; break; case V4L2_PIX_FMT_RGB24: IFMT(eColorFormat) = OMX_COLOR_Format24bitRGB888; break; // TODO: найти устройство с RGB565 и протестить его. // FIXME: RGB24 не работает нормально, нижняя половина экрана зеленая. // FIXME: Китайский EasyCap тоже не работает, мусор на экране. // Вероятно обе проблемы вызваны некорректной реализацией OMX на пае. default: assert(0 && "Unsupported pixelformat"); } # undef IFMT if (omx_component_set_portdef(&omx->comp, &portdef) < 0) { return -1; } if (omx_component_get_portdef(&omx->comp, &portdef, _INPUT_PORT) < 0) { LOG_ERROR("... second"); return -1; } if (omx_component_enable_port(&omx->comp, _INPUT_PORT) < 0) { return -1; } omx->i_input_port_enabled = true; if ((error = OMX_AllocateBuffer(omx->comp, &omx->input_buf, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't allocate OMX JPEG input buffer"); return -1; } return 0; } static int _omx_setup_output(omx_encoder_s *omx, unsigned quality) { LOG_DEBUG("Setting up OMX JPEG output port ..."); OMX_ERRORTYPE error; OMX_PARAM_PORTDEFINITIONTYPE portdef; if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) { LOG_ERROR("... first"); return -1; } # define OFMT(_next) portdef.format.image._next OFMT(bFlagErrorConcealment) = OMX_FALSE; OFMT(eCompressionFormat) = OMX_IMAGE_CodingJPEG; OFMT(eColorFormat) = OMX_COLOR_FormatYCbYCr; # undef OFMT if (omx_component_set_portdef(&omx->comp, &portdef) < 0) { return -1; } if (omx_component_get_portdef(&omx->comp, &portdef, _OUTPUT_PORT) < 0) { LOG_ERROR("... second"); return -1; } # define SET_PARAM(_key, _value) { \ if ((error = OMX_SetParameter(omx->comp, OMX_IndexParam##_key, _value)) != OMX_ErrorNone) { \ LOG_ERROR_OMX(error, "Can't set OMX param %s", #_key); \ return -1; \ } \ } OMX_CONFIG_BOOLEANTYPE exif; OMX_INIT_STRUCTURE(exif); exif.bEnabled = OMX_FALSE; SET_PARAM(BrcmDisableEXIF, &exif); OMX_PARAM_IJGSCALINGTYPE ijg; OMX_INIT_STRUCTURE(ijg); ijg.nPortIndex = _OUTPUT_PORT; ijg.bEnabled = OMX_TRUE; SET_PARAM(BrcmEnableIJGTableScaling, &ijg); OMX_IMAGE_PARAM_QFACTORTYPE qfactor; OMX_INIT_STRUCTURE(qfactor); qfactor.nPortIndex = _OUTPUT_PORT; qfactor.nQFactor = quality; SET_PARAM(QFactor, &qfactor); # undef SET_PARAM if (omx_component_enable_port(&omx->comp, _OUTPUT_PORT) < 0) { return -1; } omx->i_output_port_enabled = true; if ((error = OMX_AllocateBuffer(omx->comp, &omx->output_buf, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't allocate OMX JPEG output buffer"); return -1; } return 0; } static int _omx_encoder_clear_ports(omx_encoder_s *omx) { OMX_ERRORTYPE error; int retval = 0; if (omx->i_output_port_enabled) { retval -= omx_component_disable_port(&omx->comp, _OUTPUT_PORT); omx->i_output_port_enabled = false; } if (omx->i_input_port_enabled) { retval -= omx_component_disable_port(&omx->comp, _INPUT_PORT); omx->i_input_port_enabled = false; } if (omx->input_buf) { if ((error = OMX_FreeBuffer(omx->comp, _INPUT_PORT, omx->input_buf)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't free OMX JPEG input buffer"); // retval -= 1; } omx->input_buf = NULL; } if (omx->output_buf) { if ((error = OMX_FreeBuffer(omx->comp, _OUTPUT_PORT, omx->output_buf)) != OMX_ErrorNone) { LOG_ERROR_OMX(error, "Can't free OMX JPEG output buffer"); // retval -= 1; } omx->output_buf = NULL; } return retval; } static OMX_ERRORTYPE _omx_event_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, OMX_EVENTTYPE event, OMX_U32 data1, UNUSED OMX_U32 data2, UNUSED OMX_PTR event_data) { // OMX calls this handler for all the events it emits omx_encoder_s *omx = (omx_encoder_s *)v_omx; if (event == OMX_EventError) { LOG_ERROR_OMX((OMX_ERRORTYPE)data1, "OMX error event received"); omx->failed = true; assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS); } return OMX_ErrorNone; } static OMX_ERRORTYPE _omx_input_required_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) { // Called by OMX when the encoder component requires // the input buffer to be filled with RAW image data omx_encoder_s *omx = (omx_encoder_s *)v_omx; omx->input_required = true; assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS); return OMX_ErrorNone; } static OMX_ERRORTYPE _omx_output_available_handler( UNUSED OMX_HANDLETYPE comp, OMX_PTR v_omx, UNUSED OMX_BUFFERHEADERTYPE *buf) { // Called by OMX when the encoder component has filled // the output buffer with JPEG data omx_encoder_s *omx = (omx_encoder_s *)v_omx; omx->output_available = true; assert(vcos_semaphore_post(&omx->handler_sem) == VCOS_SUCCESS); return OMX_ErrorNone; } ustreamer-4.9/src/ustreamer/encoders/omx/encoder.h000066400000000000000000000053131414164060100223670ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include "../../../libs/tools.h" #include "../../../libs/logging.h" #include "../../../libs/frame.h" #include "vcos.h" #include "formatters.h" #include "component.h" #ifndef CFG_OMX_MAX_ENCODERS # define CFG_OMX_MAX_ENCODERS 3 // Raspberry Pi limitation #endif #define OMX_MAX_ENCODERS ((unsigned)(CFG_OMX_MAX_ENCODERS)) typedef struct { OMX_HANDLETYPE comp; OMX_BUFFERHEADERTYPE *input_buf; OMX_BUFFERHEADERTYPE *output_buf; bool input_required; bool output_available; bool failed; VCOS_SEMAPHORE_T handler_sem; bool i_handler_sem; bool i_encoder; bool i_input_port_enabled; bool i_output_port_enabled; } omx_encoder_s; omx_encoder_s *omx_encoder_init(void); void omx_encoder_destroy(omx_encoder_s *omx); int omx_encoder_prepare(omx_encoder_s *omx, const frame_s *frame, unsigned quality); int omx_encoder_compress(omx_encoder_s *omx, const frame_s *src, frame_s *dest); ustreamer-4.9/src/ustreamer/encoders/omx/formatters.c000066400000000000000000000102421414164060100231260ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "formatters.h" #define CASE_TO_STRING(_value) \ case _value: { return #_value; } const char *omx_error_to_string(OMX_ERRORTYPE error) { switch (error) { CASE_TO_STRING(OMX_ErrorNone); CASE_TO_STRING(OMX_ErrorInsufficientResources); CASE_TO_STRING(OMX_ErrorUndefined); CASE_TO_STRING(OMX_ErrorInvalidComponentName); CASE_TO_STRING(OMX_ErrorComponentNotFound); CASE_TO_STRING(OMX_ErrorInvalidComponent); CASE_TO_STRING(OMX_ErrorBadParameter); CASE_TO_STRING(OMX_ErrorNotImplemented); CASE_TO_STRING(OMX_ErrorUnderflow); CASE_TO_STRING(OMX_ErrorOverflow); CASE_TO_STRING(OMX_ErrorHardware); CASE_TO_STRING(OMX_ErrorInvalidState); CASE_TO_STRING(OMX_ErrorStreamCorrupt); CASE_TO_STRING(OMX_ErrorPortsNotCompatible); CASE_TO_STRING(OMX_ErrorResourcesLost); CASE_TO_STRING(OMX_ErrorNoMore); CASE_TO_STRING(OMX_ErrorVersionMismatch); CASE_TO_STRING(OMX_ErrorNotReady); CASE_TO_STRING(OMX_ErrorTimeout); CASE_TO_STRING(OMX_ErrorSameState); CASE_TO_STRING(OMX_ErrorResourcesPreempted); CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringAllocation); CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringDeallocation); CASE_TO_STRING(OMX_ErrorPortUnresponsiveDuringStop); CASE_TO_STRING(OMX_ErrorIncorrectStateTransition); CASE_TO_STRING(OMX_ErrorIncorrectStateOperation); CASE_TO_STRING(OMX_ErrorUnsupportedSetting); CASE_TO_STRING(OMX_ErrorUnsupportedIndex); CASE_TO_STRING(OMX_ErrorBadPortIndex); CASE_TO_STRING(OMX_ErrorPortUnpopulated); CASE_TO_STRING(OMX_ErrorComponentSuspended); CASE_TO_STRING(OMX_ErrorDynamicResourcesUnavailable); CASE_TO_STRING(OMX_ErrorMbErrorsInFrame); CASE_TO_STRING(OMX_ErrorFormatNotDetected); CASE_TO_STRING(OMX_ErrorContentPipeOpenFailed); CASE_TO_STRING(OMX_ErrorContentPipeCreationFailed); CASE_TO_STRING(OMX_ErrorSeperateTablesUsed); CASE_TO_STRING(OMX_ErrorTunnelingUnsupported); CASE_TO_STRING(OMX_ErrorKhronosExtensions); CASE_TO_STRING(OMX_ErrorVendorStartUnused); CASE_TO_STRING(OMX_ErrorDiskFull); CASE_TO_STRING(OMX_ErrorMaxFileSize); CASE_TO_STRING(OMX_ErrorDrmUnauthorised); CASE_TO_STRING(OMX_ErrorDrmExpired); CASE_TO_STRING(OMX_ErrorDrmGeneral); default: return "Unknown OMX error"; } } const char *omx_state_to_string(OMX_STATETYPE state) { switch (state) { CASE_TO_STRING(OMX_StateInvalid); CASE_TO_STRING(OMX_StateLoaded); CASE_TO_STRING(OMX_StateIdle); CASE_TO_STRING(OMX_StateExecuting); CASE_TO_STRING(OMX_StatePause); CASE_TO_STRING(OMX_StateWaitForResources); // cppcheck-suppress constArgument // cppcheck-suppress knownArgument default: break; } assert(0 && "Unsupported OMX state"); } #undef CASE_TO_STRING ustreamer-4.9/src/ustreamer/encoders/omx/formatters.h000066400000000000000000000037411414164060100231410ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include "../../../libs/tools.h" #include "../../../libs/logging.h" #define LOG_ERROR_OMX(_error, _msg, ...) { \ LOG_ERROR(_msg ": %s", ##__VA_ARGS__, omx_error_to_string(_error)); \ } const char *omx_error_to_string(OMX_ERRORTYPE error); const char *omx_state_to_string(OMX_STATETYPE state); ustreamer-4.9/src/ustreamer/encoders/omx/vcos.c000066400000000000000000000050601414164060100217140ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "vcos.h" int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout) { // vcos_semaphore_wait() can wait infinite // vcos_semaphore_wait_timeout() is broken by design: // - https://github.com/pikvm/ustreamer/issues/56 // - https://github.com/raspberrypi/userland/issues/658 // - The current approach is an ugly busyloop // Три стула. long double deadline_ts = get_now_monotonic() + timeout; VCOS_STATUS_T sem_status; while (true) { sem_status = vcos_semaphore_trywait(sem); if (sem_status == VCOS_SUCCESS) { return 0; } else if (sem_status != VCOS_EAGAIN || get_now_monotonic() > deadline_ts) { break; } if (usleep(1000) < 0) { break; } } switch (sem_status) { case VCOS_EAGAIN: LOG_ERROR("%sCan't wait VCOS semaphore: EAGAIN (timeout)", prefix); break; case VCOS_EINVAL: LOG_ERROR("%sCan't wait VCOS semaphore: EINVAL", prefix); break; default: LOG_ERROR("%sCan't wait VCOS semaphore: %d", prefix, sem_status); break; } return -1; } ustreamer-4.9/src/ustreamer/encoders/omx/vcos.h000066400000000000000000000034031414164060100217200ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include "../../../libs/tools.h" #include "../../../libs/logging.h" int vcos_my_semwait(const char *prefix, VCOS_SEMAPHORE_T *sem, long double timeout); ustreamer-4.9/src/ustreamer/gpio/000077500000000000000000000000001414164060100171265ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/gpio/gpio.c000066400000000000000000000103401414164060100202260ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "gpio.h" gpio_s us_gpio = { .path = "/dev/gpiochip0", .consumer_prefix = "ustreamer", # define MAKE_OUTPUT(_role) { \ .pin = -1, \ .role = _role, \ .consumer = NULL, \ .line = NULL, \ .state = false \ } .prog_running = MAKE_OUTPUT("prog-running"), .stream_online = MAKE_OUTPUT("stream-online"), .has_http_clients = MAKE_OUTPUT("has-http-clients"), # undef MAKE_OUTPUT // mutex uninitialized .chip = NULL }; static void _gpio_output_init(gpio_output_s *output); static void _gpio_output_destroy(gpio_output_s *output); void gpio_init(void) { assert(us_gpio.chip == NULL); if ( us_gpio.prog_running.pin >= 0 || us_gpio.stream_online.pin >= 0 || us_gpio.has_http_clients.pin >= 0 ) { A_MUTEX_INIT(&us_gpio.mutex); LOG_INFO("GPIO: Using chip device: %s", us_gpio.path); if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) { _gpio_output_init(&us_gpio.prog_running); _gpio_output_init(&us_gpio.stream_online); _gpio_output_init(&us_gpio.has_http_clients); } else { LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path); } } } void gpio_destroy(void) { _gpio_output_destroy(&us_gpio.prog_running); _gpio_output_destroy(&us_gpio.stream_online); _gpio_output_destroy(&us_gpio.has_http_clients); if (us_gpio.chip) { gpiod_chip_close(us_gpio.chip); us_gpio.chip = NULL; A_MUTEX_DESTROY(&us_gpio.mutex); } } int gpio_inner_set(gpio_output_s *output, bool state) { int retval = 0; assert(us_gpio.chip); assert(output->line); assert(output->state != state); // Must be checked in macro for the performance A_MUTEX_LOCK(&us_gpio.mutex); if (gpiod_line_set_value(output->line, (int)state) < 0) { \ LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \ _gpio_output_destroy(output); retval = -1; } A_MUTEX_UNLOCK(&us_gpio.mutex); return retval; } static void _gpio_output_init(gpio_output_s *output) { assert(us_gpio.chip); assert(output->line == NULL); A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role); if (output->pin >= 0) { if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) { if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) { LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); _gpio_output_destroy(output); } } else { LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer); } } } static void _gpio_output_destroy(gpio_output_s *output) { if (output->line) { gpiod_line_release(output->line); output->line = NULL; } if (output->consumer) { free(output->consumer); output->consumer = NULL; } output->state = false; } ustreamer-4.9/src/ustreamer/gpio/gpio.h000066400000000000000000000053301414164060100202360ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" #include "../../libs/threading.h" typedef struct { int pin; const char *role; char *consumer; struct gpiod_line *line; bool state; } gpio_output_s; typedef struct { char *path; char *consumer_prefix; gpio_output_s prog_running; gpio_output_s stream_online; gpio_output_s has_http_clients; pthread_mutex_t mutex; struct gpiod_chip *chip; } gpio_s; extern gpio_s us_gpio; void gpio_init(void); void gpio_destroy(void); int gpio_inner_set(gpio_output_s *output, bool state); #define SET_STATE(_output, _state) { \ if (_output.line && _output.state != _state) { \ if (!gpio_inner_set(&_output, _state)) { \ _output.state = _state; \ } \ } \ } INLINE void gpio_set_prog_running(bool state) { SET_STATE(us_gpio.prog_running, state); } INLINE void gpio_set_stream_online(bool state) { SET_STATE(us_gpio.stream_online, state); } INLINE void gpio_set_has_http_clients(bool state) { SET_STATE(us_gpio.has_http_clients, state); } #undef SET_STATE ustreamer-4.9/src/ustreamer/h264/000077500000000000000000000000001414164060100166535ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/h264/encoder.c000066400000000000000000000326641414164060100204510ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "encoder.h" static void _h264_encoder_cleanup(h264_encoder_s *enc); static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key); static void _mmal_callback(MMAL_WRAPPER_T *wrapper); static const char *_mmal_error_to_string(MMAL_STATUS_T error); #define LOG_ERROR_MMAL(_error, _msg, ...) { \ LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _mmal_error_to_string(_error)); \ } h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps) { LOG_INFO("H264: Initializing MMAL encoder ..."); LOG_INFO("H264: Using bitrate: %u Kbps", bitrate); LOG_INFO("H264: Using GOP: %u", gop); h264_encoder_s *enc; A_CALLOC(enc, 1); enc->bitrate = bitrate; // Kbps enc->gop = gop; // Interval between keyframes enc->fps = fps; enc->last_online = -1; if (vcos_semaphore_create(&enc->handler_sem, "h264_handler_sem", 0) != VCOS_SUCCESS) { LOG_PERROR("H264: Can't create VCOS semaphore"); goto error; } enc->i_handler_sem = true; MMAL_STATUS_T error = mmal_wrapper_create(&enc->wrapper, MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER); if (error != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't create MMAL wrapper"); enc->wrapper = NULL; goto error; } enc->wrapper->user_data = (void *)enc; enc->wrapper->callback = _mmal_callback; return enc; error: h264_encoder_destroy(enc); return NULL; } void h264_encoder_destroy(h264_encoder_s *enc) { LOG_INFO("H264: Destroying MMAL encoder ..."); _h264_encoder_cleanup(enc); if (enc->wrapper) { MMAL_STATUS_T error = mmal_wrapper_destroy(enc->wrapper); if (error != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't destroy MMAL encoder"); } } if (enc->i_handler_sem) { vcos_semaphore_delete(&enc->handler_sem); } free(enc); } bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) { # define EQ(_field) (enc->_field == frame->_field) return (EQ(width) && EQ(height) && EQ(format) && EQ(stride) && (enc->zero_copy == zero_copy)); # undef EQ } int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy) { LOG_INFO("H264: Configuring MMAL encoder: zero_copy=%d ...", zero_copy); _h264_encoder_cleanup(enc); enc->width = frame->width; enc->height = frame->height; enc->format = frame->format; enc->stride = frame->stride; enc->zero_copy = zero_copy; if (align_size(frame->width, 32) != frame->width && frame_get_padding(frame) == 0) { LOG_ERROR("H264: MMAL encoder can't handle unaligned width"); goto error; } MMAL_STATUS_T error; # define PREPARE_PORT(_id) { \ enc->_id##_port = enc->wrapper->_id[0]; \ if (enc->_id##_port->is_enabled) { \ if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \ LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port while configuring", #_id); \ goto error; \ } \ } \ } # define COMMIT_PORT(_id) { \ if ((error = mmal_port_format_commit(enc->_id##_port)) != MMAL_SUCCESS) { \ LOG_ERROR_MMAL(error, "H264: Can't commit MMAL %s port", #_id); \ goto error; \ } \ } # define SET_PORT_PARAM(_id, _type, _key, _value) { \ if ((error = mmal_port_parameter_set_##_type(enc->_id##_port, MMAL_PARAMETER_##_key, _value)) != MMAL_SUCCESS) { \ LOG_ERROR_MMAL(error, "H264: Can't set %s for the %s port", #_key, #_id); \ goto error; \ } \ } # define ENABLE_PORT(_id) { \ if ((error = mmal_wrapper_port_enable(enc->_id##_port, MMAL_WRAPPER_FLAG_PAYLOAD_ALLOCATE)) != MMAL_SUCCESS) { \ LOG_ERROR_MMAL(error, "H264: Can't enable MMAL %s port", #_id); \ goto error; \ } \ } { PREPARE_PORT(input); # define IFMT(_next) enc->input_port->format->_next IFMT(type) = MMAL_ES_TYPE_VIDEO; switch (frame->format) { case V4L2_PIX_FMT_YUYV: IFMT(encoding) = MMAL_ENCODING_YUYV; break; case V4L2_PIX_FMT_UYVY: IFMT(encoding) = MMAL_ENCODING_UYVY; break; case V4L2_PIX_FMT_RGB565: IFMT(encoding) = MMAL_ENCODING_RGB16; break; case V4L2_PIX_FMT_RGB24: IFMT(encoding) = MMAL_ENCODING_RGB24; break; default: assert(0 && "Unsupported pixelformat"); } IFMT(es->video.width) = align_size(frame->width, 32); IFMT(es->video.height) = align_size(frame->height, 16); IFMT(es->video.crop.x) = 0; IFMT(es->video.crop.y) = 0; IFMT(es->video.crop.width) = frame->width; IFMT(es->video.crop.height) = frame->height; IFMT(flags) = MMAL_ES_FORMAT_FLAG_FRAMED; enc->input_port->buffer_size = 1000 * 1000; enc->input_port->buffer_num = enc->input_port->buffer_num_recommended * 4; # undef IFMT COMMIT_PORT(input); SET_PORT_PARAM(input, boolean, ZERO_COPY, zero_copy); } { PREPARE_PORT(output); # define OFMT(_next) enc->output_port->format->_next OFMT(type) = MMAL_ES_TYPE_VIDEO; OFMT(encoding) = MMAL_ENCODING_H264; OFMT(encoding_variant) = MMAL_ENCODING_VARIANT_H264_DEFAULT; OFMT(bitrate) = enc->bitrate * 1000; OFMT(es->video.frame_rate.num) = enc->fps; OFMT(es->video.frame_rate.den) = 1; enc->output_port->buffer_size = enc->output_port->buffer_size_recommended * 4; enc->output_port->buffer_num = enc->output_port->buffer_num_recommended; # undef OFMT COMMIT_PORT(output); { MMAL_PARAMETER_VIDEO_PROFILE_T profile; MEMSET_ZERO(profile); profile.hdr.id = MMAL_PARAMETER_PROFILE; profile.hdr.size = sizeof(profile); // http://blog.mediacoderhq.com/h264-profiles-and-levels profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE; profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // Supports 1080p if ((error = mmal_port_parameter_set(enc->output_port, &profile.hdr)) != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't set MMAL_PARAMETER_PROFILE for the output port"); goto error; } } SET_PORT_PARAM(output, boolean, ZERO_COPY, MMAL_TRUE); SET_PORT_PARAM(output, uint32, INTRAPERIOD, enc->gop); SET_PORT_PARAM(output, uint32, NALUNITFORMAT, MMAL_VIDEO_NALUNITFORMAT_STARTCODES); SET_PORT_PARAM(output, boolean, MINIMISE_FRAGMENTATION, MMAL_TRUE); SET_PORT_PARAM(output, uint32, MB_ROWS_PER_SLICE, 0); SET_PORT_PARAM(output, boolean, VIDEO_IMMUTABLE_INPUT, MMAL_TRUE); SET_PORT_PARAM(output, boolean, VIDEO_DROPPABLE_PFRAMES, MMAL_FALSE); SET_PORT_PARAM(output, boolean, VIDEO_ENCODE_INLINE_HEADER, MMAL_TRUE); // SPS/PPS: https://github.com/raspberrypi/userland/issues/443 SET_PORT_PARAM(output, uint32, VIDEO_BIT_RATE, enc->bitrate * 1000); SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_PEAK_RATE, enc->bitrate * 1000); SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MIN_QUANT, 16); SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_MAX_QUANT, 34); // Этот параметр с этим значением фризит кодирование изображения из черно-белой консоли // SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000); SET_PORT_PARAM(output, uint32, VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE); } ENABLE_PORT(input); ENABLE_PORT(output); enc->ready = true; return 0; error: _h264_encoder_cleanup(enc); LOG_ERROR("H264: Encoder destroyed due an error (prepare)"); return -1; # undef ENABLE_PORT # undef SET_PORT_PARAM # undef COMMIT_PORT # undef PREPARE_PORT } static void _h264_encoder_cleanup(h264_encoder_s *enc) { MMAL_STATUS_T error; # define DISABLE_PORT(_id) { \ if (enc->_id##_port) { \ if ((error = mmal_wrapper_port_disable(enc->_id##_port)) != MMAL_SUCCESS) { \ LOG_ERROR_MMAL(error, "H264: Can't disable MMAL %s port", #_id); \ } \ enc->_id##_port = NULL; \ } \ } DISABLE_PORT(input); DISABLE_PORT(output); # undef DISABLE_PORT if (enc->wrapper) { enc->wrapper->status = MMAL_SUCCESS; // Это реально надо? } enc->last_online = -1; enc->ready = false; } int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) { assert(enc->ready); assert(src->used > 0); assert(enc->width == src->width); assert(enc->height == src->height); assert(enc->format == src->format); assert(enc->stride == src->stride); frame_copy_meta(src, dest); dest->encode_begin_ts = get_now_monotonic(); dest->format = V4L2_PIX_FMT_H264; dest->stride = 0; force_key = (force_key || enc->last_online != src->online); if (_h264_encoder_compress_raw(enc, src, src_vcsm_handle, dest, force_key) < 0) { _h264_encoder_cleanup(enc); LOG_ERROR("H264: Encoder destroyed due an error (compress)"); return -1; } dest->encode_end_ts = get_now_monotonic(); LOG_VERBOSE("H264: Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d", dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key); enc->last_online = src->online; return 0; } static int _h264_encoder_compress_raw(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key) { LOG_DEBUG("H264: Compressing new frame; force_key=%d ...", force_key); MMAL_STATUS_T error; if (force_key) { if ((error = mmal_port_parameter_set_boolean( enc->output_port, MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, MMAL_TRUE )) != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't request keyframe"); return -1; } } MMAL_BUFFER_HEADER_T *out = NULL; MMAL_BUFFER_HEADER_T *in = NULL; bool eos = false; bool sent = false; dest->used = 0; while (!eos) { out = NULL; while (mmal_wrapper_buffer_get_empty(enc->output_port, &out, 0) == MMAL_SUCCESS) { if ((error = mmal_port_send_buffer(enc->output_port, out)) != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't send MMAL output buffer"); return -1; } } in = NULL; if (!sent && mmal_wrapper_buffer_get_empty(enc->input_port, &in, 0) == MMAL_SUCCESS) { if (enc->zero_copy && src_vcsm_handle > 0) { in->data = (uint8_t *)vcsm_vc_hdl_from_hdl(src_vcsm_handle); } else { in->data = src->data; } in->alloc_size = src->used; in->length = src->used; in->offset = 0; in->flags = MMAL_BUFFER_HEADER_FLAG_EOS; if ((error = mmal_port_send_buffer(enc->input_port, in)) != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't send MMAL input buffer"); return -1; } sent = true; } error = mmal_wrapper_buffer_get_full(enc->output_port, &out, 0); if (error == MMAL_EAGAIN) { if (vcos_my_semwait("H264: ", &enc->handler_sem, 1) < 0) { return -1; } continue; } else if (error != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't get MMAL output buffer"); return -1; } frame_append_data(dest, out->data, out->length); dest->key = out->flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME; eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS; mmal_buffer_header_release(out); } if ((error = mmal_port_flush(enc->output_port)) != MMAL_SUCCESS) { LOG_ERROR_MMAL(error, "H264: Can't flush MMAL output buffer; ignored"); } return 0; } static void _mmal_callback(MMAL_WRAPPER_T *wrapper) { vcos_semaphore_post(&((h264_encoder_s *)(wrapper->user_data))->handler_sem); } static const char *_mmal_error_to_string(MMAL_STATUS_T error) { // http://www.jvcref.com/files/PI/documentation/html/group___mmal_types.html # define CASE_ERROR(_name, _msg) case MMAL_##_name: return "MMAL_" #_name " [" _msg "]" switch (error) { case MMAL_SUCCESS: return "MMAL_SUCCESS"; CASE_ERROR(ENOMEM, "Out of memory"); CASE_ERROR(ENOSPC, "Out of resources"); CASE_ERROR(EINVAL, "Invalid argument"); CASE_ERROR(ENOSYS, "Function not implemented"); CASE_ERROR(ENOENT, "No such file or directory"); CASE_ERROR(ENXIO, "No such device or address"); CASE_ERROR(EIO, "IO error"); CASE_ERROR(ESPIPE, "Illegal seek"); CASE_ERROR(ECORRUPT, "Data is corrupt"); CASE_ERROR(ENOTREADY, "Component is not ready"); CASE_ERROR(ECONFIG, "Component is not configured"); CASE_ERROR(EISCONN, "Port is already connected"); CASE_ERROR(ENOTCONN, "Port is disconnected"); CASE_ERROR(EAGAIN, "Resource temporarily unavailable"); CASE_ERROR(EFAULT, "Bad address"); case MMAL_STATUS_MAX: break; // Makes cpplint happy } return "Unknown error"; # undef CASE_ERROR } #undef LOG_ERROR_MMAL ustreamer-4.9/src/ustreamer/h264/encoder.h000066400000000000000000000055431414164060100204520ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" #include "../../libs/frame.h" #include "../encoders/omx/vcos.h" typedef struct { unsigned bitrate; // Kbit-per-sec unsigned gop; // Interval between keyframes unsigned fps; MMAL_WRAPPER_T *wrapper; MMAL_PORT_T *input_port; MMAL_PORT_T *output_port; VCOS_SEMAPHORE_T handler_sem; bool i_handler_sem; int last_online; unsigned width; unsigned height; unsigned format; unsigned stride; bool zero_copy; bool ready; } h264_encoder_s; h264_encoder_s *h264_encoder_init(unsigned bitrate, unsigned gop, unsigned fps); void h264_encoder_destroy(h264_encoder_s *enc); bool h264_encoder_is_prepared_for(h264_encoder_s *enc, const frame_s *frame, bool zero_copy); int h264_encoder_prepare(h264_encoder_s *enc, const frame_s *frame, bool zero_copy); int h264_encoder_compress(h264_encoder_s *enc, const frame_s *src, int src_vcsm_handle, frame_s *dest, bool force_key); ustreamer-4.9/src/ustreamer/h264/stream.c000066400000000000000000000072111414164060100203130ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "stream.h" h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop) { h264_stream_s *h264; A_CALLOC(h264, 1); h264->sink = sink; h264->tmp_src = frame_init(); h264->dest = frame_init(); atomic_init(&h264->online, false); // FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100 // По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480 // енкодер через несколько секунд перестает производить корректные фреймы. if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) { goto error; } return h264; error: h264_stream_destroy(h264); return NULL; } void h264_stream_destroy(h264_stream_s *h264) { if (h264->enc) { h264_encoder_destroy(h264->enc); } frame_destroy(h264->dest); frame_destroy(h264->tmp_src); free(h264); } void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key) { if (!memsink_server_check(h264->sink, frame)) { return; } long double now = get_now_monotonic(); bool zero_copy = false; if (is_jpeg(frame->format)) { assert(vcsm_handle <= 0); LOG_DEBUG("H264: Input frame is JPEG; decoding ..."); if (unjpeg(frame, h264->tmp_src, true) < 0) { return; } frame = h264->tmp_src; LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now); } else if (vcsm_handle > 0) { LOG_DEBUG("H264: Zero-copy available for the input"); zero_copy = true; } else { LOG_DEBUG("H264: Copying source to tmp buffer ..."); frame_copy(frame, h264->tmp_src); frame = h264->tmp_src; LOG_VERBOSE("H264: Source copied; time=%.3Lf", get_now_monotonic() - now); } bool online = false; if (!h264_encoder_is_prepared_for(h264->enc, frame, zero_copy)) { h264_encoder_prepare(h264->enc, frame, zero_copy); } if (h264->enc->ready) { if (h264_encoder_compress(h264->enc, frame, vcsm_handle, h264->dest, force_key) == 0) { online = !memsink_server_put(h264->sink, h264->dest); } } atomic_store(&h264->online, online); } ustreamer-4.9/src/ustreamer/h264/stream.h000066400000000000000000000042411414164060100203200ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" #include "../../libs/frame.h" #include "../../libs/memsink.h" #include "../../libs/unjpeg.h" #include "encoder.h" typedef struct { memsink_s *sink; frame_s *tmp_src; frame_s *dest; h264_encoder_s *enc; atomic_bool online; } h264_stream_s; h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop); void h264_stream_destroy(h264_stream_s *h264); void h264_stream_process(h264_stream_s *h264, const frame_s *frame, int vcsm_handle, bool force_key); ustreamer-4.9/src/ustreamer/http/000077500000000000000000000000001414164060100171475ustar00rootroot00000000000000ustreamer-4.9/src/ustreamer/http/bev.c000066400000000000000000000045741414164060100201010ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "bev.h" char *bufferevent_my_format_reason(short what) { char *reason; A_CALLOC(reason, 2048); char perror_buf[1024] = {0}; char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-safe bool first = true; strcat(reason, perror_ptr); strcat(reason, " ("); # define FILL_REASON(_bev, _name) { \ if (what & _bev) { \ if (first) { \ first = false; \ } else { \ strcat(reason, ","); \ } \ strcat(reason, _name); \ } \ } FILL_REASON(BEV_EVENT_READING, "reading"); FILL_REASON(BEV_EVENT_WRITING, "writing"); FILL_REASON(BEV_EVENT_ERROR, "error"); FILL_REASON(BEV_EVENT_TIMEOUT, "timeout"); FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable # undef FILL_REASON strcat(reason, ")"); return reason; } ustreamer-4.9/src/ustreamer/http/bev.h000066400000000000000000000034161414164060100201000ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" char *bufferevent_my_format_reason(short what); ustreamer-4.9/src/ustreamer/http/mime.c000066400000000000000000000050461414164060100202470ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "mime.h" static const struct { const char *ext; const char *mime; } _MIME_TYPES[] = { {"html", "text/html"}, {"htm", "text/html"}, {"css", "text/css"}, {"js", "text/javascript"}, {"txt", "text/plain"}, {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"png", "image/png"}, {"gif", "image/gif"}, {"ico", "image/x-icon"}, {"bmp", "image/bmp"}, {"svg", "image/svg+xml"}, {"swf", "application/x-shockwave-flash"}, {"cab", "application/x-shockwave-flash"}, {"jar", "application/java-archive"}, {"json", "application/json"}, }; const char *guess_mime_type(const char *path) { // FIXME: false-positive cppcheck char *dot = strrchr(path, '.'); // cppcheck-suppress ctunullpointer if (dot == NULL || strchr(dot, '/') != NULL) { goto misc; } char *ext = dot + 1; for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) { if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) { return _MIME_TYPES[index].mime; } } misc: return "application/misc"; } ustreamer-4.9/src/ustreamer/http/mime.h000066400000000000000000000032711414164060100202520ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include "../../libs/tools.h" const char *guess_mime_type(const char *str); ustreamer-4.9/src/ustreamer/http/path.c000066400000000000000000000133761414164060100202610ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "path.h" char *simplify_request_path(const char *str) { // Based on Lighttpd sources: // - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840 // - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c char ch; // Current character char pre1; // The one before char pre2; // The one before that char *simplified; char *start; char *out; char *slash; A_CALLOC(simplified, strlen(str) + 1); if (str[0] == '\0') { simplified[0] = '\0'; return simplified; } start = simplified; out = simplified; slash = simplified; // Skip leading spaces for (; *str == ' '; ++str); if (*str == '.') { if (str[1] == '/' || str[1] == '\0') { ++str; } else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) { str += 2; } } pre1 = '\0'; ch = *(str++); while (ch != '\0') { pre2 = pre1; pre1 = ch; // Possibly: out == str - need to read first ch = *str; *out = pre1; out++; str++; // (out <= str) still true; also now (slash < out) if (ch == '/' || ch == '\0') { size_t toklen = out - slash; if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') { // "/../" or ("/.." at end of string) out = slash; // If there is something before "/..", there is at least one // component, which needs to be removed if (out > start) { --out; for (; out > start && *out != '/'; --out); } // Don't kill trailing '/' at end of path if (ch == '\0') { ++out; } // slash < out before, so out_new <= slash + 1 <= out_before <= str } else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) { // "//" or "/./" or (("/" or "/.") at end of string) out = slash; // Don't kill trailing '/' at end of path if (ch == '\0') { ++out; } // Slash < out before, so out_new <= slash + 1 <= out_before <= str } slash = out; } } *out = '\0'; return simplified; } #ifdef TEST_HTTP_PATH int test_simplify_request_path(const char *sample, const char *expected) { char *result = simplify_request_path(sample); int retval = -!!strcmp(result, expected); printf("Testing '%s' -> '%s' ... ", sample, expected); if (retval == 0) { printf("ok\n"); } else { printf("FAILED; got '%s'\n", result); } free(result); return retval; } int main(void) { int retval = 0; # define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \ retval += test_simplify_request_path(_sample, _expected); \ } TEST_SIMPLIFY_REQUEST_PATH("", ""); TEST_SIMPLIFY_REQUEST_PATH(" ", ""); TEST_SIMPLIFY_REQUEST_PATH("/", "/"); TEST_SIMPLIFY_REQUEST_PATH("//", "/"); TEST_SIMPLIFY_REQUEST_PATH("abc", "abc"); TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/"); TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/"); TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/"); TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/"); TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/"); TEST_SIMPLIFY_REQUEST_PATH(".", ""); TEST_SIMPLIFY_REQUEST_PATH("..", ""); TEST_SIMPLIFY_REQUEST_PATH("...", "..."); TEST_SIMPLIFY_REQUEST_PATH("....", "...."); TEST_SIMPLIFY_REQUEST_PATH(".../", ".../"); TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" ../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" /../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" /foo/bar/../../../etc/passwd", "/etc/passwd"); # undef TEST_SIMPLIFY_REQUEST_PATH if (retval < 0) { printf("===== TEST FAILED =====\n"); } return retval; } #endif ustreamer-4.9/src/ustreamer/http/path.h000066400000000000000000000033451414164060100202610ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #ifdef TEST_HTTP_PATH # include # include #endif #include #include "../../libs/tools.h" char *simplify_request_path(const char *str); ustreamer-4.9/src/ustreamer/http/server.c000066400000000000000000000666331414164060100206370ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "server.h" static int _http_preprocess_request(struct evhttp_request *request, server_s *server); static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server); static void _http_callback_root(struct evhttp_request *request, void *v_server); static void _http_callback_static(struct evhttp_request *request, void *v_server); static void _http_callback_state(struct evhttp_request *request, void *v_server); static void _http_callback_snapshot(struct evhttp_request *request, void *v_server); static void _http_callback_stream(struct evhttp_request *request, void *v_server); static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx); static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx); static void _http_exposed_refresh(int fd, short event, void *v_server); static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated); static bool _expose_new_frame(server_s *server); static char *_http_get_client_hostport(struct evhttp_request *request); #define RUN(_next) server->run->_next #define STREAM(_next) RUN(stream->_next) #define VID(_next) STREAM(run->video->_next) #define EX(_next) RUN(exposed->_next) server_s *server_init(stream_s *stream) { exposed_s *exposed; A_CALLOC(exposed, 1); exposed->frame = frame_init(); server_runtime_s *run; A_CALLOC(run, 1); run->stream = stream; run->exposed = exposed; server_s *server; A_CALLOC(server, 1); server->host = "127.0.0.1"; server->port = 8080; server->unix_path = ""; server->user = ""; server->passwd = ""; server->static_path = ""; server->allow_origin = ""; server->timeout = 10; server->run = run; assert(!evthread_use_pthreads()); assert((run->base = event_base_new())); assert((run->http = evhttp_new(run->base))); evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD); return server; } void server_destroy(server_s *server) { if (RUN(refresh)) { event_del(RUN(refresh)); event_free(RUN(refresh)); } evhttp_free(RUN(http)); if (RUN(unix_fd)) { close(RUN(unix_fd)); } event_base_free(RUN(base)); # if LIBEVENT_VERSION_NUMBER >= 0x02010100 libevent_global_shutdown(); # endif LIST_ITERATE(RUN(stream_clients), client, { free(client->key); free(client->hostport); free(client); }); if (RUN(auth_token)) { free(RUN(auth_token)); } frame_destroy(EX(frame)); free(RUN(exposed)); free(server->run); free(server); } int server_listen(server_s *server) { { if (server->static_path[0] != '\0') { LOG_INFO("Enabling HTTP file server: %s", server->static_path); evhttp_set_gencb(RUN(http), _http_callback_static, (void *)server); } else { assert(!evhttp_set_cb(RUN(http), "/", _http_callback_root, (void *)server)); } assert(!evhttp_set_cb(RUN(http), "/state", _http_callback_state, (void *)server)); assert(!evhttp_set_cb(RUN(http), "/snapshot", _http_callback_snapshot, (void *)server)); assert(!evhttp_set_cb(RUN(http), "/stream", _http_callback_stream, (void *)server)); } frame_copy(STREAM(blank), EX(frame)); EX(notify_last_width) = EX(frame->width); EX(notify_last_height) = EX(frame->height); { struct timeval refresh_interval; refresh_interval.tv_sec = 0; if (STREAM(dev->desired_fps) > 0) { refresh_interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2); } else { refresh_interval.tv_usec = 16000; // ~60fps } assert((RUN(refresh) = event_new(RUN(base), -1, EV_PERSIST, _http_exposed_refresh, server))); assert(!event_add(RUN(refresh), &refresh_interval)); } evhttp_set_timeout(RUN(http), server->timeout); if (server->user[0] != '\0') { char *encoded_token = NULL; char *raw_token; A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd); base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL); free(raw_token); A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token); free(encoded_token); LOG_INFO("Using HTTP basic auth"); } if (server->unix_path[0] != '\0') { LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path); if ((RUN(unix_fd) = evhttp_my_bind_unix( RUN(http), server->unix_path, server->unix_rm, server->unix_mode)) < 0 ) { return -1; } LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path); if (server->tcp_nodelay) { LOG_ERROR("TCP_NODELAY flag can't be used with UNIX socket and will be ignored"); } } else { LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port); if (evhttp_bind_socket(RUN(http), server->host, server->port) < 0) { LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port) return -1; } LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port); } return 0; } void server_loop(server_s *server) { LOG_INFO("Starting HTTP eventloop ..."); event_base_dispatch(RUN(base)); LOG_INFO("HTTP eventloop stopped"); } void server_loop_break(server_s *server) { event_base_loopbreak(RUN(base)); } #define ADD_HEADER(_key, _value) \ assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value)) static int _http_preprocess_request(struct evhttp_request *request, server_s *server) { if (RUN(auth_token)) { const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization"); if (token == NULL || strcmp(token, RUN(auth_token)) != 0) { ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\""); evhttp_send_reply(request, 401, "Unauthorized", NULL); return -1; } } if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \ evhttp_send_reply(request, HTTP_OK, "OK", NULL); \ return -1; } return 0; } #define PREPROCESS_REQUEST { \ if (_http_preprocess_request(request, server) < 0) { \ return; \ } \ } static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) { // MJPG-Streamer compatibility layer struct evkeyvalq params; int error = 0; evhttp_parse_query(evhttp_request_get_uri(request), ¶ms); const char *action = evhttp_find_header(¶ms, "action"); if (action && !strcmp(action, "snapshot")) { _http_callback_snapshot(request, v_server); goto ok; } else if (action && !strcmp(action, "stream")) { _http_callback_stream(request, v_server); goto ok; } error = -1; ok: evhttp_clear_headers(¶ms); return error; } #define COMPAT_REQUEST { \ if (_http_check_run_compat_action(request, v_server) == 0) { \ return; \ } \ } static void _http_callback_root(struct evhttp_request *request, void *v_server) { server_s *server = (server_s *)v_server; PREPROCESS_REQUEST; COMPAT_REQUEST; struct evbuffer *buf; assert((buf = evbuffer_new())); assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE)); ADD_HEADER("Content-Type", "text/html"); evhttp_send_reply(request, HTTP_OK, "OK", buf); evbuffer_free(buf); } static void _http_callback_static(struct evhttp_request *request, void *v_server) { server_s *server = (server_s *)v_server; PREPROCESS_REQUEST; COMPAT_REQUEST; struct evbuffer *buf = NULL; struct evhttp_uri *uri = NULL; char *decoded_path = NULL; char *static_path = NULL; int fd = -1; { char *uri_path; if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) { goto bad_request; } if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) { uri_path = "/"; } if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) { goto bad_request; } } assert((buf = evbuffer_new())); if ((static_path = find_static_file_path(server->static_path, decoded_path)) == NULL) { goto not_found; } if ((fd = open(static_path, O_RDONLY)) < 0) { LOG_PERROR("HTTP: Can't open found static file %s", static_path); goto not_found; } { struct stat st; if (fstat(fd, &st) < 0) { LOG_PERROR("HTTP: Can't stat() found static file %s", static_path); goto not_found; } if (st.st_size > 0 && evbuffer_add_file(buf, fd, 0, st.st_size) < 0) { LOG_ERROR("HTTP: Can't serve static file %s", static_path); goto not_found; } ADD_HEADER("Content-Type", guess_mime_type(static_path)); evhttp_send_reply(request, HTTP_OK, "OK", buf); goto cleanup; } bad_request: evhttp_send_error(request, HTTP_BADREQUEST, NULL); goto cleanup; not_found: evhttp_send_error(request, HTTP_NOTFOUND, NULL); goto cleanup; cleanup: if (fd >= 0) { close(fd); } if (static_path) { free(static_path); } if (buf) { evbuffer_free(buf); } if (decoded_path) { free(decoded_path); } if (uri) { evhttp_uri_free(uri); } } #undef COMPAT_REQUEST static void _http_callback_state(struct evhttp_request *request, void *v_server) { server_s *server = (server_s *)v_server; PREPROCESS_REQUEST; encoder_type_e enc_type; unsigned enc_quality; encoder_get_runtime_params(STREAM(enc), &enc_type, &enc_quality); struct evbuffer *buf; assert((buf = evbuffer_new())); assert(evbuffer_add_printf(buf, "{\"ok\": true, \"result\": {" " \"encoder\": {\"type\": \"%s\", \"quality\": %u},", encoder_type_to_string(enc_type), enc_quality )); # ifdef WITH_OMX if (STREAM(run->h264)) { assert(evbuffer_add_printf(buf, " \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},", STREAM(h264_bitrate), STREAM(h264_gop), bool_to_string(atomic_load(&STREAM(run->h264->online))) )); } # endif if ( STREAM(sink) # ifdef WITH_OMX || STREAM(h264_sink) # endif ) { assert(evbuffer_add_printf(buf, " \"sinks\": {")); if (STREAM(sink)) { assert(evbuffer_add_printf(buf, "\"jpeg\": {\"has_clients\": %s}", bool_to_string(atomic_load(&STREAM(sink->has_clients))) )); } # ifdef WITH_OMX if (STREAM(h264_sink)) { assert(evbuffer_add_printf(buf, "%s\"h264\": {\"has_clients\": %s}", (STREAM(sink) ? ", " : ""), bool_to_string(atomic_load(&STREAM(h264_sink->has_clients))) )); } # endif assert(evbuffer_add_printf(buf, "},")); } assert(evbuffer_add_printf(buf, " \"source\": {\"resolution\": {\"width\": %u, \"height\": %u}," " \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u}," " \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {", (server->fake_width ? server->fake_width : EX(frame->width)), (server->fake_height ? server->fake_height : EX(frame->height)), bool_to_string(EX(frame->online)), STREAM(dev->desired_fps), EX(captured_fps), EX(queued_fps), RUN(stream_clients_count) )); LIST_ITERATE(RUN(stream_clients), client, { assert(evbuffer_add_printf(buf, "\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s," " \"dual_final_frames\": %s, \"zero_data\": %s}%s", client->id, client->fps, bool_to_string(client->extra_headers), bool_to_string(client->advance_headers), bool_to_string(client->dual_final_frames), bool_to_string(client->zero_data), (client->next ? ", " : "") )); }); assert(evbuffer_add_printf(buf, "}}}}")); ADD_HEADER("Content-Type", "application/json"); evhttp_send_reply(request, HTTP_OK, "OK", buf); evbuffer_free(buf); } static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) { server_s *server = (server_s *)v_server; PREPROCESS_REQUEST; struct evbuffer *buf; assert((buf = evbuffer_new())); assert(!evbuffer_add(buf, (const void *)EX(frame->data), EX(frame->used))); if (server->allow_origin[0] != '\0') { ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin); } ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0"); ADD_HEADER("Pragma", "no-cache"); ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT"); char header_buf[256]; # define ADD_TIME_HEADER(_key, _value) { \ snprintf(header_buf, 255, "%.06Lf", _value); \ ADD_HEADER(_key, header_buf); \ } # define ADD_UNSIGNED_HEADER(_key, _value) { \ snprintf(header_buf, 255, "%u", _value); \ ADD_HEADER(_key, header_buf); \ } ADD_TIME_HEADER("X-Timestamp", get_now_real()); ADD_HEADER("X-UStreamer-Online", bool_to_string(EX(frame->online))); ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EX(dropped)); ADD_UNSIGNED_HEADER("X-UStreamer-Width", EX(frame->width)); ADD_UNSIGNED_HEADER("X-UStreamer-Height", EX(frame->height)); ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", EX(frame->grab_ts)); ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", EX(frame->encode_begin_ts)); ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", EX(frame->encode_end_ts)); ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", EX(expose_begin_ts)); ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", EX(expose_cmp_ts)); ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", EX(expose_end_ts)); ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", get_now_monotonic()); # undef ADD_UNSUGNED_HEADER # undef ADD_TIME_HEADER ADD_HEADER("Content-Type", "image/jpeg"); evhttp_send_reply(request, HTTP_OK, "OK", buf); evbuffer_free(buf); } #undef ADD_HEADER static void _http_callback_stream(struct evhttp_request *request, void *v_server) { // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L791 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458 server_s *server = (server_s *)v_server; PREPROCESS_REQUEST; struct evhttp_connection *conn; conn = evhttp_request_get_connection(request); if (conn) { stream_client_s *client; A_CALLOC(client, 1); client->server = server; client->request = request; client->need_initial = true; client->need_first_frame = true; struct evkeyvalq params; evhttp_parse_query(evhttp_request_get_uri(request), ¶ms); # define PARSE_PARAM(_type, _name) client->_name = uri_get_##_type(¶ms, #_name) PARSE_PARAM(string, key); PARSE_PARAM(true, extra_headers); PARSE_PARAM(true, advance_headers); PARSE_PARAM(true, dual_final_frames); PARSE_PARAM(true, zero_data); # undef PARSE_PARAM evhttp_clear_headers(¶ms); client->hostport = _http_get_client_hostport(request); client->id = get_now_id(); LIST_APPEND_C(RUN(stream_clients), client, RUN(stream_clients_count)); if (RUN(stream_clients_count) == 1) { atomic_store(&VID(has_clients), true); # ifdef WITH_GPIO gpio_set_has_http_clients(true); # endif } LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u", client->hostport, client->id, RUN(stream_clients_count)); struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn); if (server->tcp_nodelay && !RUN(unix_fd)) { evutil_socket_t fd; int on = 1; LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport); assert((fd = bufferevent_getfd(buf_event)) >= 0); if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) { LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client %s", client->hostport); } } bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client); bufferevent_enable(buf_event, EV_READ); } else { evhttp_request_free(request); } } #undef PREPROCESS_REQUEST static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) { # define BOUNDARY "boundarydonotcross" stream_client_s *client = (stream_client_s *)v_client; server_s *server = client->server; long double now = get_now_monotonic(); long long now_second = floor_ms(now); if (now_second != client->fps_accum_second) { client->fps = client->fps_accum; client->fps_accum = 0; client->fps_accum_second = now_second; } client->fps_accum += 1; struct evbuffer *buf; assert((buf = evbuffer_new())); // В хроме и его производных есть фундаментальный баг: он отрисовывает // фрейм с задержкой на один, как только ему придут заголовки следующего. // В сочетании с drop_same_frames это дает значительный лаг стрима // при большом количестве дропов (на статичном изображении, где внезапно // что-то изменилось. // // https://bugs.chromium.org/p/chromium/issues/detail?id=527446 // // Включение advance_headers заставляет стример отсылать заголовки // будущего фрейма сразу после данных текущего, чтобы триггернуть отрисовку. // Естественным следствием этого является невозможность установки заголовка // Content-Length, так как предсказывать будущее мы еще не научились. // Его наличие не требуется RFC, однако никаких стандартов на MJPG over HTTP // в природе не существует, и никто не может гарантировать, что отсутствие // Content-Length не сломает вещание для каких-нибудь маргинальных браузеров. // // Кроме того, advance_headers форсит отключение заголовков X-UStreamer-* // по тем же причинам, по которым у нас нет Content-Length. # define ADD_ADVANCE_HEADERS \ assert(evbuffer_add_printf(buf, \ "Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real())) if (client->need_initial) { assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN)); if (client->server->allow_origin[0] != '\0') { assert(evbuffer_add_printf(buf, "Access-Control-Allow-Origin: %s" RN, client->server->allow_origin)); } assert(evbuffer_add_printf(buf, "Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN "Pragma: no-cache" RN "Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN "Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN RN "--" BOUNDARY RN, (client->key != NULL ? client->key : "0"), client->id )); if (client->advance_headers) { ADD_ADVANCE_HEADERS; } assert(!bufferevent_write_buffer(buf_event, buf)); client->need_initial = false; } if (!client->advance_headers) { assert(evbuffer_add_printf(buf, "Content-Type: image/jpeg" RN "Content-Length: %zu" RN "X-Timestamp: %.06Lf" RN "%s", (!client->zero_data ? EX(frame->used) : 0), get_now_real(), (client->extra_headers ? "" : RN) )); if (client->extra_headers) { assert(evbuffer_add_printf(buf, "X-UStreamer-Online: %s" RN "X-UStreamer-Dropped: %u" RN "X-UStreamer-Width: %u" RN "X-UStreamer-Height: %u" RN "X-UStreamer-Client-FPS: %u" RN "X-UStreamer-Grab-Time: %.06Lf" RN "X-UStreamer-Encode-Begin-Time: %.06Lf" RN "X-UStreamer-Encode-End-Time: %.06Lf" RN "X-UStreamer-Expose-Begin-Time: %.06Lf" RN "X-UStreamer-Expose-Cmp-Time: %.06Lf" RN "X-UStreamer-Expose-End-Time: %.06Lf" RN "X-UStreamer-Send-Time: %.06Lf" RN "X-UStreamer-Latency: %.06Lf" RN RN, bool_to_string(EX(frame->online)), EX(dropped), EX(frame->width), EX(frame->height), client->fps, EX(frame->grab_ts), EX(frame->encode_begin_ts), EX(frame->encode_end_ts), EX(expose_begin_ts), EX(expose_cmp_ts), EX(expose_end_ts), now, now - EX(frame->grab_ts) )); } } if (!client->zero_data) { assert(!evbuffer_add(buf, (void *)EX(frame->data), EX(frame->used))); } assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN)); if (client->advance_headers) { ADD_ADVANCE_HEADERS; } assert(!bufferevent_write_buffer(buf_event, buf)); evbuffer_free(buf); bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client); bufferevent_enable(buf_event, EV_READ); # undef ADD_ADVANCE_HEADERS # undef BOUNDARY } static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) { stream_client_s *client = (stream_client_s *)v_client; server_s *server = client->server; LIST_REMOVE_C(RUN(stream_clients), client, RUN(stream_clients_count)); if (RUN(stream_clients_count) == 0) { atomic_store(&VID(has_clients), false); # ifdef WITH_GPIO gpio_set_has_http_clients(false); # endif } char *reason = bufferevent_my_format_reason(what); LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u", client->hostport, client->id, reason, RUN(stream_clients_count)); free(reason); struct evhttp_connection *conn = evhttp_request_get_connection(client->request); if (conn) { evhttp_connection_free(conn); } free(client->key); free(client->hostport); free(client); } static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) { bool has_clients = false; bool queued = false; LIST_ITERATE(RUN(stream_clients), client, { struct evhttp_connection *conn = evhttp_request_get_connection(client->request); if (conn) { // Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов, // WebKit отрисовывает последний фрейм в серии с некоторой задержкой, // и нужно послать два фрейма, чтобы серия была вовремя завершена. // Это похоже на баг Blink (см. _http_callback_stream_write() и advance_headers), // но фикс для него не лечит проблему вебкита. Такие дела. bool dual_update = ( server->drop_same_frames && client->dual_final_frames && stream_updated && client->updated_prev && !frame_updated ); if (dual_update || frame_updated || client->need_first_frame) { struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn); bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client); bufferevent_enable(buf_event, EV_READ|EV_WRITE); client->need_first_frame = false; client->updated_prev = (frame_updated || client->need_first_frame); // Игнорировать dual queued = true; } else if (stream_updated) { // Для dual client->updated_prev = false; } has_clients = true; } }); if (queued) { static unsigned queued_fps_accum = 0; static long long queued_fps_second = 0; long long now = floor_ms(get_now_monotonic()); if (now != queued_fps_second) { EX(queued_fps) = queued_fps_accum; queued_fps_accum = 0; queued_fps_second = now; } queued_fps_accum += 1; } else if (!has_clients) { EX(queued_fps) = 0; } } static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) { server_s *server = (server_s *)v_server; bool stream_updated = false; bool frame_updated = false; if (atomic_load(&VID(updated))) { frame_updated = _expose_new_frame(server); stream_updated = true; } else if (EX(expose_end_ts) + 1 < get_now_monotonic()) { LOG_DEBUG("HTTP: Repeating exposed ..."); EX(expose_begin_ts) = get_now_monotonic(); EX(expose_cmp_ts) = EX(expose_begin_ts); EX(expose_end_ts) = EX(expose_begin_ts); frame_updated = true; stream_updated = true; } _http_queue_send_stream(server, stream_updated, frame_updated); if ( frame_updated && server->notify_parent && ( EX(notify_last_online) != EX(frame->online) || EX(notify_last_width) != EX(frame->width) || EX(notify_last_height) != EX(frame->height) ) ) { EX(notify_last_online) = EX(frame->online); EX(notify_last_width) = EX(frame->width); EX(notify_last_height) = EX(frame->height); process_notify_parent(); } } static bool _expose_new_frame(server_s *server) { bool updated = false; A_MUTEX_LOCK(&VID(mutex)); LOG_DEBUG("HTTP: Updating exposed frame (online=%d) ...", VID(frame->online)); EX(captured_fps) = VID(captured_fps); EX(expose_begin_ts) = get_now_monotonic(); if (server->drop_same_frames && VID(frame->online)) { bool need_drop = false; bool maybe_same = false; if ( (need_drop = (EX(dropped) < server->drop_same_frames)) && (maybe_same = frame_compare(EX(frame), VID(frame))) ) { EX(expose_cmp_ts) = get_now_monotonic(); EX(expose_end_ts) = EX(expose_cmp_ts); LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf", EX(dropped), EX(expose_cmp_ts) - EX(expose_begin_ts)); EX(dropped) += 1; goto not_updated; } else { EX(expose_cmp_ts) = get_now_monotonic(); LOG_VERBOSE("HTTP: Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf", need_drop, maybe_same, (EX(expose_cmp_ts) - EX(expose_begin_ts))); } } frame_copy(VID(frame), EX(frame)); EX(dropped) = 0; EX(expose_cmp_ts) = EX(expose_begin_ts); EX(expose_end_ts) = get_now_monotonic(); LOG_VERBOSE("HTTP: Exposed frame: online=%d, exp_time=%.06Lf", EX(frame->online), EX(expose_end_ts) - EX(expose_begin_ts)); updated = true; not_updated: atomic_store(&VID(updated), false); A_MUTEX_UNLOCK(&VID(mutex)); return updated; } #undef EX #undef VID #undef STREAM #undef RUN static char *_http_get_client_hostport(struct evhttp_request *request) { char *addr = NULL; unsigned short port = 0; struct evhttp_connection *conn = evhttp_request_get_connection(request); if (conn) { char *peer; evhttp_connection_get_peer(conn, &peer, &port); assert(addr = strdup(peer)); } const char *xff = evhttp_find_header(evhttp_request_get_input_headers(request), "X-Forwarded-For"); if (xff) { if (addr) { free(addr); } assert(addr = strndup(xff, 1024)); for (unsigned index = 0; addr[index]; ++index) { if (addr[index] == ',') { addr[index] = '\0'; break; } } } if (addr == NULL) { assert(addr = strdup("???")); } char *hostport; A_ASPRINTF(hostport, "[%s]:%u", addr, port); free(addr); return hostport; } ustreamer-4.9/src/ustreamer/http/server.h000066400000000000000000000102131414164060100206230ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef EVTHREAD_USE_PTHREADS_IMPLEMENTED # error Required libevent-pthreads support #endif #include "../../libs/tools.h" #include "../../libs/threading.h" #include "../../libs/logging.h" #include "../../libs/process.h" #include "../../libs/frame.h" #include "../../libs/base64.h" #include "../../libs/list.h" #include "../data/index_html.h" #include "../encoder.h" #include "../stream.h" #ifdef WITH_GPIO # include "../gpio/gpio.h" #endif #include "bev.h" #include "unix.h" #include "uri.h" #include "mime.h" #include "static.h" typedef struct stream_client_sx { struct server_sx *server; struct evhttp_request *request; char *key; bool extra_headers; bool advance_headers; bool dual_final_frames; bool zero_data; char *hostport; uint64_t id; bool need_initial; bool need_first_frame; bool updated_prev; unsigned fps; unsigned fps_accum; long long fps_accum_second; LIST_STRUCT(struct stream_client_sx); } stream_client_s; typedef struct { frame_s *frame; unsigned captured_fps; unsigned queued_fps; unsigned dropped; long double expose_begin_ts; long double expose_cmp_ts; long double expose_end_ts; bool notify_last_online; unsigned notify_last_width; unsigned notify_last_height; } exposed_s; typedef struct { struct event_base *base; struct evhttp *http; evutil_socket_t unix_fd; char *auth_token; struct event *refresh; stream_s *stream; exposed_s *exposed; stream_client_s *stream_clients; unsigned stream_clients_count; } server_runtime_s; typedef struct server_sx { char *host; unsigned port; char *unix_path; bool unix_rm; mode_t unix_mode; bool tcp_nodelay; unsigned timeout; char *user; char *passwd; char *static_path; char *allow_origin; unsigned drop_same_frames; unsigned fake_width; unsigned fake_height; bool notify_parent; server_runtime_s *run; } server_s; server_s *server_init(stream_s *stream); void server_destroy(server_s *server); int server_listen(server_s *server); void server_loop(server_s *server); void server_loop_break(server_s *server); ustreamer-4.9/src/ustreamer/http/static.c000066400000000000000000000052741414164060100206120ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "static.h" char *find_static_file_path(const char *root_path, const char *request_path) { char *path = NULL; char *simplified_path = simplify_request_path(request_path); if (simplified_path[0] == '\0') { LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path); goto error; } A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html sprintf(path, "%s/%s", root_path, simplified_path); struct stat st; # define LOAD_STAT { \ if (lstat(path, &st) < 0) { \ LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \ goto error; \ } \ } LOAD_STAT; if (S_ISDIR(st.st_mode)) { LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path); strcat(path, "/index.html"); LOAD_STAT; } # undef LOAD_STAT if (!S_ISREG(st.st_mode)) { LOG_VERBOSE("HTTP: Not a regular file: %s", path); goto error; } if (access(path, R_OK) < 0) { LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path); goto error; } goto ok; error: if (path) { free(path); } path = NULL; ok: free(simplified_path); return path; } ustreamer-4.9/src/ustreamer/http/static.h000066400000000000000000000035041414164060100206110ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" #include "path.h" char *find_static_file_path(const char *root_path, const char *request_path); ustreamer-4.9/src/ustreamer/http/unix.c000066400000000000000000000053531414164060100203040ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "unix.h" evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) { struct sockaddr_un addr; # define MAX_SUN_PATH (sizeof(addr.sun_path) - 1) if (strlen(path) > MAX_SUN_PATH) { LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH); return -1; } MEMSET_ZERO(addr); strncpy(addr.sun_path, path, MAX_SUN_PATH); addr.sun_family = AF_UNIX; # undef MAX_SUN_PATH evutil_socket_t fd = -1; assert((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0); assert(!evutil_make_socket_nonblocking(fd)); if (rm && unlink(path) < 0) { if (errno != ENOENT) { LOG_PERROR("Can't remove old UNIX socket '%s'", path); return -1; } } if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path); return -1; } if (mode && chmod(path, mode) < 0) { LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path); return -1; } if (listen(fd, 128) < 0) { LOG_PERROR("Can't listen UNIX socket '%s'", path); return -1; } if (evhttp_accept_socket(http, fd) < 0) { LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path); return -1; } return fd; } ustreamer-4.9/src/ustreamer/http/unix.h000066400000000000000000000036711414164060100203120ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include "../../libs/tools.h" #include "../../libs/logging.h" evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode); ustreamer-4.9/src/ustreamer/http/uri.c000066400000000000000000000041241414164060100201130ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "uri.h" bool uri_get_true(struct evkeyvalq *params, const char *key) { const char *value_str = evhttp_find_header(params, key); if (value_str != NULL) { if ( value_str[0] == '1' || !evutil_ascii_strcasecmp(value_str, "true") || !evutil_ascii_strcasecmp(value_str, "yes") ) { return true; } } return false; } char *uri_get_string(struct evkeyvalq *params, const char *key) { const char *value_str = evhttp_find_header(params, key); if (value_str != NULL) { return evhttp_encode_uri(value_str); } return NULL; } ustreamer-4.9/src/ustreamer/http/uri.h000066400000000000000000000034501414164060100201210ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include bool uri_get_true(struct evkeyvalq *params, const char *key); char *uri_get_string(struct evkeyvalq *params, const char *key); ustreamer-4.9/src/ustreamer/main.c000066400000000000000000000125511414164060100172640ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 #ifdef NDEBUG # error WTF dude? Asserts are good things! #endif #include #if CHAR_BIT != 8 # error There are not 8 bits in a char! #endif #include #include #include #include #ifdef WITH_OMX # include # include #endif #include "../libs/tools.h" #include "../libs/threading.h" #include "../libs/logging.h" #include "options.h" #include "device.h" #include "encoder.h" #include "stream.h" #include "http/server.h" #ifdef WITH_GPIO # include "gpio/gpio.h" #endif typedef struct { stream_s *stream; server_s *server; } _main_context_s; static _main_context_s *_ctx; static void _block_thread_signals(void) { sigset_t mask; assert(!sigemptyset(&mask)); assert(!sigaddset(&mask, SIGINT)); assert(!sigaddset(&mask, SIGTERM)); assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL)); } static void *_stream_loop_thread(UNUSED void *arg) { A_THREAD_RENAME("stream"); _block_thread_signals(); stream_loop(_ctx->stream); return NULL; } static void *_server_loop_thread(UNUSED void *arg) { A_THREAD_RENAME("http"); _block_thread_signals(); server_loop(_ctx->server); return NULL; } static void _signal_handler(int signum) { switch (signum) { case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break; case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break; default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break; } stream_loop_break(_ctx->stream); server_loop_break(_ctx->server); } static void _install_signal_handlers(void) { struct sigaction sig_act; MEMSET_ZERO(sig_act); assert(!sigemptyset(&sig_act.sa_mask)); sig_act.sa_handler = _signal_handler; assert(!sigaddset(&sig_act.sa_mask, SIGINT)); assert(!sigaddset(&sig_act.sa_mask, SIGTERM)); LOG_DEBUG("Installing SIGINT handler ..."); assert(!sigaction(SIGINT, &sig_act, NULL)); LOG_DEBUG("Installing SIGTERM handler ..."); assert(!sigaction(SIGTERM, &sig_act, NULL)); LOG_DEBUG("Ignoring SIGPIPE ..."); assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR); } int main(int argc, char *argv[]) { assert(argc >= 0); int exit_code = 0; LOGGING_INIT; A_THREAD_RENAME("main"); options_s *options = options_init(argc, argv); device_s *dev = device_init(); encoder_s *enc = encoder_init(); stream_s *stream = stream_init(dev, enc); server_s *server = server_init(stream); # ifdef WITH_OMX bool i_bcm_host = false; OMX_ERRORTYPE omx_error = OMX_ErrorUndefined; # endif if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) { # ifdef WITH_OMX if (enc->type == ENCODER_TYPE_OMX || stream->h264_sink) { bcm_host_init(); i_bcm_host = true; } if (enc->type == ENCODER_TYPE_OMX) { if ((omx_error = OMX_Init()) != OMX_ErrorNone) { LOG_ERROR_OMX(omx_error, "Can't initialize OMX Core; forced CPU encoder"); enc->type = ENCODER_TYPE_CPU; } } # endif # ifdef WITH_GPIO gpio_init(); # endif _install_signal_handlers(); _main_context_s ctx; ctx.stream = stream; ctx.server = server; _ctx = &ctx; if ((exit_code = server_listen(server)) == 0) { # ifdef WITH_GPIO gpio_set_prog_running(true); # endif pthread_t stream_loop_tid; pthread_t server_loop_tid; A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL); A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL); A_THREAD_JOIN(server_loop_tid); A_THREAD_JOIN(stream_loop_tid); } # ifdef WITH_GPIO gpio_set_prog_running(false); gpio_destroy(); # endif } server_destroy(server); stream_destroy(stream); encoder_destroy(enc); device_destroy(dev); options_destroy(options); # ifdef WITH_OMX if (omx_error == OMX_ErrorNone) { OMX_Deinit(); } if (i_bcm_host) { bcm_host_deinit(); } # endif if (exit_code == 0) { LOG_INFO("Bye-bye"); } LOGGING_DESTROY; return (exit_code < 0 ? 1 : 0); } ustreamer-4.9/src/ustreamer/options.c000066400000000000000000001017771414164060100200440ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "options.h" enum _OPT_VALUES { _O_DEVICE = 'd', _O_INPUT = 'i', _O_RESOLUTION = 'r', _O_FORMAT = 'm', _O_TV_STANDARD = 'a', _O_IO_METHOD = 'I', _O_DESIRED_FPS = 'f', _O_MIN_FRAME_SIZE = 'z', _O_PERSISTENT = 'n', _O_DV_TIMINGS = 't', _O_BUFFERS = 'b', _O_WORKERS = 'w', _O_QUALITY = 'q', _O_ENCODER = 'c', # ifdef WITH_OMX _O_GLITCHED_RESOLUTIONS = 'g', # endif _O_BLANK = 'k', _O_LAST_AS_BLANK = 'K', _O_SLOWDOWN = 'l', _O_HOST = 's', _O_PORT = 'p', _O_UNIX = 'U', _O_UNIX_RM = 'D', _O_UNIX_MODE = 'M', _O_DROP_SAME_FRAMES = 'e', _O_FAKE_RESOLUTION = 'R', _O_HELP = 'h', _O_VERSION = 'v', // Longs only _O_DEVICE_TIMEOUT = 10000, _O_DEVICE_ERROR_DELAY, _O_IMAGE_DEFAULT, _O_BRIGHTNESS, _O_CONTRAST, _O_SATURATION, _O_HUE, _O_GAMMA, _O_SHARPNESS, _O_BACKLIGHT_COMPENSATION, _O_WHITE_BALANCE, _O_GAIN, _O_COLOR_EFFECT, _O_ROTATE, _O_FLIP_VERTICAL, _O_FLIP_HORIZONTAL, _O_USER, _O_PASSWD, _O_STATIC, _O_ALLOW_ORIGIN, _O_TCP_NODELAY, _O_SERVER_TIMEOUT, # define ADD_SINK(_prefix) \ _O_##_prefix, \ _O_##_prefix##_MODE, \ _O_##_prefix##_RM, \ _O_##_prefix##_CLIENT_TTL, \ _O_##_prefix##_TIMEOUT, ADD_SINK(SINK) ADD_SINK(RAW_SINK) # ifdef WITH_OMX ADD_SINK(H264_SINK) _O_H264_BITRATE, _O_H264_GOP, # endif # undef ADD_SINK # ifdef WITH_GPIO _O_GPIO_DEVICE, _O_GPIO_CONSUMER_PREFIX, _O_GPIO_PROG_RUNNING, _O_GPIO_STREAM_ONLINE, _O_GPIO_HAS_HTTP_CLIENTS, # endif # ifdef HAS_PDEATHSIG _O_EXIT_ON_PARENT_DEATH, # endif # ifdef WITH_SETPROCTITLE _O_PROCESS_NAME_PREFIX, # endif _O_NOTIFY_PARENT, _O_LOG_LEVEL, _O_PERF, _O_VERBOSE, _O_DEBUG, _O_FORCE_LOG_COLORS, _O_NO_LOG_COLORS, _O_FEATURES, }; static const struct option _LONG_OPTS[] = { {"device", required_argument, NULL, _O_DEVICE}, {"input", required_argument, NULL, _O_INPUT}, {"resolution", required_argument, NULL, _O_RESOLUTION}, {"format", required_argument, NULL, _O_FORMAT}, {"tv-standard", required_argument, NULL, _O_TV_STANDARD}, {"io-method", required_argument, NULL, _O_IO_METHOD}, {"desired-fps", required_argument, NULL, _O_DESIRED_FPS}, {"min-frame-size", required_argument, NULL, _O_MIN_FRAME_SIZE}, {"persistent", no_argument, NULL, _O_PERSISTENT}, {"dv-timings", no_argument, NULL, _O_DV_TIMINGS}, {"buffers", required_argument, NULL, _O_BUFFERS}, {"workers", required_argument, NULL, _O_WORKERS}, {"quality", required_argument, NULL, _O_QUALITY}, {"encoder", required_argument, NULL, _O_ENCODER}, # ifdef WITH_OMX {"glitched-resolutions", required_argument, NULL, _O_GLITCHED_RESOLUTIONS}, # endif {"blank", required_argument, NULL, _O_BLANK}, {"last-as-blank", required_argument, NULL, _O_LAST_AS_BLANK}, {"slowdown", no_argument, NULL, _O_SLOWDOWN}, {"device-timeout", required_argument, NULL, _O_DEVICE_TIMEOUT}, {"device-error-delay", required_argument, NULL, _O_DEVICE_ERROR_DELAY}, {"image-default", no_argument, NULL, _O_IMAGE_DEFAULT}, {"brightness", required_argument, NULL, _O_BRIGHTNESS}, {"contrast", required_argument, NULL, _O_CONTRAST}, {"saturation", required_argument, NULL, _O_SATURATION}, {"hue", required_argument, NULL, _O_HUE}, {"gamma", required_argument, NULL, _O_GAMMA}, {"sharpness", required_argument, NULL, _O_SHARPNESS}, {"backlight-compensation", required_argument, NULL, _O_BACKLIGHT_COMPENSATION}, {"white-balance", required_argument, NULL, _O_WHITE_BALANCE}, {"gain", required_argument, NULL, _O_GAIN}, {"color-effect", required_argument, NULL, _O_COLOR_EFFECT}, {"rotate", required_argument, NULL, _O_ROTATE}, {"flip-vertical", required_argument, NULL, _O_FLIP_VERTICAL}, {"flip-horizontal", required_argument, NULL, _O_FLIP_HORIZONTAL}, {"host", required_argument, NULL, _O_HOST}, {"port", required_argument, NULL, _O_PORT}, {"unix", required_argument, NULL, _O_UNIX}, {"unix-rm", no_argument, NULL, _O_UNIX_RM}, {"unix-mode", required_argument, NULL, _O_UNIX_MODE}, {"user", required_argument, NULL, _O_USER}, {"passwd", required_argument, NULL, _O_PASSWD}, {"static", required_argument, NULL, _O_STATIC}, {"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES}, {"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN}, {"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION}, {"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY}, {"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT}, # define ADD_SINK(_opt, _prefix) \ {_opt "sink", required_argument, NULL, _O_##_prefix}, \ {_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \ {_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \ {_opt "sink-client-ttl", required_argument, NULL, _O_##_prefix##_CLIENT_TTL}, \ {_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT}, ADD_SINK("", SINK) ADD_SINK("raw-", RAW_SINK) # ifdef WITH_OMX ADD_SINK("h264-", H264_SINK) {"h264-bitrate", required_argument, NULL, _O_H264_BITRATE}, {"h264-gop", required_argument, NULL, _O_H264_GOP}, # endif # undef ADD_SINK # ifdef WITH_GPIO {"gpio-device", required_argument, NULL, _O_GPIO_DEVICE}, {"gpio-consumer-prefix", required_argument, NULL, _O_GPIO_CONSUMER_PREFIX}, {"gpio-prog-running", required_argument, NULL, _O_GPIO_PROG_RUNNING}, {"gpio-stream-online", required_argument, NULL, _O_GPIO_STREAM_ONLINE}, {"gpio-has-http-clients", required_argument, NULL, _O_GPIO_HAS_HTTP_CLIENTS}, # endif # ifdef HAS_PDEATHSIG {"exit-on-parent-death", no_argument, NULL, _O_EXIT_ON_PARENT_DEATH}, # endif # ifdef WITH_SETPROCTITLE {"process-name-prefix", required_argument, NULL, _O_PROCESS_NAME_PREFIX}, # endif {"notify-parent", no_argument, NULL, _O_NOTIFY_PARENT}, {"log-level", required_argument, NULL, _O_LOG_LEVEL}, {"perf", no_argument, NULL, _O_PERF}, {"verbose", no_argument, NULL, _O_VERBOSE}, {"debug", no_argument, NULL, _O_DEBUG}, {"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS}, {"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS}, {"help", no_argument, NULL, _O_HELP}, {"version", no_argument, NULL, _O_VERSION}, {"features", no_argument, NULL, _O_FEATURES}, {NULL, 0, NULL, 0}, }; static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited); static void _features(void); static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server); options_s *options_init(unsigned argc, char *argv[]) { options_s *options; A_CALLOC(options, 1); options->argc = argc; options->argv = argv; A_CALLOC(options->argv_copy, argc); for (unsigned index = 0; index < argc; ++index) { assert(options->argv_copy[index] = strdup(argv[index])); } return options; } void options_destroy(options_s *options) { # define ADD_SINK(_prefix) { \ if (options->_prefix) { \ memsink_destroy(options->_prefix); \ } \ } ADD_SINK(sink); ADD_SINK(raw_sink); # ifdef WITH_OMX ADD_SINK(h264_sink); # endif # undef ADD_SINK if (options->blank) { frame_destroy(options->blank); } for (unsigned index = 0; index < options->argc; ++index) { free(options->argv_copy[index]); } free(options->argv_copy); free(options); } int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) { # define OPT_SET(_dest, _value) { \ _dest = _value; \ break; \ } # define OPT_NUMBER(_name, _dest, _min, _max, _base) { \ errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \ if (errno || *_end || _tmp < _min || _tmp > _max) { \ printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \ return -1; \ } \ _dest = _tmp; \ break; \ } # define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \ switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \ case -1: \ printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \ return -1; \ case -2: \ printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \ return -1; \ case -3: \ printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \ return -1; \ case 0: break; \ default: assert(0 && "Unknown error"); \ } \ break; \ } # define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \ if ((_dest = _func(optarg)) == _invalid) { \ printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \ return -1; \ } \ break; \ } # define OPT_CTL_DEFAULT_NOBREAK(_dest) { \ dev->ctl._dest.mode = CTL_MODE_DEFAULT; \ } # define OPT_CTL_MANUAL(_dest) { \ if (!strcasecmp(optarg, "default")) { \ OPT_CTL_DEFAULT_NOBREAK(_dest); \ } else { \ dev->ctl._dest.mode = CTL_MODE_VALUE; \ OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \ } \ break; \ } # define OPT_CTL_AUTO(_dest) { \ if (!strcasecmp(optarg, "default")) { \ OPT_CTL_DEFAULT_NOBREAK(_dest); \ } else if (!strcasecmp(optarg, "auto")) { \ dev->ctl._dest.mode = CTL_MODE_AUTO; \ } else { \ dev->ctl._dest.mode = CTL_MODE_VALUE; \ OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \ } \ break; \ } char *blank_path = NULL; # define ADD_SINK(_prefix) \ char *_prefix##_name = NULL; \ mode_t _prefix##_mode = 0660; \ bool _prefix##_rm = false; \ unsigned _prefix##_client_ttl = 10; \ unsigned _prefix##_timeout = 1; ADD_SINK(sink); ADD_SINK(raw_sink); # ifdef WITH_OMX ADD_SINK(h264_sink); # endif # undef ADD_SINK # ifdef WITH_SETPROCTITLE char *process_name_prefix = NULL; # endif char short_opts[128]; build_short_options(_LONG_OPTS, short_opts, 128); for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) { switch (ch) { case _O_DEVICE: OPT_SET(dev->path, optarg); case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0); case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true); # pragma GCC diagnostic ignored "-Wsign-compare" # pragma GCC diagnostic push case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR); # pragma GCC diagnostic pop case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR); case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR); case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0); case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0); case _O_PERSISTENT: OPT_SET(dev->persistent, true); case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true); case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0); case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0); case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0); case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR); # ifdef WITH_OMX case _O_GLITCHED_RESOLUTIONS: break; # endif case _O_BLANK: OPT_SET(blank_path, optarg); case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0); case _O_SLOWDOWN: OPT_SET(stream->slowdown, true); case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0); case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0); case _O_IMAGE_DEFAULT: OPT_CTL_DEFAULT_NOBREAK(brightness); OPT_CTL_DEFAULT_NOBREAK(contrast); OPT_CTL_DEFAULT_NOBREAK(saturation); OPT_CTL_DEFAULT_NOBREAK(hue); OPT_CTL_DEFAULT_NOBREAK(gamma); OPT_CTL_DEFAULT_NOBREAK(sharpness); OPT_CTL_DEFAULT_NOBREAK(backlight_compensation); OPT_CTL_DEFAULT_NOBREAK(white_balance); OPT_CTL_DEFAULT_NOBREAK(gain); OPT_CTL_DEFAULT_NOBREAK(color_effect); OPT_CTL_DEFAULT_NOBREAK(rotate); OPT_CTL_DEFAULT_NOBREAK(flip_vertical); OPT_CTL_DEFAULT_NOBREAK(flip_horizontal); break; case _O_BRIGHTNESS: OPT_CTL_AUTO(brightness); case _O_CONTRAST: OPT_CTL_MANUAL(contrast); case _O_SATURATION: OPT_CTL_MANUAL(saturation); case _O_HUE: OPT_CTL_AUTO(hue); case _O_GAMMA: OPT_CTL_MANUAL(gamma); case _O_SHARPNESS: OPT_CTL_MANUAL(sharpness); case _O_BACKLIGHT_COMPENSATION: OPT_CTL_MANUAL(backlight_compensation); case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance); case _O_GAIN: OPT_CTL_AUTO(gain); case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect); case _O_ROTATE: OPT_CTL_MANUAL(rotate); case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical); case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal); case _O_HOST: OPT_SET(server->host, optarg); case _O_PORT: OPT_NUMBER("--port", server->port, 1, 65535, 0); case _O_UNIX: OPT_SET(server->unix_path, optarg); case _O_UNIX_RM: OPT_SET(server->unix_rm, true); case _O_UNIX_MODE: OPT_NUMBER("--unix-mode", server->unix_mode, INT_MIN, INT_MAX, 8); case _O_USER: OPT_SET(server->user, optarg); case _O_PASSWD: OPT_SET(server->passwd, optarg); case _O_STATIC: OPT_SET(server->static_path, optarg); case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0); case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false); case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg); case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true); case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0); # define ADD_SINK(_opt, _lp, _up) \ case _O_##_up: OPT_SET(_lp##_name, optarg); \ case _O_##_up##_MODE: OPT_NUMBER("--" #_opt "sink-mode", _lp##_mode, INT_MIN, INT_MAX, 8); \ case _O_##_up##_RM: OPT_SET(_lp##_rm, true); \ case _O_##_up##_CLIENT_TTL: OPT_NUMBER("--" #_opt "sink-client-ttl", _lp##_client_ttl, 1, 60, 0); \ case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0); ADD_SINK("", sink, SINK) ADD_SINK("raw-", raw_sink, RAW_SINK) # ifdef WITH_OMX ADD_SINK("h264-", h264_sink, H264_SINK) case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 100, 16000, 0); case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0); # endif # undef ADD_SINK # ifdef WITH_GPIO case _O_GPIO_DEVICE: OPT_SET(us_gpio.path, optarg); case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_gpio.consumer_prefix, optarg); case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", us_gpio.prog_running.pin, 0, 256, 0); case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", us_gpio.stream_online.pin, 0, 256, 0); case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_gpio.has_http_clients.pin, 0, 256, 0); # endif # ifdef HAS_PDEATHSIG case _O_EXIT_ON_PARENT_DEATH: if (process_track_parent_death() < 0) { return -1; }; break; # endif # ifdef WITH_SETPROCTITLE case _O_PROCESS_NAME_PREFIX: OPT_SET(process_name_prefix, optarg); # endif case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true); case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0); case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF); case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE); case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG); case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true); case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false); case _O_HELP: _help(stdout, dev, enc, stream, server); return 1; case _O_VERSION: puts(VERSION); return 1; case _O_FEATURES: _features(); return 1; case 0: break; default: return -1; } } options->blank = blank_frame_init(blank_path); stream->blank = options->blank; # define ADD_SINK(_label, _prefix) { \ if (_prefix##_name && _prefix##_name[0] != '\0') { \ options->_prefix = memsink_init( \ _label, \ _prefix##_name, \ true, \ _prefix##_mode, \ _prefix##_rm, \ _prefix##_client_ttl, \ _prefix##_timeout \ ); \ } \ stream->_prefix = options->_prefix; \ } ADD_SINK("JPEG", sink); ADD_SINK("RAW", raw_sink); # ifdef WITH_OMX ADD_SINK("H264", h264_sink); # endif # undef ADD_SINK # ifdef WITH_SETPROCTITLE if (process_name_prefix != NULL) { process_set_name_prefix(options->argc, options->argv, process_name_prefix); } # endif # undef OPT_CTL_AUTO # undef OPT_CTL_MANUAL # undef OPT_CTL_DEFAULT_NOBREAK # undef OPT_PARSE # undef OPT_RESOLUTION # undef OPT_NUMBER # undef OPT_SET return 0; } static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) { unsigned tmp_width; unsigned tmp_height; if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) { return -1; } if (limited) { if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) { return -2; } if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) { return -3; } } *width = tmp_width; *height = tmp_height; return 0; } static void _features(void) { # ifdef WITH_OMX puts("+ WITH_OMX"); # else puts("- WITH_OMX"); # endif # ifdef WITH_GPIO puts("+ WITH_GPIO"); # else puts("- WITH_GPIO"); # endif # ifdef WITH_PTHREAD_NP puts("+ WITH_PTHREAD_NP"); # else puts("- WITH_PTHREAD_NP"); # endif # ifdef WITH_SETPROCTITLE puts("+ WITH_SETPROCTITLE"); # else puts("- WITH_SETPROCTITLE"); # endif # ifdef HAS_PDEATHSIG puts("+ HAS_PDEATHSIG"); # else puts("- HAS_PDEATHSIG"); # endif } static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) { # define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__) SAY("\nuStreamer - Lightweight and fast MJPG-HTTP streamer"); SAY("═══════════════════════════════════════════════════"); SAY("Version: %s; license: GPLv3", VERSION); SAY("Copyright (C) 2018-2021 Maxim Devaev \n"); SAY("Capturing options:"); SAY("══════════════════"); SAY(" -d|--device ───────────── Path to V4L2 device. Default: %s.\n", dev->path); SAY(" -i|--input ────────────────────── Input channel. Default: %u.\n", dev->input); SAY(" -r|--resolution ─────────────── Initial image resolution. Default: %ux%u.\n", dev->width, dev->height); SAY(" -m|--format ─────────────────── Image format."); SAY(" Available: %s; default: YUYV.\n", FORMATS_STR); SAY(" -a|--tv-standard ────────────── Force TV standard."); SAY(" Available: %s; default: disabled.\n", STANDARDS_STR); SAY(" -I|--io-method ───────────── Set V4L2 IO method (see kernel documentation)."); SAY(" Changing of this parameter may increase the performance. Or not."); SAY(" Available: %s; default: MMAP.\n", IO_METHODS_STR); SAY(" -f|--desired-fps ──────────────── Desired FPS. Default: maximum possible.\n"); SAY(" -z|--min-frame-size ───────────── Drop frames smaller then this limit. Useful if the device"); SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size); SAY(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n"); SAY(" -t|--dv-timings ───────────────────── Enable DV timings querying and events processing"); SAY(" to automatic resolution change. Default: disabled.\n"); SAY(" -b|--buffers ──────────────────── The number of buffers to receive data from the device."); SAY(" Each buffer may processed using an independent thread."); SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", dev->n_bufs); SAY(" -w|--workers ──────────────────── The number of worker threads but not more than buffers."); SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers); SAY(" -q|--quality ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.", dev->jpeg_quality); SAY(" Note: If HW encoding is used (JPEG source format selected),"); SAY(" this parameter attempts to configure the camera"); SAY(" or capture device hardware's internal encoder."); SAY(" It does not re-encode MJPG to MJPG to change the quality level"); SAY(" for sources that already output MJPG.\n"); SAY(" -c|--encoder ───────────────── Use specified encoder. It may affect the number of workers."); SAY(" Available:"); SAY(" * CPU ── Software MJPG encoding (default);"); # ifdef WITH_OMX SAY(" * OMX ── GPU hardware accelerated MJPG encoding with OpenMax;"); # endif SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware."); SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n"); # ifdef WITH_OMX SAY(" -g|--glitched-resolutions ─ It doesn't do anything. Still here for compatibility.\n"); # endif SAY(" -k|--blank ─────────────────── Path to JPEG file that will be shown when the device is disconnected"); SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n"); SAY(" -K|--last-as-blank ──────────── Show the last frame received from the camera after it was disconnected,"); SAY(" but no more than specified time (or endlessly if 0 is specified)."); SAY(" If the device has not yet been online, display 'NO SIGNAL' or the image"); SAY(" specified by option --blank. Default: disabled."); SAY(" Note: currently this option has no effect on memory sinks.\n"); SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients"); SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n"); SAY(" --device-timeout ────────────── Timeout for device querying. Default: %u.\n", dev->timeout); SAY(" --device-error-delay ────────── Delay before trying to connect to the device again"); SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay); SAY("Image control options:"); SAY("══════════════════════"); SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n"); SAY(" --brightness ──────── Set brightness. Default: no change.\n"); SAY(" --contrast ─────────────── Set contrast. Default: no change.\n"); SAY(" --saturation ───────────── Set saturation. Default: no change.\n"); SAY(" --hue ─────────────── Set hue. Default: no change.\n"); SAY(" --gamma ─────────────────── Set gamma. Default: no change.\n"); SAY(" --sharpness ────────────── Set sharpness. Default: no change.\n"); SAY(" --backlight-compensation ─ Set backlight compensation. Default: no change.\n"); SAY(" --white-balance ───── Set white balance. Default: no change.\n"); SAY(" --gain ────────────── Set gain. Default: no change.\n"); SAY(" --color-effect ─────────── Set color effect. Default: no change.\n"); SAY(" --rotate ───────────────── Set rotation. Default: no change.\n"); SAY(" --flip-vertical <1|0|default> ──────── Set vertical flip. Default: no change.\n"); SAY(" --flip-horizontal <1|0|default> ────── Set horizontal flip. Default: no change.\n"); SAY(" Hint: use v4l2-ctl --list-ctrls-menus to query available controls of the device.\n"); SAY("HTTP server options:"); SAY("════════════════════"); SAY(" -s|--host
──────── Listen on Hostname or IP. Default: %s.\n", server->host); SAY(" -p|--port ────────────── Bind to this TCP port. Default: %u.\n", server->port); SAY(" -U|--unix ─────────── Bind to UNIX domain socket. Default: disabled.\n"); SAY(" -D|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled.\n"); SAY(" -M|--unix-mode ────── Set UNIX socket file permissions (like 777). Default: disabled.\n"); SAY(" --user ────────────── HTTP basic auth user. Default: disabled.\n"); SAY(" --passwd ───────────── HTTP basic auth passwd. Default: empty.\n"); SAY(" --static ───────────── Path to dir with static files instead of embedded root index page."); SAY(" Symlinks are not supported for security reasons. Default: disabled.\n"); SAY(" -e|--drop-same-frames ── Don't send identical frames to clients, but no more than specified number."); SAY(" It can significantly reduce the outgoing traffic, but will increase"); SAY(" the CPU loading. Don't use this option with analog signal sources"); SAY(" or webcams, it's useless. Default: disabled.\n"); SAY(" -R|--fake-resolution ─ Override image resolution for the /state. Default: disabled.\n"); SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Ignored for --unix."); SAY(" Default: disabled.\n"); SAY(" --allow-origin ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n"); SAY(" --server-timeout ───── Timeout for client connections. Default: %u.\n", server->timeout); # define ADD_SINK(_name, _opt) \ SAY(_name " sink options:"); \ SAY("══════════════════"); \ SAY(" --" _opt "sink ─────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \ SAY(" --" _opt "sink-mode ────── Set " _name " sink permissions (like 777). Default: 660.\n"); \ SAY(" --" _opt "sink-rm ─────────────── Remove shared memory on stop. Default: disabled.\n"); \ SAY(" --" _opt "sink-client-ttl ─ Client TTL. Default: 10.\n"); \ SAY(" --" _opt "sink-timeout ──── Timeout for lock. Default: 1.\n"); ADD_SINK("JPEG", "") ADD_SINK("RAW", "raw-") # ifdef WITH_OMX ADD_SINK("H264", "h264-") SAY(" --h264-bitrate ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate); SAY(" --h264-gop ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop); # endif # undef ADD_SINK # ifdef WITH_GPIO SAY("GPIO options:"); SAY("═════════════"); SAY(" --gpio-device ───── Path to GPIO character device. Default: %s.\n", us_gpio.path); SAY(" --gpio-consumer-prefix ── Consumer prefix for GPIO outputs. Default: %s.\n", us_gpio.consumer_prefix); SAY(" --gpio-prog-running ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n"); SAY(" --gpio-stream-online ──── Set 1 while streaming. Default: disabled.\n"); SAY(" --gpio-has-http-clients ─ Set 1 while stream has at least one client. Default: disabled.\n"); # endif # if (defined(HAS_PDEATHSIG) || defined(WITH_SETPROCTITLE)) SAY("Process options:"); SAY("════════════════"); # endif # ifdef HAS_PDEATHSIG SAY(" --exit-on-parent-death ─────── Exit the program if the parent process is dead. Default: disabled.\n"); # endif # ifdef WITH_SETPROCTITLE SAY(" --process-name-prefix ── Set process name prefix which will be displayed in the process list"); SAY(" like ': ustreamer --blah-blah-blah'. Default: disabled.\n"); SAY(" --notify-parent ────────────── Send SIGUSR2 to the parent process when the stream parameters are changed."); SAY(" Checking changes is performed for the online flag and image resolution.\n"); # endif SAY("Logging options:"); SAY("════════════════"); SAY(" --log-level ──── Verbosity level of messages from 0 (info) to 3 (debug)."); SAY(" Enabling debugging messages can slow down the program."); SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug)."); SAY(" Default: %d.\n", us_log_level); SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n"); SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n"); SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n"); SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n"); SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n"); SAY("Help options:"); SAY("═════════════"); SAY(" -h|--help ─────── Print this text and exit.\n"); SAY(" -v|--version ──── Print version and exit.\n"); SAY(" --features ────── Print list of supported features.\n"); # undef SAY } ustreamer-4.9/src/ustreamer/options.h000066400000000000000000000047001414164060100200350ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include "../libs/config.h" #include "../libs/logging.h" #include "../libs/process.h" #include "../libs/frame.h" #include "../libs/memsink.h" #include "../libs/options.h" #include "device.h" #include "encoder.h" #include "blank.h" #include "stream.h" #include "http/server.h" #ifdef WITH_GPIO # include "gpio/gpio.h" #endif typedef struct { unsigned argc; char **argv; char **argv_copy; frame_s *blank; memsink_s *sink; memsink_s *raw_sink; # ifdef WITH_OMX memsink_s *h264_sink; # endif } options_s; options_s *options_init(unsigned argc, char *argv[]); void options_destroy(options_s *options); int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server); ustreamer-4.9/src/ustreamer/stream.c000066400000000000000000000251571414164060100176410ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "stream.h" static workers_pool_s *_stream_init_loop(stream_s *stream); static workers_pool_s *_stream_init_one(stream_s *stream); static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps); #define RUN(_next) stream->run->_next #define SINK_PUT(_sink, _frame) { \ if (stream->_sink && memsink_server_check(stream->_sink, _frame)) {\ memsink_server_put(stream->_sink, _frame); \ } \ } #ifdef WITH_OMX # define H264_PUT(_frame, _vcsm_handle, _force_key) { \ if (RUN(h264)) { \ h264_stream_process(RUN(h264), _frame, _vcsm_handle, _force_key); \ } \ } #endif stream_s *stream_init(device_s *dev, encoder_s *enc) { stream_runtime_s *run; A_CALLOC(run, 1); atomic_init(&run->stop, false); video_s *video; A_CALLOC(video, 1); video->frame = frame_init(); atomic_init(&video->updated, false); A_MUTEX_INIT(&video->mutex); atomic_init(&video->has_clients, false); run->video = video; stream_s *stream; A_CALLOC(stream, 1); stream->dev = dev; stream->enc = enc; stream->last_as_blank = -1; stream->error_delay = 1; # ifdef WITH_OMX stream->h264_bitrate = 5000; // Kbps stream->h264_gop = 30; # endif stream->run = run; return stream; } void stream_destroy(stream_s *stream) { A_MUTEX_DESTROY(&RUN(video->mutex)); frame_destroy(RUN(video->frame)); free(RUN(video)); free(stream->run); free(stream); } void stream_loop(stream_s *stream) { assert(stream->blank); LOG_INFO("Using V4L2 device: %s", stream->dev->path); LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps); # ifdef WITH_OMX if (stream->h264_sink) { RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_bitrate, stream->h264_gop); } # endif for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) { long double grab_after = 0; unsigned fluency_passed = 0; unsigned captured_fps = 0; unsigned captured_fps_accum = 0; long long captured_fps_second = 0; LOG_INFO("Capturing ..."); while (!atomic_load(&RUN(stop))) { SEP_DEBUG('-'); LOG_DEBUG("Waiting for worker ..."); worker_s *ready_wr = workers_pool_wait(pool); encoder_job_s *ready_job = (encoder_job_s *)(ready_wr->job); if (ready_job->hw) { if (device_release_buffer(stream->dev, ready_job->hw) < 0) { ready_wr->job_failed = true; } ready_job->hw = NULL; if (!ready_wr->job_failed) { if (ready_wr->job_timely) { _stream_expose_frame(stream, ready_job->dest, captured_fps); LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name); } else { LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name); } } else { break; } } # ifdef WITH_OMX bool h264_force_key = false; # endif if (stream->slowdown) { unsigned slc = 0; while ( slc < 10 && !atomic_load(&RUN(stop)) && !atomic_load(&RUN(video->has_clients)) // has_clients синков НЕ обновляются в реальном времени && (stream->sink == NULL || !atomic_load(&stream->sink->has_clients)) # ifdef WITH_OMX && (RUN(h264) == NULL || /*RUN(h264->sink) == NULL ||*/ !atomic_load(&RUN(h264->sink->has_clients))) # endif ) { usleep(100000); ++slc; } # ifdef WITH_OMX h264_force_key = (slc == 10); # endif } if (atomic_load(&RUN(stop))) { break; } bool has_read; bool has_write; bool has_error; int selected = device_select(stream->dev, &has_read, &has_write, &has_error); if (selected < 0) { if (errno != EINTR) { LOG_PERROR("Mainloop select() error"); break; } } else if (selected == 0) { // Persistent timeout # ifdef WITH_GPIO gpio_set_stream_online(false); # endif } else { if (has_read) { LOG_DEBUG("Frame is ready"); # ifdef WITH_GPIO gpio_set_stream_online(true); # endif const long double now = get_now_monotonic(); const long long now_second = floor_ms(now); hw_buffer_s *hw; int buf_index = device_grab_buffer(stream->dev, &hw); if (buf_index >= 0) { if (now < grab_after) { fluency_passed += 1; LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf", fluency_passed, now, grab_after); if (device_release_buffer(stream->dev, hw) < 0) { break; } } else { fluency_passed = 0; if (now_second != captured_fps_second) { captured_fps = captured_fps_accum; captured_fps_accum = 0; captured_fps_second = now_second; LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps); } captured_fps_accum += 1; const long double fluency_delay = workers_pool_get_fluency_delay(pool, ready_wr); grab_after = now + fluency_delay; LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after); ready_job->hw = hw; workers_pool_assign(pool, ready_wr); LOG_DEBUG("Assigned new frame in buffer %d to worker %s", buf_index, ready_wr->name); SINK_PUT(raw_sink, &hw->raw); # ifdef WITH_OMX H264_PUT(&hw->raw, hw->vcsm_handle, h264_force_key); # endif } } else if (buf_index != -2) { // -2 for broken frame break; } } if (has_write) { LOG_ERROR("Got unexpected writing event, seems device was disconnected"); break; } if (has_error) { LOG_INFO("Got V4L2 event"); if (device_consume_event(stream->dev) < 0) { break; } } } } workers_pool_destroy(pool); device_switch_capturing(stream->dev, false); device_close(stream->dev); # ifdef WITH_GPIO gpio_set_stream_online(false); # endif } # ifdef WITH_OMX if (RUN(h264)) { h264_stream_destroy(RUN(h264)); } # endif } void stream_loop_break(stream_s *stream) { atomic_store(&RUN(stop), true); } static workers_pool_s *_stream_init_loop(stream_s *stream) { workers_pool_s *pool = NULL; int access_error = 0; LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&RUN(stop))); while (!atomic_load(&RUN(stop))) { _stream_expose_frame(stream, NULL, 0); if (access(stream->dev->path, R_OK|W_OK) < 0) { if (access_error != errno) { SEP_INFO('='); LOG_PERROR("Can't access device"); LOG_INFO("Waiting for the device access ..."); access_error = errno; } sleep(stream->error_delay); continue; } else { SEP_INFO('='); access_error = 0; } if ((pool = _stream_init_one(stream)) == NULL) { LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay); sleep(stream->error_delay); } else { break; } } return pool; } static workers_pool_s *_stream_init_one(stream_s *stream) { if (device_open(stream->dev) < 0) { goto error; } # ifdef WITH_OMX if (RUN(h264) && !is_jpeg(stream->dev->run->format)) { device_export_to_vcsm(stream->dev); } # endif if (device_switch_capturing(stream->dev, true) < 0) { goto error; } return encoder_workers_pool_init(stream->enc, stream->dev); error: device_close(stream->dev); return NULL; } static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps) { # define VID(_next) RUN(video->_next) frame_s *new = NULL; A_MUTEX_LOCK(&VID(mutex)); if (frame) { new = frame; RUN(last_as_blank_ts) = 0; // Останавливаем таймер LOG_DEBUG("Exposed ALIVE video frame"); } else { if (VID(frame->used == 0)) { new = stream->blank; // Инициализация RUN(last_as_blank_ts) = 0; } else if (VID(frame->online)) { // Если переходим из online в offline if (stream->last_as_blank < 0) { // Если last_as_blank выключен, просто покажем старую картинку new = stream->blank; LOG_INFO("Changed video frame to BLANK"); } else if (stream->last_as_blank > 0) { // // Если нужен таймер - запустим RUN(last_as_blank_ts) = get_now_monotonic() + stream->last_as_blank; LOG_INFO("Freezed last ALIVE video frame for %d seconds", stream->last_as_blank); } else { // last_as_blank == 0 - показываем последний фрейм вечно LOG_INFO("Freezed last ALIVE video frame forever"); } } else if (stream->last_as_blank < 0) { new = stream->blank; // LOG_INFO("Changed video frame to BLANK"); } if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен stream->last_as_blank > 0 && RUN(last_as_blank_ts) != 0 && RUN(last_as_blank_ts) < get_now_monotonic() ) { new = stream->blank; RUN(last_as_blank_ts) = 0; // // Останавливаем таймер LOG_INFO("Changed last ALIVE video frame to BLANK"); } } if (new) { frame_copy(new, VID(frame)); } VID(frame->online) = (bool)frame; VID(captured_fps) = captured_fps; atomic_store(&VID(updated), true); A_MUTEX_UNLOCK(&VID(mutex)); new = (frame ? frame : stream->blank); SINK_PUT(sink, new); if (frame == NULL) { SINK_PUT(raw_sink, stream->blank); # ifdef WITH_OMX H264_PUT(stream->blank, -1, false); # endif } # undef VID } #ifdef WITH_OMX # undef H264_PUT #endif #undef SINK_PUT #undef RUN ustreamer-4.9/src/ustreamer/stream.h000066400000000000000000000055121414164060100176370ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include "../libs/tools.h" #include "../libs/threading.h" #include "../libs/logging.h" #include "../libs/frame.h" #include "../libs/memsink.h" #include "blank.h" #include "device.h" #include "encoder.h" #include "workers.h" #ifdef WITH_OMX # include "h264/stream.h" #endif #ifdef WITH_GPIO # include "gpio/gpio.h" #endif typedef struct { frame_s *frame; unsigned captured_fps; atomic_bool updated; pthread_mutex_t mutex; atomic_bool has_clients; // For slowdown } video_s; typedef struct { video_s *video; long double last_as_blank_ts; # ifdef WITH_OMX h264_stream_s *h264; # endif atomic_bool stop; } stream_runtime_s; typedef struct { device_s *dev; encoder_s *enc; frame_s *blank; int last_as_blank; bool slowdown; unsigned error_delay; memsink_s *sink; memsink_s *raw_sink; # ifdef WITH_OMX memsink_s *h264_sink; unsigned h264_bitrate; unsigned h264_gop; # endif stream_runtime_s *run; } stream_s; stream_s *stream_init(device_s *dev, encoder_s *enc); void stream_destroy(stream_s *stream); void stream_loop(stream_s *stream); void stream_loop_break(stream_s *stream); ustreamer-4.9/src/ustreamer/workers.c000066400000000000000000000162131414164060100200330ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 "workers.h" static void *_worker_thread(void *v_worker); workers_pool_s *workers_pool_init( const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval, workers_pool_job_init_f job_init, void *job_init_arg, workers_pool_job_destroy_f job_destroy, workers_pool_run_job_f run_job) { LOG_INFO("Creating pool %s with %u workers ...", name, n_workers); workers_pool_s *pool; A_CALLOC(pool, 1); pool->name = name; pool->desired_interval = desired_interval; pool->job_destroy = job_destroy; pool->run_job = run_job; atomic_init(&pool->stop, false); pool->n_workers = n_workers; A_CALLOC(pool->workers, pool->n_workers); A_MUTEX_INIT(&pool->free_workers_mutex); A_COND_INIT(&pool->free_workers_cond); for (unsigned number = 0; number < pool->n_workers; ++number) { # define WR(_next) pool->workers[number]._next WR(number) = number; A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number); A_MUTEX_INIT(&WR(has_job_mutex)); atomic_init(&WR(has_job), false); A_COND_INIT(&WR(has_job_cond)); WR(pool) = pool; WR(job) = job_init(job_init_arg); A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number])); pool->free_workers += 1; # undef WR } return pool; } void workers_pool_destroy(workers_pool_s *pool) { LOG_INFO("Destroying workers pool %s ...", pool->name); atomic_store(&pool->stop, true); for (unsigned number = 0; number < pool->n_workers; ++number) { # define WR(_next) pool->workers[number]._next A_MUTEX_LOCK(&WR(has_job_mutex)); atomic_store(&WR(has_job), true); // Final job: die A_MUTEX_UNLOCK(&WR(has_job_mutex)); A_COND_SIGNAL(&WR(has_job_cond)); A_THREAD_JOIN(WR(tid)); A_MUTEX_DESTROY(&WR(has_job_mutex)); A_COND_DESTROY(&WR(has_job_cond)); free(WR(name)); pool->job_destroy(WR(job)); # undef WR } A_MUTEX_DESTROY(&pool->free_workers_mutex); A_COND_DESTROY(&pool->free_workers_cond); free(pool->workers); free(pool); } worker_s *workers_pool_wait(workers_pool_s *pool) { worker_s *ready_wr = NULL; A_MUTEX_LOCK(&pool->free_workers_mutex); A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex); A_MUTEX_UNLOCK(&pool->free_workers_mutex); if (pool->oldest_wr && !atomic_load(&pool->oldest_wr->has_job)) { ready_wr = pool->oldest_wr; ready_wr->job_timely = true; pool->oldest_wr = pool->oldest_wr->next_wr; } else { for (unsigned number = 0; number < pool->n_workers; ++number) { if ( !atomic_load(&pool->workers[number].has_job) && ( ready_wr == NULL || ready_wr->job_start_ts < pool->workers[number].job_start_ts ) ) { ready_wr = &pool->workers[number]; break; } } assert(ready_wr != NULL); ready_wr->job_timely = false; // Освободился воркер, получивший задание позже (или самый первый при самом первом захвате) } return ready_wr; } void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/) { if (pool->oldest_wr == NULL) { pool->oldest_wr = ready_wr; pool->latest_wr = pool->oldest_wr; } else { if (ready_wr->next_wr) { ready_wr->next_wr->prev_wr = ready_wr->prev_wr; } if (ready_wr->prev_wr) { ready_wr->prev_wr->next_wr = ready_wr->next_wr; } ready_wr->prev_wr = pool->latest_wr; pool->latest_wr->next_wr = ready_wr; pool->latest_wr = ready_wr; } pool->latest_wr->next_wr = NULL; A_MUTEX_LOCK(&ready_wr->has_job_mutex); //ready_wr->job = job; atomic_store(&ready_wr->has_job, true); A_MUTEX_UNLOCK(&ready_wr->has_job_mutex); A_COND_SIGNAL(&ready_wr->has_job_cond); A_MUTEX_LOCK(&pool->free_workers_mutex); pool->free_workers -= 1; A_MUTEX_UNLOCK(&pool->free_workers_mutex); } long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr) { const long double approx_job_time = pool->approx_job_time * 0.9 + ready_wr->last_job_time * 0.1; LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)", pool->name, pool->approx_job_time, approx_job_time, ready_wr->last_job_time); pool->approx_job_time = approx_job_time; const long double min_delay = pool->approx_job_time / pool->n_workers; // Среднее время работы размазывается на N воркеров if (pool->desired_interval > 0 && min_delay > 0 && pool->desired_interval > min_delay) { // Искусственное время задержки на основе желаемого FPS, если включен --desired-fps // и аппаратный fps не попадает точно в желаемое значение return pool->desired_interval; } return min_delay; } static void *_worker_thread(void *v_worker) { worker_s *wr = (worker_s *)v_worker; A_THREAD_RENAME("%s", wr->name); LOG_DEBUG("Hello! I am a worker %s ^_^", wr->name); while (!atomic_load(&wr->pool->stop)) { LOG_DEBUG("Worker %s waiting for a new job ...", wr->name); A_MUTEX_LOCK(&wr->has_job_mutex); A_COND_WAIT_TRUE(atomic_load(&wr->has_job), &wr->has_job_cond, &wr->has_job_mutex); A_MUTEX_UNLOCK(&wr->has_job_mutex); if (!atomic_load(&wr->pool->stop)) { long double job_start_ts = get_now_monotonic(); wr->job_failed = !wr->pool->run_job(wr); if (!wr->job_failed) { wr->job_start_ts = job_start_ts; wr->last_job_time = get_now_monotonic() - wr->job_start_ts; } //wr->job = NULL; atomic_store(&wr->has_job, false); } A_MUTEX_LOCK(&wr->pool->free_workers_mutex); wr->pool->free_workers += 1; A_MUTEX_UNLOCK(&wr->pool->free_workers_mutex); A_COND_SIGNAL(&wr->pool->free_workers_cond); } LOG_DEBUG("Bye-bye (worker %s)", wr->name); return NULL; } ustreamer-4.9/src/ustreamer/workers.h000066400000000000000000000063041414164060100200400ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include #include #include "../libs/tools.h" #include "../libs/threading.h" #include "../libs/logging.h" typedef struct worker_sx { pthread_t tid; unsigned number; char *name; long double last_job_time; pthread_mutex_t has_job_mutex; void *job; atomic_bool has_job; bool job_timely; bool job_failed; long double job_start_ts; pthread_cond_t has_job_cond; struct worker_sx *prev_wr; struct worker_sx *next_wr; struct workers_pool_sx *pool; } worker_s; typedef void *(*workers_pool_job_init_f)(void *arg); typedef void (*workers_pool_job_destroy_f)(void *job); typedef bool (*workers_pool_run_job_f)(worker_s *wr); typedef struct workers_pool_sx { const char *name; long double desired_interval; workers_pool_job_destroy_f job_destroy; workers_pool_run_job_f run_job; unsigned n_workers; worker_s *workers; worker_s *oldest_wr; worker_s *latest_wr; long double approx_job_time; pthread_mutex_t free_workers_mutex; unsigned free_workers; pthread_cond_t free_workers_cond; atomic_bool stop; } workers_pool_s; workers_pool_s *workers_pool_init( const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval, workers_pool_job_init_f job_init, void *job_init_arg, workers_pool_job_destroy_f job_destroy, workers_pool_run_job_f run_job); void workers_pool_destroy(workers_pool_s *pool); worker_s *workers_pool_wait(workers_pool_s *pool); void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/); long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr); ustreamer-4.9/src/ustreamer/xioctl.h000066400000000000000000000043221414164060100176440ustar00rootroot00000000000000/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ #pragma once #include #include #include "../libs/tools.h" #include "../libs/logging.h" #ifndef CFG_XIOCTL_RETRIES # define CFG_XIOCTL_RETRIES 4 #endif #define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES)) INLINE int xioctl(int fd, int request, void *arg) { int retries = XIOCTL_RETRIES; int retval = -1; do { retval = ioctl(fd, request, arg); } while ( retval && retries-- && ( errno == EINTR || errno == EAGAIN || errno == ETIMEDOUT ) ); // cppcheck-suppress knownConditionTrueFalse if (retval && retries <= 0) { LOG_PERROR("ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES); } return retval; } ustreamer-4.9/tools/000077500000000000000000000000001414164060100145325ustar00rootroot00000000000000ustreamer-4.9/tools/common.py000066400000000000000000000065451414164060100164060ustar00rootroot00000000000000#!/usr/bin/env python3 # ========================================================================== # # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # # ========================================================================== # import textwrap # ===== C_PREPEND = textwrap.dedent(""" /***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # *****************************************************************************/ """).strip() + "\n" ustreamer-4.9/tools/make-html-h.py000077500000000000000000000047301414164060100172170ustar00rootroot00000000000000#!/usr/bin/env -S python3 -B # ========================================================================== # # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # # ========================================================================== # import sys import os import textwrap import common # ===== def main() -> None: assert len(sys.argv) == 4, f"{sys.argv[0]} " html_path = sys.argv[1] c_path = sys.argv[2] h_path = os.path.basename(c_path[:-2] + ".h") name = sys.argv[3] with open(html_path, "r") as html_file: html = html_file.read() html = html.strip() html = html.replace("\"", "\\\"") html = html.replace("%VERSION%", "\" VERSION \"") html = textwrap.indent(html, "\t", (lambda line: True)) html = "\n".join( (f"{line} \\" if line.strip() else f"{line}\\") for line in html.split("\n") ) text = f"{common.C_PREPEND}\n#include \"{h_path}\"\n\n\n" text += f"const char *const HTML_{name}_PAGE = \" \\\n{html}\n\";\n" with open(c_path, "w") as c_file: c_file.write(text) # ===== if __name__ == "__main__": main() ustreamer-4.9/tools/make-jpeg-h.py000077500000000000000000000070771414164060100172070ustar00rootroot00000000000000#!/usr/bin/env -S python3 -B # ========================================================================== # # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018-2021 Maxim Devaev # # # # 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 . # # # # ========================================================================== # import sys import os import io import struct from typing import Tuple import common # ===== def _get_jpeg_size(data: bytes) -> Tuple[int, int]: # https://sheep.horse/2013/9/finding_the_dimensions_of_a_jpeg_file_in_python.html stream = io.BytesIO(data) while True: marker = struct.unpack(">H", stream.read(2))[0] if marker == 0xFFD9: raise RuntimeError("Can't find jpeg size") if ( marker == 0xFFD8 # Start of image or marker == 0xFF01 # Private marker or 0xFFD0 <= marker <= 0xFFD7 # Restart markers ): continue # All other markers specify chunks with lengths length = struct.unpack(">H", stream.read(2))[0] if marker == 0xFFC0: # Baseline DCT chunk, has the info we want (_, height, width) = struct.unpack(">BHH", stream.read(5)) return (width, height) # Not the chunk we want, skip it stream.seek(length - 2, 1) # ===== def main() -> None: assert len(sys.argv) == 4, f"{sys.argv[0]} " jpeg_path = sys.argv[1] c_path = sys.argv[2] h_path = os.path.basename(c_path[:-2]) + ".h" name = sys.argv[3] with open(jpeg_path, "rb") as jpeg_file: jpeg_data = jpeg_file.read() (width, height) = _get_jpeg_size(jpeg_data) jpeg_data_text = "{\n\t" + ",\n\t".join( ", ".join( f"0x{ch:02X}" for ch in jpeg_data[index:index + 20] ) for index in range(0, len(jpeg_data), 20) ) + ",\n}" text = f"{common.C_PREPEND}\n" text += f"#include \"{h_path}\"\n\n\n" text += f"const unsigned {name}_JPEG_WIDTH = {width};\n" text += f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n" text += f"const size_t {name}_JPEG_DATA_SIZE = {len(jpeg_data)};\n" text += f"const uint8_t {name}_JPEG_DATA[] = {jpeg_data_text};\n" with open(c_path, "w") as c_file: c_file.write(text) # ===== if __name__ == "__main__": main()