commit 6f9553aa1f8c286ecfbafc960d55457f56df6ef0 Author: icetd Date: Thu May 9 16:31:45 2024 +0800 Initialize diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e68ada --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.clangd/ +build/ +thirdparty/mpp +compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e2078da --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdparty/libv4l2cc"] + path = thirdparty/libv4l2cc + url = https://github.com/icetd/libv4l2cc.git +[submodule "thirdparty/libjpeg-turbo"] + path = thirdparty/libjpeg-turbo + url = https://github.com/icetd/libjpeg-turbo.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..275dc98 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.14) + +project(RkCamRtspServer) + +option (WITH_SSL "Enable SSL support" ON) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_BUILD_TYPE Debug) +include (cmake/live555.cmake) + +add_subdirectory(thirdparty/libv4l2cc) +add_subdirectory(thirdparty/libjpeg-turbo) + +set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/inc") +set(SOURCE_DIR "${CMAKE_SOURCE_DIR}/src") +set(V4L2CC_INC "${CMAKE_SOURCE_DIR}/thirdparty/libv4l2cc/inc") +set(JPEGTURBO_INC "${CMAKE_SOURCE_DIR}/thirdparty/libjpeg-turbo") +set(MPP_INC "${CMAKE_SOURCE_DIR}/thirdparty/mpp/include") +set(MPP_LIB "${CMAKE_SOURCE_DIR}/thirdparty/mpp/lib") + +link_directories( + ${MPP_LIB} +) + +include_directories( + ${INCLUDE_DIR} + ${V4L2CC_INC} + ${JPEGTURBO_INC} + ${MPP_INC} + ${LIVE555_INC} +) + +file(GLOB_RECURSE SRC_FILES + "${SOURCE_DIR}/*c*" +) + +file (GLOB_RECURSE INC_FILES + "${INCLUDE_DIR}/*.h" +) + + +list(APPEND EXTRA_LIBS +) + +# Add the executable +add_executable(${PROJECT_NAME} ${SRC_FILES} ${LIVE555_FILES} ${INC_FILES} ${LIVE555_INC_FILES}) + +# Add the target includes for MY_PROJECT +target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${LIVE555_INC}) +target_compile_definitions(${PROJECT_NAME} PUBLIC ${LIVE555_CFLAGS}) + +#===================== LINKING LIBRARIES =======================# +target_link_libraries(${PROJECT_NAME} v4l2cc pthread rockchip_vpu rockchip_mpp turbojpeg ${EXTRA_LIBS}) +if (OpenSSL_FOUND) + target_link_libraries(${PROJECT_NAME} OpenSSL::SSL) +endif () + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/configs + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d582144 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# RkCamRtspServer +simple rtspserver for rk hardware + +## test env +- rk3588s +- ubuntu 20.04 + +## build && run +``` +./scripts/build_mpp.sh +git submodule update --init +mkdir build && cd build +cmake .. +make -j4 +sudo ./RkCamRtspServer +``` + +## config +config with file configs/config.ini +``` +[log] +level = NOTICE + +[video] +width = 640 +height = 480 +fps = 30 +fix_qp = 23 ;[0 - 51] [high - low] +format = YUY2 ;[MJPEG] 720P +device = /dev/video0 + +[server] +rtsp_port = 8554 +stream_name = unicast +max_buf_size = 200000 +max_packet_size = 1500 +http_enable = false +http_port = 8000 +bitrate = 1440 +``` + +## function +- support yuy2 format usb camera. +- support hardware h264 encode. + +## Benefits of improvement +- RkCamRtspServer cpu usage +``` +%Cpu0 : 0.7 us, 0.4 sy, 0.0 ni, 94.4 id, 0.0 wa, 0.0 hi, 4.6 si, 0.0 st +%Cpu1 : 4.3 us, 7.2 sy, 0.0 ni, 88.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu2 : 3.7 us, 0.7 sy, 0.0 ni, 95.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu3 : 0.7 us, 4.4 sy, 0.0 ni, 94.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu4 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu5 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu6 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu7 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +``` + +- CamRtspServer cpu usage +``` +%Cpu0 : 19.6 us, 1.0 sy, 0.0 ni, 77.3 id, 0.0 wa, 0.0 hi, 2.1 si, 0.0 st +%Cpu1 : 21.6 us, 0.0 sy, 0.0 ni, 78.0 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st +%Cpu2 : 23.7 us, 0.7 sy, 0.0 ni, 75.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu3 : 28.0 us, 4.0 sy, 0.0 ni, 67.3 id, 0.0 wa, 0.0 hi, 0.7 si, 0.0 st +%Cpu4 : 0.0 us, 0.0 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.7 si, 0.0 st +%Cpu5 : 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu6 : 1.3 us, 0.7 sy, 0.0 ni, 98.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu7 : 0.0 us, 11.6 sy, 0.0 ni, 88.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +``` diff --git a/cmake/live555.cmake b/cmake/live555.cmake new file mode 100644 index 0000000..a8e511e --- /dev/null +++ b/cmake/live555.cmake @@ -0,0 +1,21 @@ +set(LIVE555_URL http://www.live555.com/liveMedia/public/live555-latest.tar.gz CACHE STRING "live555 url") +set(LIVE555_CFLAGS -DBSD=1 -DSOCKLEN_T=socklen_t -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE=1 -DALLOW_RTSP_SERVER_PORT_REUSE=1 -DNO_STD_LIB=1 CACHE STRING "live555 CFGLAGS") + +if (WITH_SSL) + find_package(OpenSSL QUIET) +endif() + +set(LIVE ${CMAKE_BINARY_DIR}/live) +set(LIVE555_INC ${LIVE}/groupsock/include ${LIVE}/liveMedia/include ${LIVE}/UsageEnvironment/include ${LIVE}/BasicUsageEnvironment/include) +if (NOT EXISTS ${LIVE}) + file (DOWNLOAD ${LIVE555_URL} ${CMAKE_BINARY_DIR}/live555-latest.tar.gz ) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xvf ${CMAKE_BINARY_DIR}/live555-latest.tar.gz RESULT_VARIABLE unpack_result) + if(NOT unpack_result STREQUAL "0") + message(FATAL_ERROR "Fetching and compiling live555 failed!") + endif() +endif() +FILE(GLOB LIVE555_FILES ${LIVE}/groupsock/*.c* ${LIVE}/liveMedia/*.c* ${LIVE}/UsageEnvironment/*.c* ${LIVE}/BasicUsageEnvironment/*.c*) +FILE(GLOB LIVE555_INC_FILES ${LIVE}/groupsock/*.h* ${LIVE}/liveMedia/*.h* ${LIVE}/UsageEnvironment/*.h* ${LIVE}/BasicUsageEnvironment/*.h*) +if (NOT OpenSSL_FOUND) + set(LIVE555_CFLAGS ${LIVE555_CFLAGS} -DNO_OPENSSL=1) +endif() diff --git a/configs/config.ini b/configs/config.ini new file mode 100644 index 0000000..6555c9c --- /dev/null +++ b/configs/config.ini @@ -0,0 +1,19 @@ +[log] +level = NOTICE + +[video] +width = 640 +height = 480 +fps = 30 +fix_qp = 23 +format = YUY2 +device = /dev/video0 + +[server] +rtsp_port = 8554 +stream_name = unicast +max_buf_size = 200000 +max_packet_size = 1500 +http_enable = false +http_port = 8000 +bitrate = 1440 diff --git a/inc/CamFramedSource.h b/inc/CamFramedSource.h new file mode 100644 index 0000000..13e5dd0 --- /dev/null +++ b/inc/CamFramedSource.h @@ -0,0 +1,35 @@ +#ifndef CAM_FRAME_SOURCE_H +#define CAM_FRAME_SOURCE_H + +#include +#include +#include +#include "transcoder.h" + +class CamFramedSource : public FramedSource { +public: + static CamFramedSource *createNew(UsageEnvironment &env, TransCoder &transcoder); + +protected: + CamFramedSource(UsageEnvironment &env, TransCoder &transcoder); + ~CamFramedSource(); + + void doGetNextFrame() override; + void doStopGettingFrames() override; + +private: + TransCoder &transcoder_; + EventTriggerId eventTriggerId; + std::mutex encodedDataMutex; + + size_t max_nalu_size_bytes; + std::vector encodedData; + std::vector > encodedDataBuffer; + + void onEncodedData(std::vector &&data); + + static void deliverFrame0(void *); + void deliverData(); +}; + +#endif \ No newline at end of file diff --git a/inc/CamRTSPServer.h b/inc/CamRTSPServer.h new file mode 100644 index 0000000..5415fd1 --- /dev/null +++ b/inc/CamRTSPServer.h @@ -0,0 +1,70 @@ +#ifndef CAM_RTSP_SERVER_H +#define CAM_RTSP_SERVER_H + +#include +#include +#include +#include +#include + +#include "CamFramedSource.h" +#include "CamUnicastServerMediaSubsession.h" + +class CamRTSPServer +{ +public: + typedef struct { + int rtspPort; + std::string streamName; + int maxPacketSize; + int maxBufSize; + bool isHttpEnable; + int httpPort; + int bitrate; + } Config_t; + + CamRTSPServer(); + ~CamRTSPServer(); + + void stopServer(); + + void addTranscoder(std::shared_ptr transcoder); + + void run(); + +private: + char watcher_; + TaskScheduler *scheduler_; + UsageEnvironment *env_; + RTSPServer *server_; + Config_t config; + /** + * @brief Video sources. + */ + std::vector> transcoders_; + + /** + * @brief Framed sources. + */ + std::vector allocatedVideoSources; + + /** + * @brief Announce new create media session. + * + * @param sms - create server media session. + * @param deviceName - the name of video source device. + */ + void announceStream(ServerMediaSession *sms, const std::string &deviceName); + + /** + * @brief Add new server media session using transcoder as a source to the server + * + * @param transoder - video source. + * @param streamName - the name of stream (part of the URL), e.g. rtsp:///camera/1. + * @param streamDesc - description of stream. + */ + void addMediaSession(std::shared_ptr transoder, const std::string &streamName, const std::string &streamDesc); + +}; + +#endif \ No newline at end of file diff --git a/inc/CamUnicastServerMediaSubsession.h b/inc/CamUnicastServerMediaSubsession.h new file mode 100644 index 0000000..5529244 --- /dev/null +++ b/inc/CamUnicastServerMediaSubsession.h @@ -0,0 +1,35 @@ +#ifndef CAM_UNICAST_SERVER_MEDIA_SUBSESSION_H +#define CAM_UNICAST_SERVER_MEDIA_SUBSESSION_H + +#include +#include +#include +#include + +class CamUbicastServerMediaSubsession : public OnDemandServerMediaSubsession +{ +public: + static CamUbicastServerMediaSubsession * + createNew(UsageEnvironment &env, StreamReplicator *replicator, size_t estBitrate, size_t udpDatagramSize); + + +protected: + StreamReplicator *replicator_; + + size_t estBitrate_; + + size_t udpDatagramSize_; + + CamUbicastServerMediaSubsession(UsageEnvironment &env, + StreamReplicator *replicator, + size_t estBitrate, + size_t udpDatagramSize); + + FramedSource *createNewStreamSource(unsigned clientSessionId, unsigned &estBitrate) override; + + RTPSink *createNewRTPSink(Groupsock *rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + FramedSource *inputSource) override; +}; + +#endif \ No newline at end of file diff --git a/inc/DeCompress.h b/inc/DeCompress.h new file mode 100644 index 0000000..7e4e086 --- /dev/null +++ b/inc/DeCompress.h @@ -0,0 +1,23 @@ +#ifndef DECOMPRESS_H +#define DECOMPTESS_H + +#include +#include + +class DeCompress +{ +public: + DeCompress(); + ~DeCompress(); + + int tjpeg2yuv(unsigned char *jpeg_buffer, int jpeg_size, unsigned char **yuv_buffer, int *yuv_size); + +private: + tjhandle m_handler; + int yuv_type; + int width; + int height; +}; + + +#endif \ No newline at end of file diff --git a/inc/INIReader.h b/inc/INIReader.h new file mode 100644 index 0000000..9c8a131 --- /dev/null +++ b/inc/INIReader.h @@ -0,0 +1,96 @@ +#ifndef INIREADER_H +#define INIREADER_H + +#include +#include +#include + +// Visibility symbols, required for Windows DLLs +#ifndef INI_API +#if defined _WIN32 || defined __CYGWIN__ +# ifdef INI_SHARED_LIB +# ifdef INI_SHARED_LIB_BUILDING +# define INI_API __declspec(dllexport) +# else +# define INI_API __declspec(dllimport) +# endif +# else +# define INI_API +# endif +#else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define INI_API __attribute__ ((visibility ("default"))) +# else +# define INI_API +# endif +#endif +#endif + +// Read an INI file into easy-to-access name/value pairs. (Note that I've gone +// for simplicity here rather than speed, but it should be pretty decent.) +class INIReader +{ +public: + // Construct INIReader and parse given filename. See ini.h for more info + // about the parsing. + INI_API explicit INIReader(const std::string& filename); + + // Construct INIReader and parse given buffer. See ini.h for more info + // about the parsing. + INI_API explicit INIReader(const char *buffer, size_t buffer_size); + + // Return the result of ini_parse(), i.e., 0 on success, line number of + // first error on parse error, or -1 on file open error. + INI_API int ParseError() const; + + // Get a string value from INI file, returning default_value if not found. + INI_API std::string Get(const std::string& section, const std::string& name, + const std::string& default_value) const; + + // Get a string value from INI file, returning default_value if not found, + // empty, or contains only whitespace. + INI_API std::string GetString(const std::string& section, const std::string& name, + const std::string& default_value) const; + + // Get an integer (long) value from INI file, returning default_value if + // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). + INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; + + // Get a 64-bit integer (int64_t) value from INI file, returning default_value if + // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). + INI_API int64_t GetInteger64(const std::string& section, const std::string& name, int64_t default_value) const; + + // Get an unsigned integer (unsigned long) value from INI file, returning default_value if + // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). + INI_API unsigned long GetUnsigned(const std::string& section, const std::string& name, unsigned long default_value) const; + + // Get an unsigned 64-bit integer (uint64_t) value from INI file, returning default_value if + // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). + INI_API uint64_t GetUnsigned64(const std::string& section, const std::string& name, uint64_t default_value) const; + + // Get a real (floating point double) value from INI file, returning + // default_value if not found or not a valid floating point value + // according to strtod(). + INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; + + // Get a boolean value from INI file, returning default_value if not found or if + // not a valid true/false value. Valid true values are "true", "yes", "on", "1", + // and valid false values are "false", "no", "off", "0" (not case sensitive). + INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; + + // Return true if the given section exists (section must contain at least + // one name=value pair). + INI_API bool HasSection(const std::string& section) const; + + // Return true if a value exists with the given section and field names. + INI_API bool HasValue(const std::string& section, const std::string& name) const; + +private: + int _error; + std::map _values; + static std::string MakeKey(const std::string& section, const std::string& name); + static int ValueHandler(void* user, const char* section, const char* name, + const char* value); +}; + +#endif // INIREADER_H \ No newline at end of file diff --git a/inc/MThread.h b/inc/MThread.h new file mode 100644 index 0000000..9d4cdce --- /dev/null +++ b/inc/MThread.h @@ -0,0 +1,31 @@ +#ifndef THREAD_H +#define THREAD_H + +#include +#include +#include + +class MThread +{ +public: + MThread(); + virtual ~MThread(); + + std::thread::id getId(); + + void start(); + void detach(); + void stop(); + void join(); + void sleep(int sec); + bool isStoped(); + + virtual void run() = 0; + +private: + std::atomic stopState; + std::thread th; +}; + +#endif + diff --git a/inc/RkEncoder.h b/inc/RkEncoder.h new file mode 100644 index 0000000..f790236 --- /dev/null +++ b/inc/RkEncoder.h @@ -0,0 +1,44 @@ +#ifndef RK_ENCODER_H +#define RK_ENCODER_H + +#include +#include +#include +#include +#include +#include + +typedef struct +{ + MppFrameFormat fmt; + int width; + int height; + int hor_stride; + int ver_stride; + int frame_size; + int fps; + int fix_qp; +} Encoder_Param_t; + +class RkEncoder +{ +public: + RkEncoder(Encoder_Param_t param); + ~RkEncoder(); + + int init(); + int encode(void *inbuf, int insize, uint8_t *outbuf); + + int startCode3(uint8_t *buf); + int startCode4(uint8_t *buf); + +private: + Encoder_Param_t m_param; + MppCtx m_contex; + MppApi *m_mpi; + MppPacket m_packet = nullptr; + MppFrame m_frame = nullptr; + MppBuffer m_buffer = nullptr; +}; + +#endif diff --git a/inc/ini.h b/inc/ini.h new file mode 100644 index 0000000..dac561e --- /dev/null +++ b/inc/ini.h @@ -0,0 +1,165 @@ +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Visibility symbols, required for Windows DLLs */ +#ifndef INI_API +#if defined _WIN32 || defined __CYGWIN__ +# ifdef INI_SHARED_LIB +# ifdef INI_SHARED_LIB_BUILDING +# define INI_API __declspec(dllexport) +# else +# define INI_API __declspec(dllimport) +# endif +# else +# define INI_API +# endif +#else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define INI_API __attribute__ ((visibility ("default"))) +# else +# define INI_API +# endif +#endif +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +INI_API int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ \ No newline at end of file diff --git a/inc/transcoder.h b/inc/transcoder.h new file mode 100644 index 0000000..7101c82 --- /dev/null +++ b/inc/transcoder.h @@ -0,0 +1,50 @@ +#ifndef TRANS_CODE_H +#define TRANS_CODE_H + +#include +#include +#include +#include + +#include "MThread.h" +#include "V4l2Device.h" +#include "V4l2Capture.h" +#include "RkEncoder.h" +#include "DeCompress.h" + +class TransCoder : public MThread +{ +public: + typedef struct { + int width; + int height; + int fps; + int fix_qp; + std::string format; + std::string device_name; + } Config_t; + + TransCoder(); + ~TransCoder(); + void init(); + + void run() override; + void setOnEncoderDataCallback(std::function &&)> callback); + + TransCoder::Config_t const &getConfig() const; +private: + Config_t config; + + V4l2Capture *capture; + uint8_t *yuv_buf; + int yuv_size; + uint8_t *encodeData; + int frameSize; + std::function &&)> onEncodedDataCallback; + + RkEncoder *rk_encoder; + DeCompress *decompress; +}; + + +#endif diff --git a/scripts/build_mpp.sh b/scripts/build_mpp.sh new file mode 100755 index 0000000..e10dd48 --- /dev/null +++ b/scripts/build_mpp.sh @@ -0,0 +1,29 @@ +#! /usr/bin/bash +CURRENT_DIR=$(pwd) +MPP_VERSION=1.0.5 +MPP_SRC=mpp-${MPP_VERSION} +MPP_SRC_DIR=${CURRENT_DIR}/thirdparty/${MPP_SRC} +MPP_URL=https://github.com/rockchip-linux/mpp/archive/refs/tags/${MPP_VERSION}.tar.gz +MPP_PREFIX=${CURRENT_DIR}/thirdparty/mpp + +cd ${CURRENT_DIR}/thirdparty + +if [ ! -d "${MPP_PREFIX}" ] +then + (wget -O "${MPP_SRC}.tar" ${MPP_URL} \ + && tar -xf "${MPP_SRC}.tar") || exit + rm -f "${MPP_SRC}.tar" +else + echo "already build mpp" +fi + +if [ ! -d "${MPP_PREFIX}" ] +then + cd ${MPP_SRC_DIR} + (cmake -DCMAKE_INSTALL_PREFIX=${MPP_PREFIX} .) || exit + (make -j4 && make install) || exit + cd ${CURRENT_DIR} + (rm -rf "${MPP_SRC_DIR}") || exit +else + echo "already build mpp" +fi diff --git a/src/CamFramedSource.cc b/src/CamFramedSource.cc new file mode 100644 index 0000000..a978fe6 --- /dev/null +++ b/src/CamFramedSource.cc @@ -0,0 +1,101 @@ +#include "CamFramedSource.h" +#include "log.h" + +CamFramedSource *CamFramedSource::createNew(UsageEnvironment &env, TransCoder &transcoder) +{ + return new CamFramedSource(env, transcoder); +} + +CamFramedSource::CamFramedSource(UsageEnvironment &env, TransCoder &transcoder) : + FramedSource(env), + transcoder_(transcoder), + eventTriggerId(0), + max_nalu_size_bytes(0) +{ + // create trigger invoking method which will deliver frame + eventTriggerId = envir().taskScheduler().createEventTrigger(CamFramedSource::deliverFrame0); + encodedDataBuffer.reserve(5); // reserve enough space for handling incoming encoded data + + // set transcoder's callback indicating new encoded data availabity + transcoder_.setOnEncoderDataCallback(std::bind(&CamFramedSource::onEncodedData, this, std::placeholders::_1)); + + LOG(INFO, "Starting to capture and encoder video from video %s", + transcoder_.getConfig().device_name.c_str()); + + transcoder_.start(); + transcoder_.detach(); +} + +CamFramedSource::~CamFramedSource() +{ + transcoder_.stop(); + + envir().taskScheduler().deleteEventTrigger(eventTriggerId); + encodedDataBuffer.clear(); + encodedDataBuffer.shrink_to_fit(); +} + +void CamFramedSource::onEncodedData(std::vector &&data) +{ + if (!isCurrentlyAwaitingData()) { + return; + } + + encodedDataMutex.lock(); + encodedDataBuffer.emplace_back(std::move(data)); + encodedDataMutex.unlock(); + + envir().taskScheduler().triggerEvent(eventTriggerId, this); +} + +void CamFramedSource::deliverFrame0(void *clientData) { + ((CamFramedSource*) clientData)->deliverData(); +} + +void CamFramedSource::deliverData() { + if (!isCurrentlyAwaitingData()) { + return; + } + + encodedDataMutex.lock(); + encodedData = std::move(encodedDataBuffer.back()); + encodedDataBuffer.pop_back(); + encodedDataMutex.unlock(); + + if (encodedData.size() > max_nalu_size_bytes) { + max_nalu_size_bytes = encodedData.size(); + } + + if (encodedData.size() > fMaxSize) { + fFrameSize = fMaxSize; + fNumTruncatedBytes = static_cast(encodedData.size() - fMaxSize); + LOG(WARN, "Exceeded max size, truncated: %d, size: %d", fNumTruncatedBytes, encodedData.size()); + } else { + fFrameSize = static_cast (encodedData.size()); + } + + // can be changed to the actual frame's captured time + gettimeofday(&fPresentationTime, nullptr); + + // DO NOT CHANGE ADDRESS, ONLY COPY (see Live555 docs) + memcpy(fTo, encodedData.data(), fFrameSize); + + // should be invoked after successfully getting data + FramedSource::afterGetting(this); +} + +void CamFramedSource::doGetNextFrame() +{ + if (!encodedDataBuffer.empty()) { + deliverData(); + } else { + fFrameSize = 0; + return; + } +} + +void CamFramedSource::doStopGettingFrames() +{ + LOG(NOTICE, "Stop getting frames from the camera: %s", transcoder_.getConfig().device_name.c_str()); + FramedSource::doStopGettingFrames(); +} diff --git a/src/CamRTSPServer.cc b/src/CamRTSPServer.cc new file mode 100644 index 0000000..4dbe802 --- /dev/null +++ b/src/CamRTSPServer.cc @@ -0,0 +1,118 @@ +#include "CamRTSPServer.h" +#include "INIReader.h" +#include "log.h" +CamRTSPServer::CamRTSPServer() : + watcher_(0), + scheduler_(nullptr), + env_(nullptr) +{ + INIReader configs("./configs/config.ini"); + if (configs.ParseError() < 0) { + LOG(ERROR, "read server config failed."); + return; + } else { + config.rtspPort = configs.GetInteger("server", "rtsp_port", 8554); + config.streamName = configs.Get("server", "stream_name", "unicast"); + config.maxBufSize = configs.GetInteger("server", "max_buf_size", 2000000); + config.maxPacketSize = configs.GetInteger("server", "max_packet_size", 1500); + config.isHttpEnable = configs.GetBoolean("server", "http_ebable", false); + config.httpPort = configs.GetInteger("server", "http_port", 8000); + config.bitrate = configs.GetInteger("server", "bitrate", 2000); + } + + OutPacketBuffer::maxSize = config.maxBufSize; + LOG(NOTICE, "setting OutPacketbuffer max size to %d (bytes)", OutPacketBuffer::maxSize); + + scheduler_ = BasicTaskScheduler::createNew(); + env_ = BasicUsageEnvironment::createNew(*scheduler_); +} + +CamRTSPServer::~CamRTSPServer() +{ + Medium::close(server_); + + for (auto &src : allocatedVideoSources) { + if(src) + Medium::close(src); + } + + env_->reclaim(); + if (scheduler_) + delete scheduler_; + + transcoders_.clear(); + allocatedVideoSources.clear(); + watcher_ = 0; + + LOG(NOTICE, "RTSP server has been destructed!"); +} + +void CamRTSPServer::stopServer() +{ + LOG(NOTICE, "Stop server is involed!"); + watcher_ = 's'; +} + +void CamRTSPServer::run() +{ + // create server listening on the specified RTSP port + server_ = RTSPServer::createNew(*env_, config.rtspPort); + if (!server_) { + LOG(ERROR, "Failed to create RTSP server: %s", env_->getResultMsg()); + exit(1); + } + + LOG(NOTICE, "Server has been created on port %d", config.rtspPort); + + if (config.isHttpEnable) { + auto ret = server_->setUpTunnelingOverHTTP(config.httpPort); + if (ret) { + LOG(NOTICE, "Enable HTTP tunneling over:%d", config.httpPort); + } + } + + LOG(INFO, "Creating media session for each transcoder"); + for (auto &transcoder : transcoders_) { + addMediaSession(transcoder, config.streamName.c_str(), "stream description"); + } + + env_->taskScheduler().doEventLoop(&watcher_); // do not return; +} + +void CamRTSPServer::announceStream(ServerMediaSession *sms, const std::string &deviceName) +{ + auto url = server_->rtspURL(sms); + LOG(NOTICE, "Play the stream of the %s, url: %s", deviceName.c_str(), url); + delete[] url; +} + +void CamRTSPServer::addMediaSession(std::shared_ptr transoder, const std::string &streamName, + const std::string &streamDesc) +{ + LOG(INFO, "Adding media session for camera: %s", transoder->getConfig().device_name.c_str()); + + // create framed source for camera + auto framedSource = CamFramedSource::createNew(*env_, *transoder); + + // store it in order to cleanup after + allocatedVideoSources.push_back(framedSource); + + // store it in order to replicator for the framed source ** + auto replicator = StreamReplicator::createNew(*env_, framedSource, False); + + // create media session with the specified topic and descripton + auto sms = ServerMediaSession::createNew(*env_, streamName.c_str(), "stream information", + streamDesc.c_str(), False, "a=fmtp:96\n"); + + // add unicast subsession + sms->addSubsession(CamUbicastServerMediaSubsession::createNew(*env_, replicator, config.bitrate, config.maxPacketSize)); + + server_->addServerMediaSession(sms); + + announceStream(sms, transoder->getConfig().device_name); +} + +void CamRTSPServer::addTranscoder(std::shared_ptr transcoder) +{ + transcoders_.emplace_back(transcoder); +} \ No newline at end of file diff --git a/src/CamUnicastServerMediaSubsession.cc b/src/CamUnicastServerMediaSubsession.cc new file mode 100644 index 0000000..7de15e2 --- /dev/null +++ b/src/CamUnicastServerMediaSubsession.cc @@ -0,0 +1,45 @@ +#include "CamUnicastServerMediaSubsession.h" +#include "log.h" + +CamUbicastServerMediaSubsession * +CamUbicastServerMediaSubsession::createNew(UsageEnvironment &env, + StreamReplicator *replicator, + size_t estBitrate, size_t udpDatagramSize) +{ + return new CamUbicastServerMediaSubsession(env, replicator, estBitrate, udpDatagramSize); +} + +CamUbicastServerMediaSubsession::CamUbicastServerMediaSubsession(UsageEnvironment &env, + StreamReplicator *replicator, + size_t estBitrate, + size_t udpDatagramSize) : + OnDemandServerMediaSubsession(env, False), + replicator_(replicator), + estBitrate_(estBitrate), + udpDatagramSize_(udpDatagramSize) +{ + LOG(INFO, "Unicast media subsession with UDP datagram size of %d\n" \ + "\tand estimated bitrate of %d (kbps) is created", udpDatagramSize, estBitrate); + +} + +FramedSource * +CamUbicastServerMediaSubsession::createNewStreamSource(unsigned clientSessionId, unsigned &estBitrate) +{ + estBitrate = static_cast (this->estBitrate_); + + auto source = replicator_->createStreamReplica(); + + // only discrete frames are being sent (w/o start code bytes) + return H264VideoStreamDiscreteFramer::createNew(envir(), source); +} + +RTPSink * +CamUbicastServerMediaSubsession::createNewRTPSink(Groupsock *rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + FramedSource *inputSource) +{ + auto sink = H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); + sink->setPacketSizes(static_cast (udpDatagramSize_), static_cast(udpDatagramSize_)); + return sink; +} diff --git a/src/DeCompress.cc b/src/DeCompress.cc new file mode 100644 index 0000000..1c490b5 --- /dev/null +++ b/src/DeCompress.cc @@ -0,0 +1,45 @@ +#include "DeCompress.h" +#include +#include +#include "log.h" + +DeCompress::DeCompress() +{ + m_handler = tjInitDecompress(); +} + +DeCompress::~DeCompress() +{ + tjDestroy(m_handler); +} + + +int DeCompress::tjpeg2yuv(unsigned char *jpeg_buffer, int jpeg_size, unsigned char **yuv_buffer, int *yuv_size) { + + int subsample, colorspace; + int flags = 0; + int padding = 4; // 1或4均可,但不能是0 + int ret = 0; + tjDecompressHeader3(m_handler, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace); + //LOG(INFO, "w: %d h: %d subsample: %d color: %d\n", width, height, 1, colorspace); + + yuv_type = subsample; + + *yuv_size = tjBufSizeYUV2(width, padding, height, subsample); + *yuv_buffer = (unsigned char *)malloc(*yuv_size); + if (*yuv_buffer == NULL) + { + LOG(ERROR, "malloc buffer for rgb failed.\n"); + return -1; + } + + ret = tjDecompressToYUV2(m_handler, jpeg_buffer, jpeg_size, *yuv_buffer, width, + padding, height, TJFLAG_FASTDCT); + if (ret < 0) + { + LOG(INFO,"compress to jpeg failed: %s\n", tjGetErrorStr()); + return -1; + } + + return ret; +} diff --git a/src/INIReader.cc b/src/INIReader.cc new file mode 100644 index 0000000..13499e5 --- /dev/null +++ b/src/INIReader.cc @@ -0,0 +1,137 @@ +#include +#include +#include +#include "ini.h" +#include "INIReader.h" + +using std::string; + +INIReader::INIReader(const string& filename) +{ + _error = ini_parse(filename.c_str(), ValueHandler, this); +} + +INIReader::INIReader(const char *buffer, size_t buffer_size) +{ + string content(buffer, buffer_size); + _error = ini_parse_string(content.c_str(), ValueHandler, this); +} + +int INIReader::ParseError() const +{ + return _error; +} + +string INIReader::Get(const string& section, const string& name, const string& default_value) const +{ + string key = MakeKey(section, name); + // Use _values.find() here instead of _values.at() to support pre C++11 compilers + return _values.count(key) ? _values.find(key)->second : default_value; +} + +string INIReader::GetString(const string& section, const string& name, const string& default_value) const +{ + const string str = Get(section, name, ""); + return str.empty() ? default_value : str; +} + +long INIReader::GetInteger(const string& section, const string& name, long default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + long n = strtol(value, &end, 0); + return end > value ? n : default_value; +} + +INI_API int64_t INIReader::GetInteger64(const std::string& section, const std::string& name, int64_t default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + int64_t n = strtoll(value, &end, 0); + return end > value ? n : default_value; +} + +unsigned long INIReader::GetUnsigned(const string& section, const string& name, unsigned long default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + unsigned long n = strtoul(value, &end, 0); + return end > value ? n : default_value; +} + +INI_API uint64_t INIReader::GetUnsigned64(const std::string& section, const std::string& name, uint64_t default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + uint64_t n = strtoull(value, &end, 0); + return end > value ? n : default_value; +} + +double INIReader::GetReal(const string& section, const string& name, double default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + double n = strtod(value, &end); + return end > value ? n : default_value; +} + +bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const +{ + string valstr = Get(section, name, ""); + // Convert to lower case to make string comparisons case-insensitive + std::transform(valstr.begin(), valstr.end(), valstr.begin(), + [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); + if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") + return true; + else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") + return false; + else + return default_value; +} + +bool INIReader::HasSection(const string& section) const +{ + const string key = MakeKey(section, ""); + std::map::const_iterator pos = _values.lower_bound(key); + if (pos == _values.end()) + return false; + // Does the key at the lower_bound pos start with "section"? + return pos->first.compare(0, key.length(), key) == 0; +} + +bool INIReader::HasValue(const string& section, const string& name) const +{ + string key = MakeKey(section, name); + return _values.count(key); +} + +string INIReader::MakeKey(const string& section, const string& name) +{ + string key = section + "=" + name; + // Convert to lower case to make section/name lookups case-insensitive + std::transform(key.begin(), key.end(), key.begin(), + [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); + return key; +} + +int INIReader::ValueHandler(void* user, const char* section, const char* name, + const char* value) +{ + if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled + return 1; + INIReader* reader = static_cast(user); + string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) + reader->_values[key] += "\n"; + reader->_values[key] += value ? value : ""; + return 1; +} \ No newline at end of file diff --git a/src/MThread.cc b/src/MThread.cc new file mode 100644 index 0000000..fdd2ac6 --- /dev/null +++ b/src/MThread.cc @@ -0,0 +1,56 @@ +#include "MThread.h" +#include "log.h" + + +MThread::MThread() {} + +MThread::~MThread() +{ + if (!this->isStoped()) { + this->stop(); + } + + if (this->th.joinable()) { + this->th.join(); + } +} + +void MThread::start() +{ + std::thread thr(&MThread::run, this); + this->th = std::move(thr); +} + +void MThread::stop() +{ + this->stopState = true; + if (this->th.joinable()) { + this->th.join(); + } +} + +void MThread::join() +{ + this->th.join(); +} +void MThread::detach() +{ + this->th.detach(); +} + +void MThread::sleep(int sec) +{ + std::this_thread::sleep_for(std::chrono::seconds(sec)); +} + +std::thread::id MThread::getId() +{ + return this->th.get_id(); +} + + +bool MThread::isStoped() +{ + return this->stopState; +} + diff --git a/src/RkEncoder.cc b/src/RkEncoder.cc new file mode 100644 index 0000000..c2aeb05 --- /dev/null +++ b/src/RkEncoder.cc @@ -0,0 +1,179 @@ +#include "RkEncoder.h" +#include "log.h" +#include +#include + +#define MPP_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) + +RkEncoder::RkEncoder(Encoder_Param_t param) : m_param(param) +{ + m_param.hor_stride = MPP_ALIGN(m_param.width, 16); + m_param.ver_stride = MPP_ALIGN(m_param.height, 16); + + if (m_param.fmt <= MPP_FMT_YUV422SP_VU) { + m_param.hor_stride; + m_param.frame_size = m_param.hor_stride * m_param.ver_stride * 2; + } + else if (m_param.fmt <= MPP_FMT_YUV422_VYUY) { + m_param.hor_stride *= 2; + m_param.frame_size = m_param.hor_stride * m_param.ver_stride; + } + else { + m_param.frame_size = m_param.hor_stride * m_param.ver_stride * 4; + } + LOG(INFO, "frame_size %d", m_param.frame_size); +} + +RkEncoder::~RkEncoder() +{ +} + +int RkEncoder::init() +{ + MppEncCfg cfg; + MppCodingType type = MPP_VIDEO_CodingAVC; + int ret = 0; + + ret = mpp_create(&m_contex, &m_mpi); + if (ret) { + LOG(ERROR, "mpp create failed. error:%d", ret); + return -1; + } + + ret = mpp_init(m_contex, MPP_CTX_ENC, type); + if (ret) { + LOG(ERROR, "mpp init failed. error:%d", ret); + return -1; + } + + ret = mpp_enc_cfg_init(&cfg); + if (ret || !cfg) { + LOG(ERROR, "mpp_enc_cfg_init failed error:%d", ret); + return -1; + } + + mpp_buffer_get(NULL, &m_buffer, m_param.frame_size); + + /* fix input / output frame rate */ + mpp_enc_cfg_set_s32(cfg, "rc:fps_in_flex", 0); + mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", m_param.fps); + mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denom", 1); + mpp_enc_cfg_set_s32(cfg, "rc:fps_out_flex", 0); + mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", m_param.fps); + mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denom", 1); + + /* setup bitrate for different rc_mode */ + mpp_enc_cfg_set_s32(cfg, "rc:bps_target", 14400); + + mpp_enc_cfg_set_s32(cfg, "prep:width", m_param.width); + mpp_enc_cfg_set_s32(cfg, "prep:height", m_param.height); + mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", m_param.hor_stride); + mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", m_param.ver_stride); + mpp_enc_cfg_set_s32(cfg, "prep:format", m_param.fmt); + + mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_FIXQP); + mpp_enc_cfg_set_s32(cfg, "rc:qp_init", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:qp_max", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:qp_min", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:qp_max_i", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:qp_min_i", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:qp_ip", 0); + mpp_enc_cfg_set_s32(cfg, "rc:fqp_min_i", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:fqp_max_i", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:fqp_min_p", m_param.fix_qp); + mpp_enc_cfg_set_s32(cfg, "rc:fqp_max_p", m_param.fix_qp); + + mpp_enc_cfg_set_s32(cfg, "h264:profile", 100); + /* + * H.264 level_idc parameter + * 10 / 11 / 12 / 13 - qcif@15fps / cif@7.5fps / cif@15fps / cif@30fps + * 20 / 21 / 22 - cif@30fps / half-D1@@25fps / D1@12.5fps + * 30 / 31 / 32 - D1@25fps / 720p@30fps / 720p@60fps + * 40 / 41 / 42 - 1080p@30fps / 1080p@30fps / 1080p@60fps + * 50 / 51 / 52 - 4K@30fps + */ + mpp_enc_cfg_set_s32(cfg, "h264:level", 40); + mpp_enc_cfg_set_s32(cfg, "h264:cabac_en", 1); + mpp_enc_cfg_set_s32(cfg, "h264:cabac_idc", 0); + mpp_enc_cfg_set_s32(cfg, "h264:trans8x8", 1); + + ret = m_mpi->control(m_contex, MPP_ENC_SET_CFG, cfg); + if (ret) { + LOG(ERROR, "mpi control MPP_ENC_SET_CFG failed error:%d", ret); + mpp_enc_cfg_deinit(cfg); + return -1; + } + + mpp_dec_cfg_deinit(cfg); + return ret; +} + +int RkEncoder::encode(void *inbuf, int insize, uint8_t *outbuf) +{ + int ret = 0; + + ret = mpp_frame_init(&m_frame); + if (ret) { + LOG(ERROR, "mpp_frame_init failed\n"); + return -1; + } + + mpp_frame_set_width(m_frame, m_param.width); + mpp_frame_set_height(m_frame, m_param.height); + mpp_frame_set_hor_stride(m_frame, m_param.hor_stride); + mpp_frame_set_ver_stride(m_frame, m_param.ver_stride); + mpp_frame_set_fmt(m_frame, m_param.fmt); + mpp_frame_set_eos(m_frame, 1); + mpp_frame_set_buffer(m_frame, m_buffer); + + LOG(INFO, "insize %d", insize); + memcpy(mpp_buffer_get_ptr(m_buffer), inbuf, insize); + + ret = m_mpi->encode_put_frame(m_contex, m_frame); + if (ret) { + LOG(ERROR, "encoder put frame failed error:%d", ret); + return -1; + } + + ret = m_mpi->encode_get_packet(m_contex, &m_packet); + if (ret) { + LOG(ERROR, "encoder get packet failed error:%d", ret); + return -1; + } + + MppPacket m_extra_info; + ret = m_mpi->control(m_contex, MPP_ENC_GET_EXTRA_INFO, &m_extra_info); + if (ret) { + LOG(ERROR, "mpi control enc get extra info failed"); + return -1; + } + + void *ptr_extra_info = mpp_packet_get_data(m_extra_info); + size_t len_extra_info = mpp_packet_get_length(m_extra_info); + memcpy(outbuf, ptr_extra_info, len_extra_info); + mpp_packet_deinit(&m_extra_info); + + void *ptr_video_data = mpp_packet_get_data(m_packet); + size_t len_video_data = mpp_packet_get_length(m_packet); + memcpy(outbuf + len_extra_info, ptr_video_data, len_video_data); + mpp_packet_deinit(&m_packet); + + size_t len = len_extra_info + len_video_data; + return len; +} + +int RkEncoder::startCode3(uint8_t *buf) +{ + if (buf[0] == 0 && buf[1] == 0 && buf[2] == 1) + return 1; + else + return 0; +} + +int RkEncoder::startCode4(uint8_t *buf) +{ + if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 1) + return 1; + else + return 0; +} diff --git a/src/ini.c b/src/ini.c new file mode 100644 index 0000000..ba19fac --- /dev/null +++ b/src/ini.c @@ -0,0 +1,291 @@ +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + size_t max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(start, NULL); + if (*end) + *end = '\0'; + rstrip(start); +#endif + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} \ No newline at end of file diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..6f3910a --- /dev/null +++ b/src/main.cc @@ -0,0 +1,36 @@ +#include +#include "CamRTSPServer.h" +#include "INIReader.h" +#include "log.h" + +int LogLevel; +int main() +{ + INIReader configs("./configs/config.ini"); + if (configs.ParseError() < 0) { + printf("read config failed."); + exit(1); + } else { + std::string level = configs.Get("log", "level", "NOTICE"); + if (level == "NOTICE") { + initLogger(NOTICE); + } + else if (level == "INFO") { + initLogger(INFO); + } + else if (level == "ERROR") { + initLogger(ERROR); + } + } + + try { + CamRTSPServer server; + server.addTranscoder(std::make_shared()); + server.run(); + } catch (const std::invalid_argument &err) { + LOG(ERROR, err.what()); + } + + LOG(INFO, "test."); + return 0; +} diff --git a/src/transcoder.cc b/src/transcoder.cc new file mode 100644 index 0000000..6f566ee --- /dev/null +++ b/src/transcoder.cc @@ -0,0 +1,115 @@ +#include +#include +#include "transcoder.h" +#include "INIReader.h" +#include "log.h" + +TransCoder::TransCoder() +{ + init(); +} + +TransCoder::~TransCoder() +{ +} + +void TransCoder::init() +{ + std::list formatList; + v4l2IoType ioTypeIn = IOTYPE_MMAP; + MppFrameFormat mpp_fmt; + + INIReader configs("./configs/config.ini"); + if (configs.ParseError() < 0) { + LOG(ERROR, "read video config failed."); + return; + } else { + config.width = configs.GetInteger("video", "width", 640); + config.height = configs.GetInteger("video", "height", 480); + config.fps = configs.GetInteger("video", "fps", 30); + config.fix_qp = configs.GetInteger("video", "fix_qp", 23); + config.format = configs.Get("video", "format", "UNKNOWN"); + config.device_name = configs.Get("video", "device", "/dev/video0"); + + if (config.format == "YUY2") { + formatList.push_back(V4L2_PIX_FMT_YUYV); + mpp_fmt = MPP_FMT_YUV422_YUYV; + } else if (config.format == "MJPEG") { + formatList.push_back(V4L2_PIX_FMT_MJPEG); + mpp_fmt = MPP_FMT_YUV422P; + } + } + + V4L2DeviceParameters param(config.device_name.c_str(), + formatList, + config.width, + config.height, + config.fps, + ioTypeIn, + DEBUG); + + capture = V4l2Capture::create(param); + encodeData = (uint8_t *)malloc(capture->getBufferSize()); + + decompress = new DeCompress(); + + Encoder_Param_t encoder_param{ + mpp_fmt, + config.width, + config.height, + 0, + 0, + 0, + config.fps, + config.fix_qp}; + + rk_encoder = new RkEncoder(encoder_param); + rk_encoder->init(); +} + +void TransCoder::run() +{ + timeval tv; + for (;;) + { + tv.tv_sec = 1; + tv.tv_usec = 0; + int startCode = 0; + int ret = capture->isReadable(&tv); + if (ret == 1) { + uint8_t buffer[capture->getBufferSize()]; + int resize = capture->read((char *)buffer, sizeof(buffer)); + frameSize = 0; + if (config.format == "MJPEG") { + ret = decompress->tjpeg2yuv(buffer, resize, &yuv_buf, &yuv_size); + if (ret >= 0) { + frameSize = rk_encoder->encode(yuv_buf, yuv_size, encodeData); + free(yuv_buf); + } + } else if (config.format == "YUY2") { + frameSize = rk_encoder->encode(buffer, resize, encodeData); + } + LOG(INFO, "encodeData size %d", frameSize); + if (rk_encoder->startCode3(encodeData)) + startCode = 3; + else + startCode = 4; + + if (onEncodedDataCallback && frameSize > 0) { + onEncodedDataCallback(std::vector(encodeData + startCode, encodeData + frameSize)); + } + } else if (ret == -1) { + LOG(ERROR, "stop %s", strerror(errno)); + } + } +} + +TransCoder::Config_t const &TransCoder::getConfig() const +{ + return config; +} + +void TransCoder::setOnEncoderDataCallback(std::function &&)> callback) +{ + onEncodedDataCallback = std::move(callback); +} diff --git a/thirdparty/libjpeg-turbo b/thirdparty/libjpeg-turbo new file mode 160000 index 0000000..76a815f --- /dev/null +++ b/thirdparty/libjpeg-turbo @@ -0,0 +1 @@ +Subproject commit 76a815fe9f81919b1e8bf0eedc7fd4cd43757607 diff --git a/thirdparty/libv4l2cc b/thirdparty/libv4l2cc new file mode 160000 index 0000000..86989e1 --- /dev/null +++ b/thirdparty/libv4l2cc @@ -0,0 +1 @@ +Subproject commit 86989e1caa9ce7cf895fe1ef3316a9dab41a9485