pax_global_header00006660000000000000000000000064147325215570014525gustar00rootroot0000000000000052 comment=56f93f7a65affc20c1a668ea4e92828bc16edef3 libmegapixels-0.2.0/000077500000000000000000000000001473252155700143515ustar00rootroot00000000000000libmegapixels-0.2.0/.gitignore000066400000000000000000000000501473252155700163340ustar00rootroot00000000000000/cmake-* /build *.raw *.data /doc/_buildlibmegapixels-0.2.0/.gitlab-ci.yml000066400000000000000000000022131473252155700170030ustar00rootroot00000000000000build: stage: build image: debian:bookworm-slim before_script: - apt-get update && apt-get -y install libconfig-dev meson script: - meson build --buildtype release --werror - ninja -C build - cd build && meson test --no-rebuild artifacts: paths: - build docs: stage: build image: alpine:edge before_script: - apk add --no-cache python3 py3-sphinx make script: - cd docs - make html lint: stage: test dependencies: - build image: debian:bookworm-slim before_script: - apt-get update && apt-get -y install libconfig-dev bash script: - bash ./configtest.sh tests: stage: test image: alpine:edge before_script: - apk add --no-cache build-base meson samurai scdoc libconfig-dev linux-headers script: - meson setup unittests --buildtype release --werror - ninja -C unittests - cd unittests && meson test --no-rebuild pages: stage: deploy image: alpine:edge before_script: - apk add --no-cache python3 py3-sphinx make script: - cd docs - make html - mv _build/html ../public artifacts: paths: - public environment: production libmegapixels-0.2.0/CMakeLists.txt000066400000000000000000000031151473252155700171110ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.23) project(libmegapixels C) set(LIBRARY_VERSION_MAJOR 0) set(LIBRARY_VERSION_STRING 0.1) set(CMAKE_C_STANDARD 23) set(CMAKE_C_VISIBILITY_PRESET hidden) add_library(megapixels SHARED include/libmegapixels.h src/findconfig.c src/parse.c src/mode.c src/pipeline.c src/log.c src/util.c src/convert.c src/aaa.c src/flash.c config.h) set_target_properties(megapixels PROPERTIES VERSION ${LIBRARY_VERSION_STRING} SOVERSION ${LIBRARY_VERSION_MAJOR} PUBLIC_HEADER include/libmegapixels.h) target_include_directories(megapixels PUBLIC include) target_link_libraries(megapixels "config") add_executable(findconfig util/findconfig.c) target_include_directories(findconfig PUBLIC include) target_link_libraries(findconfig PUBLIC megapixels) add_executable(getframe util/getframe.c) target_include_directories(getframe PUBLIC include) target_link_libraries(getframe PUBLIC megapixels) add_executable(sensorprofile util/sensorprofile.c) target_include_directories(sensorprofile PUBLIC include) target_link_libraries(sensorprofile PUBLIC megapixels) add_executable(configlint util/configlint.c) target_include_directories(configlint PUBLIC include) target_link_libraries(configlint PUBLIC megapixels) add_compile_definitions(SYSCONFDIR="/etc") add_compile_definitions(DATADIR="/usr/share") include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_custom_command( OUTPUT config.h COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/config.h DEPENDS CMakeLists.txt COMMENT "Generating dummy header for meson compat..." ) add_subdirectory(tests)libmegapixels-0.2.0/LICENSE000066400000000000000000001045151473252155700153640ustar00rootroot00000000000000 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 . libmegapixels-0.2.0/config/000077500000000000000000000000001473252155700156165ustar00rootroot00000000000000libmegapixels-0.2.0/config/pine64,pinephone-pro.conf000066400000000000000000000156651473252155700223670ustar00rootroot00000000000000Version = 1; Make: "PINE64"; Model: "PinePhone Pro"; Rear: { SensorDriver: "imx258"; BridgeDriver: "rkisp1"; Modes: ( { Width: 4208; Height: 3120; Rate: 30; Format: "RGGB8"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "imx258", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, {Type: "Mode", Entity: "imx258", Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_csi"}, {Type: "Mode", Entity: "rkisp1_isp"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "RGGB8", SkipTry: true}, {Type: "Crop", Entity: "rkisp1_isp"}, # Cropped by default {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, # Cropped by default {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} ); }, # { # Doesn't work well in Megapixels yet # Width: 4208; # Height: 3120; # Rate: 30; # Format: "RGGB10"; # Rotate: 270; # FocalLength: 3.33; # FNumber: 3.0; # Pipeline: ( # {Type: "Link", From: "imx258", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, # {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, # {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, # {Type: "Mode", Entity: "imx258", Format: "RGGB10P"}, # {Type: "Mode", Entity: "rkisp1_csi"}, # {Type: "Mode", Entity: "rkisp1_isp"}, # {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "RGGB10"}, # {Type: "Crop", Entity: "rkisp1_isp"}, # Cropped by default # {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, # Cropped by default # {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, # {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} # ); # }, { Width: 1048; Height: 780; Rate: 30; Format: "RGGB8"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "imx258", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, {Type: "Mode", Entity: "imx258", Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_csi"}, {Type: "Mode", Entity: "rkisp1_isp"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "RGGB8", SkipTry: true}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1}, {Type: "Crop", Entity: "rkisp1_isp"}, {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, {Type: "Crop", Entity: "rkisp1_resizer_mainpath"} ); } ); }; Front: { SensorDriver: "ov8858"; BridgeDriver: "rkisp1"; FlashDisplay: true; Modes: ( { Width: 3264; Height: 2448; Rate: 30; Format: "BGGR8"; Rotate: 90; FocalLength: 3.33; FNumber: 3.0; Mirror: true; Pipeline: ( {Type: "Link", From: "ov8858", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, {Type: "Mode", Entity: "ov8858", Format: "BGGR10"}, {Type: "Mode", Entity: "rkisp1_csi"}, {Type: "Mode", Entity: "rkisp1_isp"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "BGGR8"}, {Type: "Crop", Entity: "rkisp1_isp"}, # Cropped by default {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, # Cropped by default {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} ); }, # { # Doesn't work well in Megapixels yet # Width: 3264; # Height: 2448; # Rate: 30; # Format: "BGGR10"; # Rotate: 90; # FocalLength: 3.33; # FNumber: 3.0; # Mirror: true; # Pipeline: ( # {Type: "Link", From: "ov8858", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, # {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, # {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, # {Type: "Mode", Entity: "ov8858", Format: "BGGR10"}, # {Type: "Mode", Entity: "rkisp1_csi"}, # {Type: "Mode", Entity: "rkisp1_isp"}, # {Type: "Mode", Entity: "rkisp1_isp", Pad: 2}, # {Type: "Crop", Entity: "rkisp1_isp"}, # Cropped by default # {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, # Cropped by default # {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, # {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} # ); # }, { Width: 1048; Height: 780; Rate: 30; Format: "BGGR8"; Rotate: 90; FocalLength: 3.33; FNumber: 3.0; Mirror: true; Pipeline: ( {Type: "Link", From: "ov8858", FromPad: 0, To: "rkisp1_csi", ToPad: 0}, {Type: "Link", From: "rkisp1_csi", FromPad: 1, To: "rkisp1_isp", ToPad: 0}, {Type: "Link", From: "rkisp1_isp", FromPad: 2, To: "rkisp1_resizer_mainpath", ToPad: 0}, {Type: "Mode", Entity: "ov8858", Format: "BGGR10", Width: 1632, Height: 1224}, {Type: "Mode", Entity: "rkisp1_csi"}, {Type: "Mode", Entity: "rkisp1_isp"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "BGGR8", Width: 1048, Height: 780}, {Type: "Crop", Entity: "rkisp1_isp"}, # Cropped by default {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, # Cropped by default {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} ); } ); };libmegapixels-0.2.0/config/pine64,pinephone.conf000066400000000000000000000040571473252155700215620ustar00rootroot00000000000000Version = 1; Make: "PINE64"; Model: "PinePhone"; Rear: { SensorDriver: "ov5640"; BridgeDriver: "sun6i-csi"; FlashPath: "/sys/class/leds/white:flash"; IsoMin: 100; IsoMax: 64000; Modes: ( { Width: 2592; Height: 1944; Rate: 15; Format: "BGGR8"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi-bridge", ToPad: 0}, {Type: "Mode", Entity: "ov5640"}, {Type: "Mode", Entity: "sun6i-csi-bridge"} ); }, { Width: 1280; Height: 720; Rate: 30; Format: "BGGR8"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi-bridge", ToPad: 0}, {Type: "Mode", Entity: "ov5640"}, {Type: "Mode", Entity: "sun6i-csi-bridge"} ); }, { Width: 1280; Height: 720; Rate: 30; Format: "YUYV"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi-bridge", ToPad: 0}, {Type: "Mode", Entity: "ov5640"}, {Type: "Mode", Entity: "sun6i-csi-bridge"} ); } ); }; Front: { SensorDriver: "gc2145"; BridgeDriver: "sun6i-csi"; FlashDisplay: true; Modes: ( { Width: 1280; Height: 960; Rate: 60; Format: "BGGR8"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "gc2145", FromPad: 0, To: "sun6i-csi-bridge", ToPad: 0}, {Type: "Mode", Entity: "gc2145"}, {Type: "Mode", Entity: "sun6i-csi-bridge"} ); } ); }; libmegapixels-0.2.0/config/pine64,pinetab.conf000066400000000000000000000025261473252155700212160ustar00rootroot00000000000000Version = 1; Make: "PINE64"; Model: "PinePhone"; Rear: { SensorDriver: "ov5640"; BridgeDriver: "sun6i-csi"; FlashPath: "/sys/class/leds/white:flash"; IsoMin: 100; IsoMax: 64000; Modes: ( { Width: 2592; Height: 1944; Rate: 15; Format: "BGGR8"; Rotate: 270; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi", ToPad: 0}, {Type: "Mode", Entity: "ov5640", Width: 2592, Height: 1944, Format: "BGGR8"} ); }, { Width: 1280; Height: 720; Rate: 30; Format: "BGGR8"; FocalLength: 3.33; FNumber: 3.0; Pipeline: ( {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi", ToPad: 0} ); } ); }; Front: { SensorDriver: "gc2145"; BridgeDriver: "sun6i-csi"; FlashDisplay: true; Modes: ( { Width: 1280; Height: 960; Rate: 60; Format: "BGGR8"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "gc2145", FromPad: 0, To: "sun6i-csi", ToPad: 0} ); } ); };libmegapixels-0.2.0/config/purism,librem5.conf000066400000000000000000000026301473252155700213410ustar00rootroot00000000000000Version = 1; Make: "Purism"; Model: "Librem 5"; Rear: { SensorDriver: "s5k3l6xx"; BridgeDriver: "imx7-csi"; Modes: ( { Width: 4208; Height: 3120; Rate: 15; Format: "GRBG8"; Rotate: 270; Transfer: "srgb"; # All the links on this platform are immutable Pipeline: ( {Type: "Mode", Entity: "s5k3l6xx"}, {Type: "Mode", Entity: "imx8mq-mipi-csi2"}, {Type: "Mode", Entity: "csi"} ); }, { Width: 1052; Height: 780; Rate: 30; Format: "GRBG8"; Rotate: 270; Transfer: "srgb"; Pipeline: ( {Type: "Mode", Entity: "s5k3l6xx"}, {Type: "Mode", Entity: "imx8mq-mipi-csi2"}, {Type: "Mode", Entity: "csi"} ); } ); }; Front: { SensorDriver: "hi846"; BridgeDriver: "imx7-csi"; FlashDisplay: true; Modes: ( { Width: 1632; Height: 1224; Rate: 30; Format: "GBRG10"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Mode", Entity: "hi846"}, {Type: "Mode", Entity: "imx8mq-mipi-csi2"}, {Type: "Mode", Entity: "csi"} ); } ); };libmegapixels-0.2.0/config/samsung,midas.conf000066400000000000000000000016061473252155700212370ustar00rootroot00000000000000Version = 1; Make: "Samsung"; Model: "Galaxy S III & Note II"; Rear: { SensorDriver: "S5C73M3"; BridgeDriver: "s5p-fimc-md"; FlashPath: "/sys/class/leds/white:flash"; Modes: ( { Width: 1536; Height: 864; Rate: 30; Format: "YUYV"; Rotate: 270; Pipeline: ( {Type: "Link", From: "s5p-mipi-csis.0", FromPad: 1, To: "FIMC.0", ToPad: 0}, {Type: "Mode", Entity: "S5C73M3", Pad: 0, Format: "VYUY", SkipTry: true, ExactName: true}, {Type: "Mode", Entity: "s5p-mipi-csis.0", Pad: 0, Format: "VYUY"}, {Type: "Mode", Entity: "s5p-mipi-csis.0", Pad: 1, Format: "VYUY"}, {Type: "Mode", Entity: "FIMC.0", Pad: 0, Format: "VYUY"}, {Type: "Mode", Entity: "FIMC.0", Pad: 2, Format: "YUYV"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,daisy.conf000066400000000000000000000032021473252155700210560ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Mi A2 Lite"; Front: { SensorDriver: "ov5675"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 2592; Height: 1944; Rate: 30; Format: "GRBG10p"; Rotate: 270; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1296; Height: 972; Rate: 30; Format: "GRBG10p"; Rotate: 270; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,markw.conf000066400000000000000000000062371473252155700211010ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Redmi 4 Prime"; Front: { SensorDriver: "ov5670"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 2592; Height: 1944; Rate: 30; Format: "GRBG10p"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5670"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1296; Height: 972; Rate: 30; Format: "GRBG10p"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5670"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; Rear: { SensorDriver: "s5k3l8"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 4208; Height: 3120; Rate: 30; Format: "GRBG10p"; Rotate: 270; Pipeline: ( {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "s5k3l8"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1280; Height: 720; Rate: 120; Format: "GRBG10p"; Rotate: 270; Pipeline: ( {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "s5k3l8"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,mido.conf000066400000000000000000000031201473252155700206740ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Redmi Note 4"; Rear: { SensorDriver: "s5k3l8"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 4208; Height: 3120; Rate: 30; Format: "GRBG10p"; Rotate: 270; Pipeline: ( {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "s5k3l8"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1280; Height: 720; Rate: 120; Format: "GRBG10p"; Rotate: 270; Pipeline: ( {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "s5k3l8"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,rosy.conf000066400000000000000000000031771473252155700207540ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Redmi 5"; Front: { SensorDriver: "ov5675"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 2592; Height: 1944; Rate: 30; Format: "GRBG10p"; Rotate: 270; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1296; Height: 972; Rate: 30; Format: "GRBG10p"; Rotate: 270; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,scorpio.conf000066400000000000000000000015541473252155700214330ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Mi Note 2"; Rear: { SensorDriver: "imx318"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 3840; Height: 2160; Rate: 30; Format: "RGGB10p"; Rotate: 90; Pipeline: ( {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "imx318"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/config/xiaomi,vince.conf000066400000000000000000000032021473252155700210510ustar00rootroot00000000000000Version = 1; Make: "Xiaomi"; Model: "Redmi 5 Plus"; Front: { SensorDriver: "ov5675"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 2592; Height: 1944; Rate: 30; Format: "GRBG10p"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); }, { Width: 1296; Height: 972; Rate: 30; Format: "GRBG10p"; Rotate: 90; Mirror: true; Pipeline: ( {Type: "Link", From: "msm_csiphy2", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "ov5675"}, {Type: "Mode", Entity: "msm_csiphy2"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, {Type: "Mode", Entity: "msm_vfe0_rdi0"} ); } ); }; libmegapixels-0.2.0/configtest.sh000077500000000000000000000006261473252155700170610ustar00rootroot00000000000000#!/bin/bash RED='\033[0;31m' BOLDRED='\033[1;31m' GREEN='\033[0;32m' CLEAR='\033[0m' ret=0 for cfg in $(find config/ -type f -iname *.conf) do printf "%s: " $cfg errors=$(./build/megapixels-configlint "$cfg" 2>&1) if [ -z "$errors" ] ; then echo -e "${GREEN}PASS${CLEAR}" else ret=1 echo -e "${BOLDRED}FAIL${CLEAR}" echo -e "${RED}$errors${CLEAR}" echo "" fi done exit $ret libmegapixels-0.2.0/docs/000077500000000000000000000000001473252155700153015ustar00rootroot00000000000000libmegapixels-0.2.0/docs/.gitignore000066400000000000000000000000071473252155700172660ustar00rootroot00000000000000/_buildlibmegapixels-0.2.0/docs/Makefile000066400000000000000000000011721473252155700167420ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) libmegapixels-0.2.0/docs/building.rst000066400000000000000000000027551473252155700176410ustar00rootroot00000000000000Building ======== Libmegapixels is build through Meson. the Cmake files present in the repository are only for IDE integration and does not install the binaries and required files. The libmegapixels library depends on libconfig and linux-headers only. The build steps for libmegapixels is just the normal Meson procedure for building and installing a library: .. code-block:: shell-session $ git clone https://gitlab.com/megapixels-org/libmegapixels.git Switch over to your favorite release branch after this $ meson setup build The Meson build system Version: 1.3.1 ... etcetera $ cd build $ meson compile compiler output here... $ sudo meson install Installing libmegapixels.so.1.0.0 to /usr/local/lib Installing megapixels-findconfig to /usr/local/bin Installing megapixels-getframe to /usr/local/bin Installing /..snip.../include/libmegapixels.h to /usr/local/include Installing /..snip.../libmegapixels.pc to /usr/local/lib/pkgconfig Installing /..snip.../config/pine64,pinephone.conf to /usr/local/share/megapixels/config Installing symlink pointing to libmegapixels.so.1.0.0 to /usr/local/lib/libmegapixels.so.1 Installing symlink pointing to libmegapixels.so.1 to /usr/local/lib/libmegapixels.so This will install the :code:`libmegapixels.so.1.0.0` library globally. This usually ends up in :code:`/usr/local/lib` which is not by default in the search path for libraries. This will also install the libmegapixels utilities and bundled config files.libmegapixels-0.2.0/docs/conf.py000066400000000000000000000016671473252155700166120ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'libmegapixels' copyright = '2023, Martijn Braam' author = 'Martijn Braam' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'alabaster' html_static_path = ['_static'] libmegapixels-0.2.0/docs/config.rst000066400000000000000000000371041473252155700173050ustar00rootroot00000000000000Config file format ================== Libmegapixels gets its camera pipeline definitions from config files stored in one of the config directories. For finding config files libmegapixels will scan a few preconfigured path from the "compatible" names of the device. The first part of this puzzle is the compatible names, these can be found at runtime from the :code:`/proc/device-tree/compatible` file or otherwise from the top-most "compatible" node in the device tree. As an example the Xiaomi Mi Note 2 will be used. The compatible list for the device tree for this device is at https://github.com/torvalds/linux/blob/b85ea95d086471afb4ad062012a4d73cd328fa86/arch/arm64/boot/dts/qcom/msm8996pro-xiaomi-scorpio.dts#L17 The relevant line here is this: .. code-block:: device-tree compatible = "xiaomi,scorpio", "qcom,msm8996pro", "qcom,msm8996"; The second part is the lookup paths. They are checked in this order: * :code:`config/$model.conf` in the current working directory * :code:`/etc/megapixels/config/$model.conf` * :code:`/usr/share/megapixels/config/$model.conf` So on this device the file will be looked up in this order: * :code:`config/xiaomi,scorpio.conf` * :code:`/etc/megapixels/config/xiaomi,scorpio.conf` * :code:`/usr/share/megapixels/config/xiaomi,scorpio.conf` * :code:`config/qcom,msm8996pro.conf` * :code:`/etc/megapixels/config/qcom,msm8996pro.conf` * :code:`/usr/share/megapixels/config/qcom,msm8996pro.conf` * :code:`config/qcom,msm8996.conf` * :code:`/etc/megapixels/config/qcom,msm8996.conf` * :code:`/usr/share/megapixels/config/qcom,msm8996.conf` The first matched file will be used. The file format --------------- The config files are parsed by `libconfig `_ and define all the cameras present on the specific device and the modes that are possible on this hardware. An example of a minimal config is this: .. code-block:: config Version = 1; Make: "Xiaomi"; Model: "Scorpio"; Rear: { SensorDriver: "imx318"; BridgeDriver: "qcom-camss"; Modes: ( { Width: 3840; Height: 2160; Rate: 30; Format: "RGGB10"; Rotate: 90; Pipeline: ( {Type: "Link", From: "imx318", FromPad: 0, To: "msm_csiphy0", ToPad: 0}, {Type: "Link", From: "msm_csiphy0", FromPad: 1, To: "msm_csid0", ToPad: 0}, {Type: "Link", From: "msm_csid0", FromPad: 1, To: "msm_ispif0", ToPad: 0}, {Type: "Link", From: "msm_ispif0", FromPad: 1, To: "msm_vfe0_rdi0", ToPad: 0}, {Type: "Mode", Entity: "imx318"}, {Type: "Mode", Entity: "msm_csiphy0"}, {Type: "Mode", Entity: "msm_csid0"}, {Type: "Mode", Entity: "msm_ispif0"}, ); }, ); }; The only top-level keys are Version, Make and Model. The Version should always be 1 and is reserved for possible future breaking updates. The Make and Model keys set the human-readable name for the device which will be written in the EXIF data of pictures as he camera model. Camera definitions ------------------ All the other top-level keys in the config file are definitions for cameras. The name is not very important and is for documentation purposes. The keys in the camera block are: * :code:`BridgeDriver`: The name of the driver that provides the /dev/media and /dev/video node for this camera * :code:`SensorDriver`: The name of the sensor entity in the media graph * :code:`FlashPath`: optional, defines the path to a LED device to use as the flash for this camera. * :code:`FlashDisplay`: optional, defines that there is no LED that can be used as flash, use the display as the light-source instead. The camera section requires a :code:`SensorDriver` and :code:`BridgeDriver` key. The BridgeDriver will identify the bridge device (eg. /dev/video0) and media device (eg. /dev/media0). This combination should uniquely identify a single camera on the device. These values can be found using the :code:`media-ctl` utility. .. code-block:: shell-session $ media-ctl --device 0 --print-topology Media controller API version 6.1.14 Media device information ------------------------ driver qcom-camss model Qualcomm Camera Subsystem serial bus info platform:a34000.camss hw revision 0x0 driver version 6.1.14 Device topology - entity 1: msm_csiphy0 (2 pads, 5 links) type V4L2 subdev subtype Unknown flags 0 device node name /dev/v4l-subdev0 pad0: Sink [fmt:UYVY8_2X8/1920x1080 field:none colorspace:srgb] <- "imx318 3-001a":0 [ENABLED,IMMUTABLE] pad1: Source [fmt:UYVY8_2X8/1920x1080 field:none colorspace:srgb] -> "msm_csid0":0 [] -> "msm_csid1":0 [] -> "msm_csid2":0 [] -> "msm_csid3":0 [] [ Removed A LOT of entities here for brevity ] - entity 226: imx318 3-001a (1 pad, 1 link) type V4L2 subdev subtype Sensor flags 0 device node name /dev/v4l-subdev19 pad0: Source [fmt:SRGGB10_1X10/5488x4112@1/30 field:none colorspace:raw xfer:none] -> "msm_csiphy0":0 [ENABLED,IMMUTABLE] - entity 228: ak7375 3-000c (0 pad, 0 link) type V4L2 subdev subtype Lens flags 0 device node name /dev/v4l-subdev20 The BridgeDriver config node needs to match the driver listed at the top of the output. In this case :code:`qcom-camss`. For the SensorDriver the entity needs to be found that represents the sensor. This is mentioned as :code:`subtype Sensor` in the list and in the example is entity 226. The entity will be matched by the name listed directly after the number which is :code:`imx318 3-001a`. The full name doesn't need to be used but is matched by prefix to avoid the config needing to hardcode the i2c bus number for the sensor which might change on enabling/disabling kernel modules. Camera modes ------------ Every camera block requires to have at least one mode block but can have multiple. This is required since different usecases of the camera need different modes. Usually the maximum resolution of the sensor will be used for taking pictures but at that resolution the framerate will generally be too low for realtime preview. For example the OV5640 sensor can run at 2592x1944 resolution for decently sharp pictures, but at that resolution is limited to 15fps which will be quite annoying for the live-preview and not fast enough for smooth video recording. This is why the PinePhone defines a 2592x1944@15fps mode and a 1280x720@30fps mode for the preview. The Megapixels application will default to the highest defined resolution for taking pictures and will take the resolution closest to the display resolution as the preview mode. The required configuration keys for the mode are: * :code:`Width` and :code:`Height` for the resolution of image _after_ any processing by the ISP. * :code:`Rate` the frame interval in frames-per-second for this mode. * :code:`Format` is the pixelformat for the data after ISP processing. The optional keys are: * :code:`Transfer` sets the transfer curve of the image data, can be "srgb" or "raw". * :code:`Rotate` defines the rotation for matching up the orientation of the sensor and the display of the device. * :code:`Mirror` can be set to :code:`true` to flip the image for the front-facing sensors. * :code:`FocalLength` sets the effective focal length of the camera in this mode. Sensors generally crop the image slightly depending on the resolution which changes the focal length for the mode. This is only used for EXIF metadata and is defined in milimeters. * :code:`FNumber` sets the aperture size of the lens. 3.0 will mean F/3 as aperture size, this is only used for EXIF Mode pipeline ------------- To make the mode actually work the pipeline has to be added. This is a small script that defines the ioctls that need to be sent to the drivers to set up all the hardware in this mode. This is a full scripting system to deal with inconsistencies in the Linux drivers, especially the staging drivers for sensors. The pipeline script is a list of command objects. The specific command is set using the :code:`Type` key in this object. All other keys in the command object depend on the specific command. The possible commands are: * `Link`_ * `Mode`_ * `Rate`_ * `Crop`_ The commands all accept the name of an entity from MediaCtl and these are prefix matched by default. This is because for things like sensors there might be an i2c bus ID in the name of the entity and the bus ID is not stable across reboots and kernel changes. There is one case though where the prefix match doesn't work when the name of one entity is the exact prefix of one with a longer name. For this case prefix matching can be disabled with the `ExactName` key in every command. Cascading values ^^^^^^^^^^^^^^^^ In order to not repeat the values for resolutions, formats and framerates in every command values are cascaded internally if nothing is specified. These cascading values are initialized to the resolution and mode for the Mode block that contains this pipeline. If any command in the pipeline hardcodes another value then that new default will also be used for the following commands. This means that if the pipeline script is defined in strict source->sink order the minimum amount of repetitions of these values are required. For example this pipeline: .. code-block:: config Modes: ( { Width: 4208; Height: 3120; Rate: 30; Format: "RGGB8"; Pipeline: ( {Type: "Mode", Entity: "imx258", Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_csi"}, {Type: "Mode", Entity: "rkisp1_isp"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Format: "RGGB8"}, {Type: "Crop", Entity: "rkisp1_isp"}, {Type: "Crop", Entity: "rkisp1_isp", Pad: 2}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1} ); }, The resolution and framerate is never defined in any of the pipeline commands and are inherited from the values set in the mode. The pixelformat is specified twice here since the ISP will convert between RGGB10P and RGGB8 formats. Once to set the format of the sensor and once on the ISP to return it to the value of the Mode block. This pipeline will be interpreted as: .. code-block:: config {Type: "Mode", Entity: "imx258", Width: 4208, Height: 3120, Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_csi", Width: 4208, Height: 3120, Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_isp", Width: 4208, Height: 3120, Format: "RGGB10P"}, {Type: "Mode", Entity: "rkisp1_isp", Pad: 2, Width: 4208, Height: 3120, Format: "RGGB8"}, {Type: "Crop", Entity: "rkisp1_isp", Width: 4208, Height: 3120, Top: 0, Left: 0}, {Type: "Crop", Entity: "rkisp1_isp", Pad: 2, , Width: 4208, Height: 3120, Top: 0, Left: 0}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Width: 4208, Height: 3120, Format: "RGGB8"}, {Type: "Mode", Entity: "rkisp1_resizer_mainpath", Pad: 1, Width: 4208, Height: 3120, Format: "RGGB8"} Link ^^^^ The Link command is the most important one, this defines the media pipeline itself by specifying which links should be created and broken. This is equivalent to the :code:`MEDIA_IOC_SETUP_LINK` ioctl. Parameters: * :code:`From` source entity name * :code:`To` sink entity name * :code:`FromPad` pad index on the source entity * :code:`ToPad` pad index on the sink entity * :code:`ExactName` match the exact From/To names instead of just a prefix. Example: .. code-block:: config {Type: "Link", From: "ov5640", FromPad: 0, To: "sun6i-csi-bridge", ToPad: 0}, Is equivalent to: .. code-block:: shell-session $ media-ctl -l "'ov5640':0 -> 'sun6i-csi-bridge':0 [1]" Mode ^^^^ The mode sets the V4L2 format on the entity pad, some drivers in V4L2 will cascade these values down the pipeline and some will need the mode to be set on every entity in the pipeline to make the pipeline start streaming correctly. The most minimal option is specifying just the entity name here and the rest of the values are used from the resolution and format specified in the mode block this pipeline is in, or the values of the previous Mode command if one hardcodes a value. This is for pipelines where an ISP changes the format or resolution halfway through the pipeline. This command is equivalent to the :code:`VIDIOC_SUBDEV_S_FMT` ioctl. Parameters: * :code:`Entity` media entity to set the mode on * :code:`Pad` pad index on the entity, defaults to 0 * :code:`Width` Resolution width, if not specified it will use value of the previous command * :code:`Height` Resolution width, if not specified it will use value of the previous command * :code:`Format` Pixelformat for the mode, if not specified it will use the value of the previous command * :code:`SkipTry` disables the use of the VIDIOC_SUBDEV_S_FMT ioctl with V4L2_SUBDEV_FORMAT_TRY. This is for drivers that don't implement the V4L2_SUBDEV_FORMAT_TRY functionality or where it returns the wrong data. * :code:`ExactName` match the exact Entity name instead of just a prefix. Example: .. code-block:: config {Type: "Mode", Entity: "ov5640", Pad: 0, Width: 640, Height: 480, Format: "BGGR8"}, {Type: "Mode", Entity: "sun6i-csi-bridge"}, Is equivalent to: .. code-block:: shell-session $ media-ctl --set-v4l2 '"ov5640":0 [fmt:SBBGR8_1X8/640x480]' $ media-ctl --set-v4l2 '"sun6i-csi-bridge":0 [fmt:SBBGR8_1X8/640x480]' Rate ^^^^ The mode sets the framerate on the entity for the drivers that believe in such things, some drivers will require this to be set and some will reject this ioctl altogether. Just like the Mode command the rate value will cascade from the rate specified in the Mode block that contains this pipeline and previous Rate calls that changes the pipeline framerate. This command is equivalent to the :code:`VIDIOC_SUBDEV_S_FRAME_INTERVAL` ioctl. Parameters: * :code:`Entity` media entity to set the mode on * :code:`Rate` frame interval, if not specified it will use value of the previous command * :code:`ExactName` match the exact Entity name instead of just a prefix. Example: .. code-block:: config {Type: "Rate", Entity: "ov5640", Rate: 30}, Is equivalent to: .. code-block:: shell-session // Only the @30 part here is relevant $ media-ctl --set-v4l2 '"ov5640":0 [fmt:SBBGR8_1X8/640x480@30]' Crop ^^^^ Some drivers will allow cropping out a section of the frame, usually this is to remove dummy pixels from the edge of the sensor. Some drivers will internally set the cropping automatically when setting the capture resolution and some will fail to start until you specify the cropping to be 0 on all edges instead of having that as default. There are also drivers that don't update the crop values when changing resolution so the crop ioctl needs to be called. This command is equivalent to the :code:`VIDIOC_SUBDEV_S_CROP` ioctl. Parameters: * :code:`Entity` media entity to set the crop on * :code:`Pad` pad index on the entity, defaults to 0 * :code:`Width` Crop width, if not specified it will use the mode width * :code:`Height` Crop height, if not specified it will use the mode height * :code:`Top` Top offset of the cropped region, defaults to 0 * :code:`Left` Left offset of the cropped region, defaults to 0 * :code:`ExactName` match the exact Entity name instead of just a prefix. Example: .. code-block:: config {Type: "Crop", Entity: "rkisp1_isp", Width: 640, Height: 480, Top: 0, Left: 0}, Is equivalent to: .. code-block:: shell-session // Only the last crop: part here is relevant $ media-ctl --set-v4l2 '"rkisp1_isp":0 [fmt:SBBGR8_1X8/640x480 crop:(0,0)/640x480]' libmegapixels-0.2.0/docs/faq.rst000066400000000000000000000026341473252155700166070ustar00rootroot00000000000000Frequently Asked Questions ========================== What is libmegapixels? This is a library for accessing V4L2 cameras on modern ARM platforms that use the media-request pipeline to do routing of cameras to the SoC hardware to process the pixels in various ways. It's intended to be called instead of opening the plain V4L2 device so it can configure the hardware to a specific mode and then it hands over the V4L2 handles to let your application do the normal Linux camera procedures. What is the correct way to display the name 'libmegapixels'? Just follow the correct grammar rules for your language. Being difficult about the capitalisation of project names is just a massive waste of time. How is libmegapixels different from using V4L2 Libmegapixels is just a glue layer on top of V4L2 to make writing applications that use the new media request pipeline a lot easier. Once the pipeline has been configured libmegapixels will just hand over a file descriptor for a regular V4L2 device to get the frames from. Does this mean the camera stack is completely open? Yes, libmegapixels does not support the Android mess that is closed-source userspace sensor drivers and also provides an open source AAA algorithm. Using closed modules in libmegapixels is prohibited, but it's only a library for accessing V4L2 so whatever happens in the applications is up to the applications.libmegapixels-0.2.0/docs/index.rst000066400000000000000000000011301473252155700171350ustar00rootroot00000000000000.. libmegapixels documentation master file, created by sphinx-quickstart on Sun Dec 3 14:27:28 2023. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. libmegapixels's documentation! ============================== The libmegapixels library is an abstraction library between the V4L2 system in linux and the Megapixels GTK application. It helps with abstracting away the media graph interface in Linux for ARM platforms with cameras. .. toctree:: :maxdepth: 2 :caption: Contents: building overview config faqlibmegapixels-0.2.0/docs/overview.rst000066400000000000000000000130101473252155700176740ustar00rootroot00000000000000Overview ======== Libmegapixels is a library for accessing V4L2 cameras in Linux. It's the camera set-up code originally written for Megapixels but split off and improved as a library to make it easier to debug and integrate. The way you'd normally open a webcam in Linux and capture frames is roughly: - Open a :code:`/dev/video*` file descriptor - Run the :code:`VIDIOC_QUERYCAP` ioctl to check the device has the :code:`V4L2_CAP_VIDEO_CAPTURE` flag and check which methods of capturing frames are valid. - Run the :code:`VIDIOC_S_FMT` and :code:`VIDIOC_G_FMT` ioctls a few times to narrow down a format to use. - Set-up mmapped video capture on the device and run :code:`VIDIOC_STREAMON` to start the capture - Get frames from the kernel This works for UVC webcams you usually encounter on Linux devices since these are relatively simple devices. If you try to use the camera on a modern phone the whole workflow is completely different. Instead of dealing with just a video device you also have to deal with a :code:`/dev/media*` device for setting up hardware pipelines and then deal with various :code:`/dev/v4l-subdev*` devices to configure all the nodes in that media pipeline. One (or more) of the nodes in that media pipeline will be the regular old :code:`/dev/video*` device again that will provide your application with the frames, but due to the way the pipelines work a lot of the normal auto-configuration things applications do no longer work. In the new pipelines the video device only deals with getting the video frames into userspace. Due to this the video device does not actually know what formats and resolutions are valid so using the old ioctls to query this information is useless. One of the v4l-subdev devices will be representing the sensor in the device, this does know what modes are available but to know that mode will work you need to make sure all the nodes in the pipeline can run at that mode and all these nodes need to be manually configured. But in reality it's even more complicated becaues this only describes what resolutions and modes the drivers for these components support and it does not account for limitations in the actual hardware like not all the MIPI lanes being connected between the sensor and the SoC limiting the possible bandwidth, or even more basic bandwidth limitations due to the length of PCB traces. Using libmegapixels ------------------- The solution for this mess in Megapixels which is now transferred to a library is using config files that provide the pipeline information. This hardcodes a series of known working modes and also provides the metadata for producing high quality pictuers from the data coming from the pipeline. The general use of libmegapixels is this: .. code-block:: c #include // Create the empty device config object libmegapixels_devconfig *config = {0}; libmegapixels_init(&config); // Find the config file path for this device char configpath[PATH_MAX]; int ret = libmegapixels_find_config(configpath); // Load the config libmegapixels_load_file(config, configpath); // Additionally load UVC cameras with autodetection libmegapixels_load_uvc(config); printf("Found %d cameras\n", config->count); // Open the first camera libmegapixels_camera *camera = config->cameras[camera_id]; libmegapixels_open(camera); printf("Camera has %d modes\n", camera->num_modes); // Select the first mode libmegapixels_mode *mode = camera->modes[mode_idx]; struct v4l2_format format = {0}; libmegapixels_select_mode(camera, mode, &format); // Now you can do regular V4L2 things on the camera with the // supplied FDs. video_fd for the /dev/video device. ioctl(camera->video_fd, VIDIOC_QUERYCAP, &cap) Libmegapixels replaces most of the original V4L2 setup code and provides a filled in :code:`struct v4l2_format` to get all the exact mode information. To pick a camera and mode you'd iterate over the found cameras and then over the mode structs to find something that matches the needs of the application. The camera struct provides the FDs for the various devices you might need: - The :code:`video_fd` field has the FD for :code:`/dev/video*` for actually getting the frames. - The :code:`sensor_fd` field is for the :code:`/dev/v4l-subdev*` device that represents the sensor. This is for setting camera V4L2 controls while capturing. - The :code:`media_fd` field is for the :code:`/dev/media*` device that has the pipeline for the current camera. This is mostly for the libmegapixels internals. The config files ---------------- The libmegapixels library ships with configuration files for some devices. The device configuration can be stored in multiple locations so it can be overridden by packagers and end users. /usr/share/megapixels/config/%model.conf This is the location where the config files from packages are stored. This is also where the build script from libmegapixels will place the internal config files here. /etc/megapixels/config/%model.conf This is the place for extra and/or override config files $cwd/config/%model.conf The config is loaded from the current working directory to make testing and debugging the code easier and to run it from the root of the git repository. The %model argument in the path is referring to the device-tree compatible names of the device. This can be found in :code:`/proc/device-tree/compatible`. This stores the name of the hardware seperated by null-bytes in decreasing specificity order. Libmegapixels will check all of them in order for every location above. libmegapixels-0.2.0/include/000077500000000000000000000000001473252155700157745ustar00rootroot00000000000000libmegapixels-0.2.0/include/libmegapixels.h000066400000000000000000000107771473252155700210060ustar00rootroot00000000000000#ifndef LIBMEGAPIXELS_HEADER #define LIBMEGAPIXELS_HEADER #include #include #include #define EXPORT __attribute__((__visibility__("default"))) EXPORT int libmegapixels_find_config(ssize_t maxlen, char *configfile); EXPORT int libmegapixels_find_config_verbose(ssize_t maxlen, char *configfile, int print); #define LIBMEGAPIXELS_CMD_LINK 1 #define LIBMEGAPIXELS_CMD_MODE 2 #define LIBMEGAPIXELS_CMD_CROP 3 #define LIBMEGAPIXELS_CMD_INTERVAL 4 #define LIBMEGAPIXELS_CFA_NONE 0 #define LIBMEGAPIXELS_CFA_BGGR 1 #define LIBMEGAPIXELS_CFA_GBRG 2 #define LIBMEGAPIXELS_CFA_GRBG 3 #define LIBMEGAPIXELS_CFA_RGGB 4 #define LIBMEGAPIXELS_XFER_RAW 0 #define LIBMEGAPIXELS_XFER_SRGB 8 #define LIBMEGAPIXELS_FLASH_SCREEN 0 #define LIBMEGAPIXELS_FLASH_V4L 1 #define LIBMEGAPIXELS_FLASH_LED 2 struct _lmp_cmd { int type; const char *entity_from; const char *entity_to; int pad_from; int pad_to; int width; int height; int top; int left; int format; int rate; uint32_t entity_from_id; int pad_from_id; uint32_t entity_to_id; int pad_to_id; int skip_try; int exact_name; }; typedef struct _lmp_cmd libmegapixels_cmd; struct _lmp_mode { int width; int height; int rate; int format; int rotation; int mirrored; int xfer; double focal_length; double f_number; uint32_t v4l_pixfmt; uint32_t media_busfmt; int num_cmds; libmegapixels_cmd **cmds; }; typedef struct _lmp_mode libmegapixels_mode; struct _lmp_subdev { char *path; int fd; uint32_t entity_id; }; typedef struct _lmp_subdev libmegapixels_subdev; struct _lmp_camera { int index; char *name; char *sensor_name; char *bridge_name; char *media_path; char *sensor_path; char *video_path; char *flash_path; char *lens_path; int flash_type; int media_fd; int sensor_fd; int video_fd; int flash_fd; int lens_fd; int num_modes; libmegapixels_mode **modes; libmegapixels_mode *current_mode; int num_handles; libmegapixels_subdev **handles; }; typedef struct _lmp_camera libmegapixels_camera; struct _lmp_device_config { char *path; const char *make; const char *model; int count; int loaded_config; int loaded_uvc; libmegapixels_camera **cameras; }; typedef struct _lmp_device_config libmegapixels_devconfig; typedef struct _lmp_aaa { float matrix1[9]; float matrix2[9]; float avg_r; float avg_g; float avg_b; int exposure; float temp; float tint; int focus; int blacklevel; } libmegapixels_aaa_stats; EXPORT int libmegapixels_init(libmegapixels_devconfig **config); EXPORT void libmegapixels_loglevel(int loglevel); EXPORT int libmegapixels_load_file(libmegapixels_devconfig *config, const char *file); EXPORT int libmegapixels_load_file_lint(libmegapixels_devconfig *config, const char *file, int linting); EXPORT int libmegapixels_load_uvc(libmegapixels_devconfig *config); EXPORT int libmegapixels_open(libmegapixels_camera *camera); EXPORT void libmegapixels_close(libmegapixels_camera *camera); EXPORT unsigned int libmegapixels_select_mode(libmegapixels_camera *camera, libmegapixels_mode *mode, struct v4l2_format *format); EXPORT char * libmegapixels_v4l_pixfmt_to_string(uint32_t pixfmt); EXPORT char * libmegapixels_format_name(int format); EXPORT const char * libmegapixels_format_cfa(int format); EXPORT const char * libmegapixels_format_cfa_pattern(int format); EXPORT uint32_t libmegapixels_mode_raw_width_to_width(int index, uint32_t width); EXPORT uint32_t libmegapixels_mode_width_to_padding(int index, uint32_t width); EXPORT uint32_t libmegapixels_mode_width_to_bytes(int index, uint32_t width); EXPORT uint32_t libmegapixels_format_to_v4l_pixfmt(int index); EXPORT uint32_t libmegapixels_format_to_media_busfmt(int index); EXPORT uint32_t libmegapixels_format_bits_per_pixel(int format); EXPORT int libmegapixels_mode_equals(libmegapixels_mode *a, libmegapixels_mode *b); EXPORT int libmegapixels_v4l_pixfmt_to_index(uint32_t pixfmt); EXPORT int libmegapixels_mode_is_packed(int index); // The AAA API is considered completely unstable and shouldn't be used yet EXPORT void libmegapixels_aaa_init(libmegapixels_aaa_stats *stats); EXPORT void libmegapixels_aaa_set_matrix(libmegapixels_aaa_stats *stats, const float matrix1[9], const float matrix2[9]); EXPORT void libmegapixels_aaa_software_statistics(libmegapixels_aaa_stats *stats, const unsigned int *frame, int width, int height); EXPORT void libmegapixels_flash_on(libmegapixels_camera *camera); EXPORT void libmegapixels_flash_off(libmegapixels_camera *camera); #endif libmegapixels-0.2.0/meson.build000066400000000000000000000057011473252155700165160ustar00rootroot00000000000000project('libmegapixels', 'c', version: '0.2.0', license: 'GPL', ) libconfig = dependency('libconfig') # We use libtool-version numbers because it's easier to understand. # Before making a release, the libmegapixels_so_* # numbers should be modified. The components are of the form C:R:A. # a) If binary compatibility has been broken (eg removed or changed interfaces) # change to C+1:0:0. # b) If interfaces have been changed or added, but binary compatibility has # been preserved, change to C+1:0:A+1 # c) If the interface is the same as the previous version, change to C:R+1:A libmegapixels_lt_c=1 libmegapixels_lt_r=0 libmegapixels_lt_a=0 libmegapixels_so_version = '@0@.@1@.@2@'.format((libmegapixels_lt_c - libmegapixels_lt_a), libmegapixels_lt_a, libmegapixels_lt_r) inc = include_directories('include') install_headers('include/libmegapixels.h') lib_src = [ 'src/findconfig.c', 'src/log.c', 'src/mode.c', 'src/util.c', 'src/parse.c', 'src/pipeline.c', 'src/aaa.c', 'src/flash.c', ] libmegapixels = shared_library('megapixels', lib_src, version: libmegapixels_so_version, include_directories: inc, dependencies: libconfig, install: true ) pkg_mod = import('pkgconfig') pkg_mod.generate(libraries: libmegapixels, version: libmegapixels_so_version, name: 'libmegapixels', filebase: 'libmegapixels', description: 'The camera control bits from Megapixels') conf = configuration_data() conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir'))) conf.set_quoted('SYSCONFDIR', get_option('sysconfdir')) configure_file( output: 'config.h', configuration: conf) executable('megapixels-findconfig', 'util/findconfig.c', link_with: libmegapixels, include_directories: inc, install: true, ) executable('megapixels-getframe', 'util/getframe.c', link_with: libmegapixels, include_directories: inc, install: true, ) executable('megapixels-configlint', 'util/configlint.c', link_with: libmegapixels, include_directories: inc, install: true, ) cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) executable('megapixels-sensorprofile', 'util/sensorprofile.c', link_with: libmegapixels, include_directories: inc, install: true, dependencies: m_dep ) install_data( [ 'config/pine64,pinephone.conf', 'config/pine64,pinephone-pro.conf', 'config/pine64,pinetab.conf', 'config/purism,librem5.conf', 'config/samsung,midas.conf', 'config/xiaomi,daisy.conf', 'config/xiaomi,markw.conf', 'config/xiaomi,mido.conf', 'config/xiaomi,rosy.conf', 'config/xiaomi,scorpio.conf', 'config/xiaomi,vince.conf', ], install_dir: get_option('datadir') / 'megapixels/config/' ) subdir('tests') libmegapixels-0.2.0/src/000077500000000000000000000000001473252155700151405ustar00rootroot00000000000000libmegapixels-0.2.0/src/aaa.c000066400000000000000000000042421473252155700160300ustar00rootroot00000000000000#include #include "libmegapixels.h" float clamp_float(float value, float min, float max) { if (value > max) return max; if (value < min) return min; return value; } void libmegapixels_aaa_init(libmegapixels_aaa_stats *stats) { float identity[] = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; libmegapixels_aaa_set_matrix(stats, identity, identity); } void libmegapixels_aaa_set_matrix(libmegapixels_aaa_stats *stats, const float matrix1[9], const float matrix2[9]) { for (size_t i = 0; i < 9; i++) { stats->matrix1[i] = matrix1[i]; stats->matrix2[i] = matrix2[i]; } } void libmegapixels_aaa_software_statistics(libmegapixels_aaa_stats *stats, const unsigned int *frame, const int width, const int height) { unsigned int bright = 0; unsigned int too_bright = 0; unsigned int too_dark = 0; unsigned int total = width * height; unsigned long long sum_r = 0, sum_g = 0, sum_b = 0; for (ssize_t p = 0; p < width * height; p++) { unsigned int r = (frame[p] >> 0) & 0xff; unsigned int g = (frame[p] >> 8) & 0xff; unsigned int b = (frame[p] >> 16) & 0xff; unsigned int y = (r + g + b) / 3; if (y > 220) { too_bright++; } if (y > 180) { bright++; } if (y < 2) { too_dark++; } // Whitebalance on the midrange pixels only if (y > 75 && y < 200) { sum_r += r; sum_g += g; sum_b += b; } } // Exposure calculations unsigned int p_bright = (bright * 100) / total; unsigned int p_too_bright = (too_bright * 100) / total; unsigned int p_dark = (too_dark * 100) / total; stats->exposure = 0; if (p_bright < 1) { stats->exposure = 1; } if (p_too_bright > 8) { stats->exposure = -1; } stats->blacklevel = 0; if (p_dark < 1) { stats->blacklevel = -1; } if (p_dark > 3) { stats->blacklevel = 1; } // Whitebalance calculations float r = (float) sum_r / total; float g = (float) sum_g / total; float b = (float) sum_b / total; stats->avg_r = (r * stats->matrix1[0]) + (g * stats->matrix1[1]) + (b * stats->matrix1[2]); stats->avg_g = (r * stats->matrix1[3]) + (g * stats->matrix1[4]) + (b * stats->matrix1[5]); stats->avg_b = (r * stats->matrix1[6]) + (g * stats->matrix1[7]) + (b * stats->matrix1[8]); } libmegapixels-0.2.0/src/convert.c000066400000000000000000000005561473252155700167720ustar00rootroot00000000000000#include #include "libmegapixels.h" int libmegapixels_convert_to_rgb(uint32_t v4l_pixfmt, uint8_t *in_data, long in_size, uint8_t *out_data, long out_size) { switch (v4l_pixfmt) { case V4L2_PIX_FMT_SBGGR8: case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_SGRBG8: case V4L2_PIX_FMT_SRGGB8: break; default: return 0; } return 1; }libmegapixels-0.2.0/src/findconfig.c000066400000000000000000000032111473252155700174070ustar00rootroot00000000000000#include #include #include #include "config.h" #ifndef SYSCONFDIR #define SYSCONFDIR "/etc" #endif #ifndef DATADIR #define DATADIR "/usr/share" #endif static int find_device_by_model(ssize_t maxlen, char *conffile, char *model, int print) { // Check config/%model.conf in the current working directory snprintf(conffile, maxlen, "config/%s.conf", model); if (print) { printf("- %s\n", conffile); } if (access(conffile, F_OK) != -1) { return 1; } // Check user overridden /etc/megapixels/config/%model.conf snprintf(conffile, maxlen, "%s/megapixels/config/%s.conf", SYSCONFDIR, model); if (print) { printf("- %s\n", conffile); } if (access(conffile, F_OK) != -1) { return 1; } // Check packaged /usr/share/megapixels/config/%model.conf snprintf(conffile, maxlen, "%s/megapixels/config/%s.conf", DATADIR, model); if (print) { printf("- %s\n", conffile); } if (access(conffile, F_OK) != -1) { return 1; } if (print) { printf("no config for '%s'\n", model); } return 0; } int libmegapixels_find_config_verbose(ssize_t maxlen, char *configfile, int print) { char model[512]; FILE *fp; if (access("/proc/device-tree/compatible", F_OK) == -1) { return 0; } fp = fopen("/proc/device-tree/compatible", "r"); char *modelptr = model; while (1) { int c = fgetc(fp); if (c == EOF) { return 0; } *(modelptr++) = (char) c; if (c == 0) { if (find_device_by_model(maxlen, configfile, model, print)) { return 1; } modelptr = model; } } } int libmegapixels_find_config(ssize_t maxlen, char *configfile) { return libmegapixels_find_config_verbose(maxlen, configfile, 0); }libmegapixels-0.2.0/src/flash.c000066400000000000000000000034551473252155700164100ustar00rootroot00000000000000#include #include #include #include #include "flash.h" #include "util.h" // Flash path is used for both the V4L path, and the LED path, and can be overwritten by a later stage, so memory is first freed void libmegapixels_flash_cleanup_flash_path(libmegapixels_camera *camera) { if (camera->flash_path != NULL) { free(camera->flash_path); camera->flash_path = NULL; } } // Given the LED path (e.g., /sys/class/leds/white:flash), it'll append /flash_strobe at the end char * libmegapixels_flash_get_led_path(const char *path) { char *result = malloc(PATH_MAX); if (result == NULL) { return NULL; } snprintf(result, PATH_MAX, "%s/flash_strobe", path); return result; } // Turns on the LED/V4L flash/strobe void libmegapixels_flash_on(libmegapixels_camera *camera) { if (camera->flash_type == LIBMEGAPIXELS_FLASH_LED) { lseek(camera->flash_fd, 0, SEEK_SET); dprintf(camera->flash_fd, "1\n"); } else if (camera->flash_type == LIBMEGAPIXELS_FLASH_V4L) { // Mode must be flash, and strobe source must be software, for strobe to be possible // Strobe source may not exist, which is fine, we ignore the result set_control(camera->flash_fd, V4L2_CID_FLASH_LED_MODE, V4L2_FLASH_LED_MODE_FLASH); set_control(camera->flash_fd, V4L2_CID_FLASH_STROBE_SOURCE, V4L2_FLASH_STROBE_SOURCE_SOFTWARE); // Starts the flash strobe. It will automatically stop set_control(camera->flash_fd, V4L2_CID_FLASH_STROBE, 1); } } // Turns off the LED/V4L flash/strobe, where applicable void libmegapixels_flash_off(libmegapixels_camera *camera) { // Neither LED nor V4L currently needs any action in here, but in the future those or a different method may need to turn flash off here // For example, if flash strobe doesn't work, and we need to manually turn on/off torch mode } libmegapixels-0.2.0/src/flash.h000066400000000000000000000002431473252155700164050ustar00rootroot00000000000000#include char * libmegapixels_flash_get_led_path(const char *path); void libmegapixels_flash_cleanup_flash_path(libmegapixels_camera *camera); libmegapixels-0.2.0/src/log.c000066400000000000000000000014031473252155700160630ustar00rootroot00000000000000#include #include #include #include "log.h" #include "libmegapixels.h" static int loglevel = 0; void init_log(int level) { loglevel = level; char *debuglevel = getenv("LIBMEGAPIXELS_DEBUG"); if (debuglevel != NULL) { char *end; long env_level = strtol(debuglevel, &end, 10); loglevel = (int) env_level; } } void libmegapixels_loglevel(int level) { loglevel = level; } void log_error(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "[libmegapixels] "); vfprintf(stderr, fmt, args); va_end(args); } void log_debug(const char *fmt, ...) { if (loglevel < 2) { return; } va_list args; va_start(args, fmt); fprintf(stderr, "[libmegapixels] "); vfprintf(stderr, fmt, args); va_end(args); }libmegapixels-0.2.0/src/log.h000066400000000000000000000001651473252155700160740ustar00rootroot00000000000000#pragma once void init_log(int level); void log_error(const char *fmt, ...); void log_debug(const char *fmt, ...);libmegapixels-0.2.0/src/mode.c000066400000000000000000000221371473252155700162350ustar00rootroot00000000000000#include #include #include #include #include #include "mode.h" #include "libmegapixels.h" // TODO: The 16 bit formats are imported from millipixels and seem broken static struct libmegapixels_modename mode_lut[] = { { .name = "unsupported", .v4l_pixel_format = 0, .media_bus_format = 0, .bpc = 0, .bpp = 0, .cfa = LIBMEGAPIXELS_CFA_NONE, }, { .name = "BGGR8", .v4l_pixel_format = V4L2_PIX_FMT_SBGGR8, .media_bus_format = MEDIA_BUS_FMT_SBGGR8_1X8, .bpc = 8, .bpp = 8, .cfa = LIBMEGAPIXELS_CFA_BGGR, }, { .name = "GBRG8", .v4l_pixel_format = V4L2_PIX_FMT_SGBRG8, .media_bus_format = MEDIA_BUS_FMT_SGBRG8_1X8, .bpc = 8, .bpp = 8, .cfa = LIBMEGAPIXELS_CFA_GBRG, }, { .name = "GRBG8", .v4l_pixel_format = V4L2_PIX_FMT_SGRBG8, .media_bus_format = MEDIA_BUS_FMT_SGRBG8_1X8, .bpc = 8, .bpp = 8, .cfa = LIBMEGAPIXELS_CFA_GRBG, }, { .name = "RGGB8", .v4l_pixel_format = V4L2_PIX_FMT_SRGGB8, .media_bus_format = MEDIA_BUS_FMT_SRGGB8_1X8, .bpc = 8, .bpp = 8, .cfa = LIBMEGAPIXELS_CFA_RGGB, }, { .name = "BGGR10P", .v4l_pixel_format = V4L2_PIX_FMT_SBGGR10P, .media_bus_format = MEDIA_BUS_FMT_SBGGR10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_BGGR, }, { .name = "GBRG10P", .v4l_pixel_format = V4L2_PIX_FMT_SGBRG10P, .media_bus_format = MEDIA_BUS_FMT_SGBRG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GBRG, }, { .name = "GRBG10P", .v4l_pixel_format = V4L2_PIX_FMT_SGRBG10P, .media_bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GRBG, }, { .name = "RGGB10P", .v4l_pixel_format = V4L2_PIX_FMT_SRGGB10P, .media_bus_format = MEDIA_BUS_FMT_SRGGB10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_RGGB, }, { .name = "BGGR10", .v4l_pixel_format = V4L2_PIX_FMT_SBGGR10, .media_bus_format = MEDIA_BUS_FMT_SBGGR10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_BGGR, }, { .name = "GBRG10", .v4l_pixel_format = V4L2_PIX_FMT_SGBRG10, .media_bus_format = MEDIA_BUS_FMT_SGBRG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GBRG, }, { .name = "GRBG10", .v4l_pixel_format = V4L2_PIX_FMT_SGRBG10, .media_bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GRBG, }, { .name = "RGGB10", .v4l_pixel_format = V4L2_PIX_FMT_SRGGB10, .media_bus_format = MEDIA_BUS_FMT_SRGGB10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_RGGB, }, { .name = "BGGR12", .v4l_pixel_format = V4L2_PIX_FMT_SBGGR12, .media_bus_format = MEDIA_BUS_FMT_SBGGR12_1X12, .bpc = 12, .bpp = 12, .cfa = LIBMEGAPIXELS_CFA_BGGR, }, { .name = "GBRG12", .v4l_pixel_format = V4L2_PIX_FMT_SGBRG12, .media_bus_format = MEDIA_BUS_FMT_SGBRG12_1X12, .bpc = 12, .bpp = 12, .cfa = LIBMEGAPIXELS_CFA_GBRG, }, { .name = "GRBG12", .v4l_pixel_format = V4L2_PIX_FMT_SGRBG12, .media_bus_format = MEDIA_BUS_FMT_SGRBG12_1X12, .bpc = 12, .bpp = 12, .cfa = LIBMEGAPIXELS_CFA_GRBG, }, { .name = "RGGB12", .v4l_pixel_format = V4L2_PIX_FMT_SRGGB12, .media_bus_format = MEDIA_BUS_FMT_SRGGB12_1X12, .bpc = 12, .bpp = 12, .cfa = LIBMEGAPIXELS_CFA_RGGB, }, { .name = "BGGR16", .v4l_pixel_format = V4L2_PIX_FMT_SBGGR16, .media_bus_format = MEDIA_BUS_FMT_SBGGR10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_BGGR, }, { .name = "GBRG16", .v4l_pixel_format = V4L2_PIX_FMT_SGBRG16, .media_bus_format = MEDIA_BUS_FMT_SGBRG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GBRG, }, { .name = "GRBG16", .v4l_pixel_format = V4L2_PIX_FMT_SGRBG16, .media_bus_format =MEDIA_BUS_FMT_SGRBG10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_GRBG, }, { .name = "RGGB16", .v4l_pixel_format = V4L2_PIX_FMT_SRGGB16, .media_bus_format = MEDIA_BUS_FMT_SRGGB10_1X10, .bpc = 10, .bpp = 10, .cfa = LIBMEGAPIXELS_CFA_RGGB, }, { .name = "UYVY", .v4l_pixel_format = V4L2_PIX_FMT_UYVY, .media_bus_format = MEDIA_BUS_FMT_UYVY8_2X8, .bpc = 8, .bpp = 16, .cfa = LIBMEGAPIXELS_CFA_NONE, }, { .name = "YUYV", .v4l_pixel_format = V4L2_PIX_FMT_YUYV, .media_bus_format = MEDIA_BUS_FMT_YUYV8_2X8, .bpc = 8, .bpp = 16, .cfa = LIBMEGAPIXELS_CFA_NONE, }, { .name = "VYUY", .v4l_pixel_format = V4L2_PIX_FMT_VYUY, .media_bus_format = MEDIA_BUS_FMT_VYUY8_2X8, .bpc = 8, .bpp = 16, .cfa = LIBMEGAPIXELS_CFA_NONE, }, { .name = "YVYU", .v4l_pixel_format = V4L2_PIX_FMT_YVYU, .media_bus_format = MEDIA_BUS_FMT_YVYU8_2X8, .bpc = 8, .bpp = 16, .cfa = LIBMEGAPIXELS_CFA_NONE, }, }; int libmegapixels_format_name_to_index(const char *name) { int count = sizeof(mode_lut) / sizeof(mode_lut[0]); for (int i = 0; i < count; i++) { if (strcasecmp(mode_lut[i].name, name) == 0) { return i; } } return 0; } uint32_t libmegapixels_format_to_v4l_pixfmt(int index) { return mode_lut[index].v4l_pixel_format; } uint32_t libmegapixels_format_to_media_busfmt(int index) { return mode_lut[index].media_bus_format; } int libmegapixels_v4l_pixfmt_to_index(uint32_t pixfmt) { int count = sizeof(mode_lut) / sizeof(mode_lut[0]); for (int i = 0; i < count; i++) { if (mode_lut[i].v4l_pixel_format == pixfmt) { return i; } } return 0; } char * libmegapixels_v4l_pixfmt_to_string(uint32_t pixfmt) { int count = sizeof(mode_lut) / sizeof(mode_lut[0]); for (int i = 0; i < count; i++) { if (mode_lut[i].v4l_pixel_format == pixfmt) { return mode_lut[i].name; } } return "unknown"; } char * libmegapixels_format_name(int format) { return mode_lut[format].name; } // mp_pixel_format_bits_per_pixel uint32_t libmegapixels_format_bits_per_pixel(int format) { return mode_lut[format].bpp; } const char * libmegapixels_format_cfa(int format) { switch (mode_lut[format].cfa) { case LIBMEGAPIXELS_CFA_BGGR: return "BGGR"; case LIBMEGAPIXELS_CFA_GBRG: return "GBRG"; case LIBMEGAPIXELS_CFA_GRBG: return "GRBG"; case LIBMEGAPIXELS_CFA_RGGB: return "RGGB"; default: return NULL; } } const char * libmegapixels_format_cfa_pattern(int format) { switch (mode_lut[format].cfa) { case LIBMEGAPIXELS_CFA_BGGR: return "\002\001\001\000"; case LIBMEGAPIXELS_CFA_GBRG: return "\001\002\000\001"; case LIBMEGAPIXELS_CFA_GRBG: return "\001\000\002\001"; case LIBMEGAPIXELS_CFA_RGGB: return "\000\001\001\002"; default: return NULL; } } // Useful for the calculation: https://www.1stvision.com/cameras/IDS/IDS-manuals/en/basics-raw-bayer-pixel-formats.html // Note that BayerGB12 is packed according to that document, but this is likely a mistake uint32_t libmegapixels_mode_width_to_bytes(int index, uint32_t width) { uint32_t bits_per_pixel = mode_lut[index].bpp; // For bpp like 10/12/14, for formats that are not packed, we want to set bpp to something like 16 if (bits_per_pixel % 8 != 0 && !libmegapixels_mode_is_packed(index)) bits_per_pixel = bits_per_pixel + 8 - (bits_per_pixel % 8); uint64_t bits_per_width = width * (uint64_t) bits_per_pixel; uint64_t remainder = bits_per_width % 8; if (remainder == 0) return bits_per_width / 8; return (bits_per_width + 8 - remainder) / 8; } uint32_t libmegapixels_mode_width_to_padding(int index, uint32_t width) { if (mode_lut[index].bpp == 8) { return 0; } uint64_t bytes_per_width = libmegapixels_mode_width_to_bytes(index, width); uint64_t remainder = bytes_per_width % 8; if (remainder == 0) return remainder; return 8 - remainder; } uint32_t libmegapixels_mode_raw_width_to_width(int index, uint32_t width) { switch (mode_lut[index].v4l_pixel_format) { case V4L2_PIX_FMT_SBGGR8: case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_SGRBG8: case V4L2_PIX_FMT_SRGGB8: return width / 2; case V4L2_PIX_FMT_SBGGR10P: case V4L2_PIX_FMT_SGBRG10P: case V4L2_PIX_FMT_SGRBG10P: case V4L2_PIX_FMT_SRGGB10P: return width / 2 * 5; case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_YUYV: return width; default: return 0; } } uint32_t libmegapixels_mode_raw_height_to_height(int index, uint32_t height) { switch (mode_lut[index].v4l_pixel_format) { case V4L2_PIX_FMT_SBGGR8: case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_SGRBG8: case V4L2_PIX_FMT_SRGGB8: case V4L2_PIX_FMT_SBGGR10P: case V4L2_PIX_FMT_SGBRG10P: case V4L2_PIX_FMT_SGRBG10P: case V4L2_PIX_FMT_SRGGB10P: return height / 2; case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_YUYV: return height; default: return 0; } } int libmegapixels_mode_equals(libmegapixels_mode *a, libmegapixels_mode *b) { if (a == NULL || b == NULL) { return 0; } if (a->width != b->width) { return 0; } if (a->height != b->height) { return 0; } if (a->rate != b->rate) { return 0; } if (a->format != b->format) { return 0; } return 1; } int libmegapixels_mode_is_packed(int index) { switch (mode_lut[index].v4l_pixel_format) { case V4L2_PIX_FMT_SBGGR10P: case V4L2_PIX_FMT_SGBRG10P: case V4L2_PIX_FMT_SGRBG10P: case V4L2_PIX_FMT_SRGGB10P: return 1; default: return 0; } } int mode_snprintf(char *buf, size_t maxlen, libmegapixels_mode *mode) { return snprintf(buf, maxlen, "%dx%d@%d %s", mode->width, mode->height, mode->rate, mode_lut[mode->format].name); }libmegapixels-0.2.0/src/mode.h000066400000000000000000000007041473252155700162360ustar00rootroot00000000000000#include #include "libmegapixels.h" #pragma once struct libmegapixels_modename { char *name; uint32_t v4l_pixel_format; uint32_t media_bus_format; int bpp; /* Bits per pixel */ int bpc; /* Bits per color - not currently used */ int cfa; }; int libmegapixels_v4l_pixfmt_to_index(uint32_t pixfmt); int libmegapixels_format_name_to_index(const char *name); int mode_snprintf(char *buf, size_t maxlen, libmegapixels_mode *mode);libmegapixels-0.2.0/src/parse.c000066400000000000000000000460141473252155700164230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "libmegapixels.h" #include "mode.h" #include "util.h" #include "log.h" #include "flash.h" char * find_path_for_devnode(struct media_v2_intf_devnode devnode) { char uevent_path[PATH_MAX] = ""; snprintf(uevent_path, PATH_MAX, "/sys/dev/char/%d:%d/uevent", devnode.major, devnode.minor); FILE *fp = fopen(uevent_path, "r"); if (!fp) { return NULL; } char line[PATH_MAX]; char path[PATH_MAX]; while (fgets(line, PATH_MAX, fp)) { if (strncmp(line, "DEVNAME=", 8) == 0) { // Drop newline unsigned long length = strlen(line); if (line[length - 1] == '\n') line[length - 1] = '\0'; snprintf(path, length, "/dev/%s", line + 8); return strdup(path); } } return ""; } int find_media_node(libmegapixels_camera *camera, const char *media_name, const char *sensor_name) { struct dirent *dir; DIR *d = opendir("/dev"); while ((dir = readdir(d)) != NULL) { if (strncmp(dir->d_name, "media", 5) == 0) { char path[PATH_MAX]; snprintf(path, PATH_MAX, "/dev/%s", dir->d_name); int media_fd = open(path, O_RDWR); if (media_fd == -1) { fprintf(stderr, "Could not open %s\n", path); continue; } struct media_device_info mdi; if (xioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdi) == -1) { fprintf(stderr, "Could not MDI\n"); close(media_fd); } if (strcmp(mdi.driver, media_name) != 0 && strcmp(mdi.model, media_name) != 0) { close(media_fd); continue; } // This media device matches on model or driver, scan the entities for the sensor struct media_v2_topology topology = {0}; if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1 || topology.num_entities == 0) { close(media_fd); continue; } struct media_v2_entity *entities = calloc(topology.num_entities, sizeof(struct media_v2_entity)); struct media_v2_interface *interfaces = calloc(topology.num_interfaces, sizeof(struct media_v2_interface)); struct media_v2_pad *pads = calloc(topology.num_pads, sizeof(struct media_v2_pad)); struct media_v2_link *links = calloc(topology.num_links, sizeof(struct media_v2_link)); topology.ptr_entities = (uint64_t) entities; topology.ptr_interfaces = (uint64_t) interfaces; topology.ptr_pads = (uint64_t) pads; topology.ptr_links = (uint64_t) links; if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1) { close(media_fd); continue; } // Find the sensor unsigned long len = strlen(sensor_name); int found = 0; for (int i = 0; i < topology.num_entities; i++) { if (strncmp(entities[i].name, sensor_name, len) == 0) { found++; for (int j = 0; j < topology.num_links; j++) { if (links[j].sink_id != entities[i].id) { continue; } for (int k = 0; k < topology.num_interfaces; k++) { if (interfaces[k].id != links[j].source_id) { continue; } camera->sensor_path = find_path_for_devnode(interfaces[k].devnode); break; } } break; } } // Find the flash for (int i = 0; i < topology.num_entities; i++) { if (entities[i].function == MEDIA_ENT_F_FLASH) { for (int j = 0; j < topology.num_links; j++) { if (links[j].sink_id != entities[i].id) { continue; } for (int k = 0; k < topology.num_interfaces; k++) { if (interfaces[k].id != links[j].source_id) { continue; } camera->flash_path = find_path_for_devnode(interfaces[k].devnode); camera->flash_type = LIBMEGAPIXELS_FLASH_V4L; break; } } break; } } // Find the lens actuator for (int i = 0; i < topology.num_entities; i++) { if (entities[i].function == MEDIA_ENT_F_LENS) { for (int j = 0; j < topology.num_links; j++) { if (links[j].sink_id != entities[i].id) { continue; } for (int k = 0; k < topology.num_interfaces; k++) { if (interfaces[k].id != links[j].source_id) { continue; } camera->lens_path = find_path_for_devnode(interfaces[k].devnode); break; } } break; } } // Find the bridge for (int i = 0; i < topology.num_entities; i++) { if (entities[i].function == MEDIA_ENT_F_IO_V4L) { found++; for (int j = 0; j < topology.num_links; j++) { if (links[j].sink_id != entities[i].id) { continue; } for (int k = 0; k < topology.num_interfaces; k++) { if (interfaces[k].id != links[j].source_id) { continue; } camera->video_path = find_path_for_devnode(interfaces[k].devnode); break; } } break; } } camera->num_handles = 0; for (int i = 0; i < topology.num_links; i++) { if (!(links[i].flags & MEDIA_LNK_FL_INTERFACE_LINK)) { continue; } camera->num_handles++; } camera->handles = calloc(camera->num_handles, sizeof(libmegapixels_subdev)); int h = 0; for (int i = 0; i < topology.num_links; i++) { if (!(links[i].flags & MEDIA_LNK_FL_INTERFACE_LINK)) { continue; } libmegapixels_subdev *sd; sd = malloc(sizeof(libmegapixels_subdev)); camera->handles[h] = sd; camera->handles[h]->entity_id = links[i].sink_id; camera->handles[h]->fd = 0; for (int j = 0; j < topology.num_interfaces; j++) { if (links[i].source_id != interfaces[j].id) { continue; } camera->handles[h]->path = find_path_for_devnode(interfaces[j].devnode); } h++; } close(media_fd); if (found == 2) { camera->media_path = strdup(path); return 1; } } } closedir(d); return -1; } int load_camera(libmegapixels_devconfig *config, config_t *cfg, const char *name, int lint) { const char *sensor_driver, *bridge_driver, *flashpath; config_setting_t *root = config_lookup(cfg, name); log_debug("Loading camera '%s'\n", name); if (!config_setting_lookup_string(root, "SensorDriver", &sensor_driver)) { log_debug(" Section is missing SensorDriver\n"); return -1; } if (!config_setting_lookup_string(root, "BridgeDriver", &bridge_driver)) { log_debug(" Section is missing BridgeDriver\n"); return -1; } libmegapixels_camera *camera; camera = malloc(sizeof(libmegapixels_camera)); camera->sensor_fd = 0; camera->media_fd = 0; camera->video_fd = 0; camera->flash_fd = 0; camera->lens_fd = 0; camera->flash_path = NULL; camera->lens_path = NULL; camera->flash_type = LIBMEGAPIXELS_FLASH_SCREEN; // Don't access hardware in linting mode if (lint == 0) { int res = find_media_node(camera, bridge_driver, sensor_driver); if (res < 0) { log_debug(" Could not find media node with this sensor\n"); free(camera); return -1; } } camera->name = strdup(name); camera->sensor_name = strdup(sensor_driver); camera->bridge_name = strdup(bridge_driver); config->cameras = realloc(config->cameras, (config->count + 1) * sizeof(config->cameras)); if (config->cameras == NULL) { return -1; } camera->index = config->count; config->cameras[config->count++] = camera; if (config_setting_lookup_string(root, "FlashPath", &flashpath)) { camera->flash_type = LIBMEGAPIXELS_FLASH_LED; libmegapixels_flash_cleanup_flash_path(camera); camera->flash_path = libmegapixels_flash_get_led_path(flashpath); if (camera->flash_path == NULL) { log_error("Failed to get LED flash path\n"); return -1; } } // You do not need to set FlashDisplay in device config files, unless for some reason there's a V4L flash device that isn't meant to be there int flashDisplay = 0; if (config_setting_lookup_bool(root, "FlashDisplay", &flashDisplay)) { if (flashDisplay) { camera->flash_type = LIBMEGAPIXELS_FLASH_SCREEN; libmegapixels_flash_cleanup_flash_path(camera); } } config_setting_t *modes = config_setting_lookup(root, "Modes"); config_setting_t *mode; if (modes == NULL) { log_error("Sensor '%s' does not define any modes\n", camera->name); return -1; } int num_modes = config_setting_length(modes); camera->modes = malloc(num_modes * sizeof(libmegapixels_mode *)); camera->num_modes = num_modes; int n = 0; while (1) { mode = config_setting_get_elem(modes, n); if (mode == NULL) { break; } libmegapixels_mode *mm = malloc(sizeof(libmegapixels_mode)); camera->modes[n] = mm; if (!config_setting_lookup_int(mode, "Width", &mm->width)) { log_error("Missing Width\n"); return -1; } if (!config_setting_lookup_int(mode, "Height", &mm->height)) { log_error("Missing Height\n"); return -1; } if (!config_setting_lookup_int(mode, "Rate", &mm->rate)) { log_error("Missing Rate\n"); return -1; } const char *fmt; config_setting_lookup_string(mode, "Format", &fmt); mm->format = libmegapixels_format_name_to_index(fmt); if (!mm->format) { log_error("Unknown format '%s'\n", fmt); return -1; } mm->v4l_pixfmt = libmegapixels_format_to_v4l_pixfmt(mm->format); mm->media_busfmt = libmegapixels_format_to_media_busfmt(mm->format); const char *xfer; if (config_setting_lookup_string(mode, "Transfer", &xfer)) { if (strcmp(xfer, "srgb") == 0) { mm->xfer = LIBMEGAPIXELS_XFER_SRGB; } else { mm->xfer = LIBMEGAPIXELS_XFER_RAW; } } else { mm->xfer = LIBMEGAPIXELS_XFER_RAW; } if (!config_setting_lookup_int(mode, "Rotate", &mm->rotation)) { mm->rotation = 0; } if (!config_setting_lookup_bool(mode, "Mirror", &mm->mirrored)) { mm->mirrored = 0; } if (!config_setting_lookup_float(mode, "FocalLength", &mm->focal_length)) { mm->focal_length = 0.0f; } if (!config_setting_lookup_float(mode, "FNumber", &mm->f_number)) { mm->f_number = 0.0f; } char modename[32] = {0}; mode_snprintf(modename, 31, mm); log_debug(" Adding mode [%s]\n", modename); config_setting_t *cmds = config_setting_lookup(mode, "Pipeline"); config_setting_t *cmd; if (cmds == NULL) { log_error("Mode has no pipeline\n"); n++; continue; } int num_cmds = config_setting_length(cmds); mm->cmds = (libmegapixels_cmd **) calloc(num_cmds, sizeof(libmegapixels_cmd *)); mm->num_cmds = num_cmds; int m = 0; int last_width = camera->modes[n]->width; int last_height = camera->modes[n]->height; int last_format = camera->modes[n]->format; while (1) { cmd = config_setting_get_elem(cmds, m); if (cmd == NULL) { break; } libmegapixels_cmd *cur = calloc(1, sizeof(libmegapixels_cmd)); mm->cmds[m] = cur; const char *type; if (!config_setting_lookup_string(cmd, "Type", &type)) { break; } if (strcmp(type, "Link") == 0) { camera->modes[n]->cmds[m]->type = LIBMEGAPIXELS_CMD_LINK; if (!config_setting_lookup_string(cmd, "From", &cur->entity_from)) { fprintf(stderr, "Missing From\n"); break; } if (!config_setting_lookup_string(cmd, "To", &cur->entity_to)) { fprintf(stderr, "Missing To\n"); break; } if (!config_setting_lookup_int(cmd, "FromPad", &cur->pad_from)) { cur->pad_from = 0; } if (!config_setting_lookup_int(cmd, "ToPad", &cur->pad_to)) { cur->pad_to = 0; } if (!config_setting_lookup_bool(cmd, "ExactName", &cur->exact_name)) { cur->exact_name = 0; } } else if (strcmp(type, "Mode") == 0) { camera->modes[n]->cmds[m]->type = LIBMEGAPIXELS_CMD_MODE; if (!config_setting_lookup_string(cmd, "Entity", &cur->entity_from)) { fprintf(stderr, "Missing entity\n"); break; } if (!config_setting_lookup_int(cmd, "Width", &cur->width)) { cur->width = last_width; } last_width = cur->width; if (!config_setting_lookup_int(cmd, "Height", &cur->height)) { cur->height = last_height; } last_height = cur->height; if (!config_setting_lookup_int(cmd, "Pad", &cur->pad_from)) { cur->pad_from = 0; } const char *newfmt; if (config_setting_lookup_string(cmd, "Format", &newfmt)) { cur->format = libmegapixels_format_name_to_index(newfmt); last_format = cur->format; } else { cur->format = last_format; } if (!config_setting_lookup_bool(cmd, "SkipTry", &cur->skip_try)) { cur->skip_try = 0; } if (!config_setting_lookup_bool(cmd, "ExactName", &cur->exact_name)) { cur->exact_name = 0; } } else if (strcmp(type, "Rate") == 0) { camera->modes[n]->cmds[m]->type = LIBMEGAPIXELS_CMD_INTERVAL; if (!config_setting_lookup_string(cmd, "Entity", &cur->entity_from)) { fprintf(stderr, "Missing entity\n"); break; } if (!config_setting_lookup_int(cmd, "Rate", &cur->rate)) { cur->rate = mm->rate; } if (!config_setting_lookup_bool(cmd, "SkipTry", &cur->skip_try)) { cur->skip_try = 0; } if (!config_setting_lookup_bool(cmd, "ExactName", &cur->exact_name)) { cur->exact_name = 0; } } else if (strcmp(type, "Crop") == 0) { camera->modes[n]->cmds[m]->type = LIBMEGAPIXELS_CMD_CROP; if (!config_setting_lookup_string(cmd, "Entity", &cur->entity_from)) { fprintf(stderr, "Missing entity\n"); break; } if (!config_setting_lookup_int(cmd, "Width", &cur->width)) { cur->width = camera->modes[n]->width; } last_width = cur->width; if (!config_setting_lookup_int(cmd, "Height", &cur->height)) { cur->height = camera->modes[n]->height; } last_height = cur->height; if (!config_setting_lookup_int(cmd, "Top", &cur->top)) { cur->top = 0; } if (!config_setting_lookup_int(cmd, "Left", &cur->left)) { cur->left = 0; } if (!config_setting_lookup_int(cmd, "Pad", &cur->pad_from)) { cur->pad_from = 0; } if (!config_setting_lookup_bool(cmd, "SkipTry", &cur->skip_try)) { cur->skip_try = 0; } if (!config_setting_lookup_bool(cmd, "ExactName", &cur->exact_name)) { cur->exact_name = 0; } } m++; } n++; } if (n == 0) { log_debug("No modes defined for sensor '%s'\n", camera->name); return -1; } return 0; } int libmegapixels_load_file(libmegapixels_devconfig *config, const char *file) { return libmegapixels_load_file_lint(config, file, 0); } int libmegapixels_load_file_lint(libmegapixels_devconfig *config, const char *file, int linting) { if (config->loaded_config) { log_error("Config already loaded\n"); return 0; } log_debug("Load config file %s\n", file); config->loaded_config = 1; config_t cfg; config_setting_t *setting, *member; config->path = strdup(file); if (config->count == 0) { config->cameras = malloc(0); } config_init(&cfg); if (!config_read_file(&cfg, file)) { if (config_error_type(&cfg) == CONFIG_ERR_FILE_IO) { fprintf(stderr, "Could not read %s: %s\n", file, config_error_text(&cfg)); } else { fprintf(stderr, "Could not read %s:\n", file); fprintf(stderr, "\t%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); } config_destroy(&cfg); return 0; } int version = 0; if (config_lookup_int(&cfg, "Version", &version)) { if (version != 1) { fprintf(stderr, "Could not load '%s': Invalid version %d\n", file, version); return 0; } } else { fprintf(stderr, "Could not load '%s': Missing version\n", file); return 0; } if (!config_lookup_string(&cfg, "Make", &config->make)) { config->make = strdup("Megapixels"); } if (!config_lookup_string(&cfg, "Model", &config->model)) { config->model = strdup("Camera"); } setting = config_root_setting(&cfg); int n = 0; while (1) { member = config_setting_get_elem(setting, n++); if (member == NULL) { break; } if (strcmp(member->name, "Version") == 0) { continue; } if (strcmp(member->name, "Make") == 0) { continue; } if (strcmp(member->name, "Model") == 0) { continue; } load_camera(config, &cfg, member->name, linting); } return 1; } void uvc_create_modes(libmegapixels_camera *camera, int fd) { struct v4l2_fmtdesc fmtdesc = {0}; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; while (xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) { fmtdesc.index++; libmegapixels_v4l_pixfmt_to_index(fmtdesc.pixelformat); int format = libmegapixels_v4l_pixfmt_to_index(fmtdesc.pixelformat); if (!format) { // fprintf(stderr, "Unsupported format: %s\n", pixfmt_to_str(fmtdesc.pixelformat)); continue; } struct v4l2_frmsizeenum framesize = {0}; framesize.pixel_format = fmtdesc.pixelformat; while (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &framesize) == 0) { if (framesize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { struct v4l2_frmivalenum frameinterval = {0}; frameinterval.pixel_format = framesize.pixel_format; frameinterval.width = framesize.discrete.width; frameinterval.height = framesize.discrete.height; while (xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameinterval) == 0) { frameinterval.index++; if (frameinterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) { libmegapixels_mode *mode = calloc(1, sizeof(libmegapixels_mode)); mode->format = format; mode->v4l_pixfmt = fmtdesc.pixelformat; mode->width = (int) framesize.discrete.width; mode->height = (int) framesize.discrete.height; mode->media_busfmt = libmegapixels_format_to_media_busfmt(format); mode->rotation = 0; mode->rate = (int) ((double) frameinterval.discrete.denominator / (double) frameinterval.discrete.numerator); camera->modes = realloc(camera->modes, (camera->num_modes + 1) * sizeof(camera->modes)); if (camera->modes == NULL) { return; } camera->modes[camera->num_modes++] = mode; } } } framesize.index++; } } } int libmegapixels_load_uvc(libmegapixels_devconfig *config) { if (config->loaded_uvc) { log_error("libmegapixels_load_uvc was already called\n"); return 0; } log_debug("Loading UVC cameras\n"); config->loaded_uvc = 1; struct dirent *dir; DIR *d = opendir("/dev"); while ((dir = readdir(d)) != NULL) { if (strncmp(dir->d_name, "video", 5) == 0) { char path[PATH_MAX]; snprintf(path, PATH_MAX, "/dev/%s", dir->d_name); int fd = open(path, O_RDWR); if (fd < 0) { continue; } struct v4l2_capability vid_cap = {0}; if (xioctl(fd, VIDIOC_QUERYCAP, &vid_cap) == -1) { perror("QUERYCAP"); continue; } if (strcmp((char *) vid_cap.driver, "uvcvideo") != 0) { continue; } if (!(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { continue; } if (!(vid_cap.capabilities & V4L2_CAP_STREAMING)) { continue; } struct v4l2_fmtdesc fmtdesc = {0}; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != 0) { continue; } libmegapixels_camera *camera; camera = calloc(1, sizeof(libmegapixels_camera)); camera->name = strdup((const char *) vid_cap.card); camera->video_path = strdup(path); uvc_create_modes(camera, fd); close(fd); config->cameras = realloc(config->cameras, (config->count + 1) * sizeof(config->cameras)); if (config->cameras == NULL) { return -1; } camera->index = config->count; config->cameras[config->count++] = camera; } } closedir(d); return 1; } int libmegapixels_init(libmegapixels_devconfig **config) { *config = calloc(1, sizeof(libmegapixels_devconfig)); init_log(0); return 1; } libmegapixels-0.2.0/src/pipeline.c000066400000000000000000000335461473252155700171240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "libmegapixels.h" #include "log.h" #include "util.h" #include "mode.h" int setup_link(libmegapixels_camera *camera, uint32_t source_entity_id, uint32_t sink_entity_id, uint16_t source_index, uint16_t sink_index, int enabled) { struct media_link_desc link = {}; link.flags = (enabled > 0) ? MEDIA_LNK_FL_ENABLED : 0; link.source.entity = source_entity_id; link.source.index = source_index; link.sink.entity = sink_entity_id; link.sink.index = sink_index; if (xioctl(camera->media_fd, MEDIA_IOC_SETUP_LINK, &link) == -1) { return -1; } return 0; } int load_entity_ids(libmegapixels_camera *camera) { // This media device matches on model or driver, scan the entities for the sensor struct media_v2_topology topology = {0}; if (xioctl(camera->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1 || topology.num_entities == 0) { close(camera->media_fd); return -1; } struct media_v2_entity *entities = calloc(topology.num_entities, sizeof(struct media_v2_entity)); struct media_v2_interface *interfaces = calloc(topology.num_interfaces, sizeof(struct media_v2_interface)); struct media_v2_pad *pads = calloc(topology.num_pads, sizeof(struct media_v2_pad)); struct media_v2_link *links = calloc(topology.num_links, sizeof(struct media_v2_link)); topology.ptr_entities = (uint64_t) entities; topology.ptr_interfaces = (uint64_t) interfaces; topology.ptr_pads = (uint64_t) pads; topology.ptr_links = (uint64_t) links; if (xioctl(camera->media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1) { close(camera->media_fd); return -1; } for (int i = 0; i < camera->num_modes; i++) { libmegapixels_mode *mode = camera->modes[i]; for (int j = 0; j < mode->num_cmds; j++) { libmegapixels_cmd *cmd = mode->cmds[j]; if (cmd->type == LIBMEGAPIXELS_CMD_LINK) { int found_from = 0; int found_to = 0; for (int k = 0; k < topology.num_entities; k++) { if (strncmp(entities[k].name, cmd->entity_from, strlen(cmd->entity_from)) == 0) { if(!cmd->exact_name || strlen(cmd->entity_from) == strlen(entities[k].name)) { cmd->entity_from_id = entities[k].id; found_from++; } } if (strncmp(entities[k].name, cmd->entity_to, strlen(cmd->entity_to)) == 0) { if(!cmd->exact_name || strlen(cmd->entity_to) == strlen(entities[k].name)) { cmd->entity_to_id = entities[k].id; found_to++; } } } if (found_from != 1) { log_error("Could not find entity '%s'\n", cmd->entity_from); return -1; } if (found_to != 1) { log_error("Could not find entity '%s'\n", cmd->entity_to); return -1; } } else if (cmd->type == LIBMEGAPIXELS_CMD_MODE || cmd->type == LIBMEGAPIXELS_CMD_CROP || cmd->type == LIBMEGAPIXELS_CMD_INTERVAL) { int found = 0; for (int k = 0; k < topology.num_entities; k++) { if (strncmp(entities[k].name, cmd->entity_from, strlen(cmd->entity_from)) == 0) { if(!cmd->exact_name || strlen(cmd->entity_from) == strlen(entities[k].name)) { cmd->entity_from_id = entities[k].id; found++; break; } } } if (found != 1) { log_error("Could not find entity '%s'\n", cmd->entity_from); } } } } for (int i = 0; i < topology.num_links; i++) { if (links[i].flags & MEDIA_LNK_FL_ENABLED && !(links[i].flags & MEDIA_LNK_FL_IMMUTABLE)) { uint32_t source_entity = 0, sink_entity = 0; uint16_t source_index = 0, sink_index = 0; for (int j = 0; j < topology.num_pads; j++) { if (pads[j].id == links[i].source_id) { source_entity = pads[j].entity_id; source_index = pads[j].index; } else if (pads[j].id == links[i].sink_id) { sink_entity = pads[j].entity_id; sink_index = pads[j].index; } } setup_link(camera, source_entity, sink_entity, source_index, sink_index, 0); } } return 0; } int libmegapixels_open(libmegapixels_camera *camera) { if (camera->media_fd != 0) { log_error("Camera already opened\n"); return -1; } if (camera->sensor_fd != 0) { log_error("Sensor already opened\n"); return -1; } if (camera->video_fd != 0) { log_error("Bridge already opened\n"); return -1; } if (camera->flash_fd != 0) { log_error("Flash already opened\n"); return -1; } if (camera->lens_fd != 0) { log_error("Lens already opened\n"); return -1; } if (camera->media_path) { camera->media_fd = open(camera->media_path, O_RDWR); if (camera->media_fd < 0) { log_error("Could not open %s: %s\n", camera->media_path, strerror(errno)); return -1; } } if (camera->sensor_path) { camera->sensor_fd = open(camera->sensor_path, O_RDWR); if (camera->sensor_fd < 0) { log_error("Could not open %s: %s\n", camera->sensor_path, strerror(errno)); return -1; } } // Flash path can be either the v4l device path, or the /sys/class/leds/... path if (camera->flash_path) { camera->flash_fd = open(camera->flash_path, O_RDWR); if (camera->flash_fd < 0) { log_error("Could not open %s: %s\n", camera->flash_path, strerror(errno)); return -1; } } camera->video_fd = open(camera->video_path, O_RDWR); if (camera->video_fd < 0) { log_error("Could not open %s: %s\n", camera->video_path, strerror(errno)); return -1; } if (camera->lens_path) { camera->lens_fd = open(camera->lens_path, O_RDWR); if (camera->lens_fd < 0) { log_error("Could not open %s: %s\n", camera->lens_path, strerror(errno)); return -1; } } // If this is an UVC camera the sensor _is_ the video device if (camera->sensor_fd == 0) { camera->sensor_fd = camera->video_fd; } if (camera->media_fd > 0) { int ret = load_entity_ids(camera); if (ret < 0) { return ret; } } return 0; } void libmegapixels_close(libmegapixels_camera *camera) { int uvc = 0; if (camera->sensor_fd != 0 && camera->sensor_fd == camera->video_fd) { uvc = 1; } if (camera->media_fd != 0) { close(camera->media_fd); camera->media_fd = 0; } if (camera->sensor_fd != 0) { close(camera->sensor_fd); camera->sensor_fd = 0; if (uvc) { camera->video_fd = 0; } } if (camera->video_fd != 0) { close(camera->video_fd); camera->video_fd = 0; } if (camera->flash_fd != 0) { close(camera->flash_fd); camera->flash_fd = 0; } if (camera->lens_fd != 0) { close(camera->lens_fd); camera->lens_fd = 0; } for (int i = 0; i < camera->num_handles; i++) { if (camera->handles[i]->fd != 0) { close(camera->handles[i]->fd); camera->handles[i]->fd = 0; } } } unsigned int libmegapixels_select_mode(libmegapixels_camera *camera, libmegapixels_mode *mode, struct v4l2_format *format) { assert(camera->video_fd != 0); assert(camera->sensor_fd != 0); char modename[32] = {0}; mode_snprintf(modename, 31, mode); log_debug("Setting '%s' mode to [%s]\n", camera->name, modename); for (int i = 0; i < mode->num_cmds; i++) { libmegapixels_cmd *cmd = mode->cmds[i]; struct v4l2_subdev_format subdev_fmt = {}; struct v4l2_subdev_crop subdev_crop = {}; struct v4l2_subdev_frame_interval subdev_ival = {}; int found = 0; libmegapixels_subdev *sd; switch (cmd->type) { case LIBMEGAPIXELS_CMD_LINK: log_debug(" Link %s:%d -> %s:%d\n", cmd->entity_from, cmd->pad_from, cmd->entity_to, cmd->pad_to); if (setup_link(camera, cmd->entity_from_id, cmd->entity_to_id, cmd->pad_from, cmd->pad_to, 1) != 0) { log_error("Could not link %d -> %d [%s -> %s] \n", cmd->entity_from_id, cmd->entity_to_id, cmd->entity_from, cmd->entity_to); } break; case LIBMEGAPIXELS_CMD_MODE: log_debug(" Mode %s:%d [%dx%d %s]\n", cmd->entity_from, cmd->pad_from, cmd->width, cmd->height, libmegapixels_format_name(cmd->format)); found = 0; for (int h = 0; h < camera->num_handles; h++) { if (camera->handles[h]->entity_id == cmd->entity_from_id) { sd = camera->handles[h]; found++; } } if (found != 1) { log_error("Could not find handle for entity\n"); break; } if (sd->fd == 0) { sd->fd = open(sd->path, O_RDWR); if (sd->fd < 0) { log_error("Could not open %s\n", sd->path); break; } } subdev_fmt.pad = cmd->pad_from; subdev_fmt.which = V4L2_SUBDEV_FORMAT_TRY; subdev_fmt.format.width = cmd->width; subdev_fmt.format.height = cmd->height; subdev_fmt.format.code = libmegapixels_format_to_media_busfmt(cmd->format); subdev_fmt.format.field = V4L2_FIELD_ANY; if (!cmd->skip_try) { if (xioctl(sd->fd, VIDIOC_SUBDEV_S_FMT, &subdev_fmt) == -1) { log_error("Could not try mode on %s:%d: %s\n", cmd->entity_from, cmd->pad_from, strerror(errno)); } } if (subdev_fmt.format.width != cmd->width || subdev_fmt.format.height != cmd->height) { log_error("Driver rejected resolution try: %dx%d, changed to %dx%d\n", cmd->width, cmd->height, subdev_fmt.format.width, subdev_fmt.format.height); break; } if (subdev_fmt.format.code != libmegapixels_format_to_media_busfmt(cmd->format)) { log_error("Driver rejected pixfmt try: 0x%x\n", subdev_fmt.format.code); break; } subdev_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; if (xioctl(sd->fd, VIDIOC_SUBDEV_S_FMT, &subdev_fmt) == -1) { log_error("Could not set mode on %s:%d: %s\n", cmd->entity_from, cmd->pad_from, strerror(errno)); } // There seem to be drivers that don't reject resolutions in V4L2_SUBDEV_FORMAT_TRY mode but // do silently change the resolution when doing the V4L2_SUBDEV_FORMAT_ACTIVE bit. So check // again since V4L2 is the wild west. if (subdev_fmt.format.width != cmd->width || subdev_fmt.format.height != cmd->height) { log_error("Driver rejected resolution %dx%d, changed to %dx%d\n", cmd->width, cmd->height, subdev_fmt.format.width, subdev_fmt.format.height); break; } if (subdev_fmt.format.code != libmegapixels_format_to_media_busfmt(cmd->format)) { log_error("Driver rejected pixfmt: 0x%x\n", subdev_fmt.format.code); break; } break; case LIBMEGAPIXELS_CMD_INTERVAL: subdev_ival.pad = cmd->pad_from; struct v4l2_fract ival; ival.numerator = 1; ival.denominator = cmd->rate; subdev_ival.interval = ival; log_debug(" Rate %s:%d [%d]\n", cmd->entity_from, cmd->pad_from, cmd->rate); found = 0; for (int h = 0; h < camera->num_handles; h++) { if (camera->handles[h]->entity_id == cmd->entity_from_id) { sd = camera->handles[h]; found++; } } if (found != 1) { log_error("Could not find handle for entity\n"); break; } if (sd->fd == 0) { sd->fd = open(sd->path, O_RDWR); if (sd->fd < 0) { log_error("Could not open %s\n", sd->path); break; } } if (xioctl(sd->fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &subdev_ival) == -1) { log_error("Could not set rate on %s:%d: %s\n", cmd->entity_from, cmd->pad_from, strerror(errno)); } break; case LIBMEGAPIXELS_CMD_CROP: subdev_crop.pad = cmd->pad_from; subdev_crop.which = V4L2_SUBDEV_FORMAT_ACTIVE; subdev_crop.rect.top = cmd->top; subdev_crop.rect.left = cmd->left; subdev_crop.rect.width = cmd->width; subdev_crop.rect.height = cmd->height; log_debug(" Crop %s:%d [%dx%d+%d+%d]\n", cmd->entity_from, cmd->pad_from, cmd->width, cmd->height, cmd->top, cmd->left); found = 0; for (int h = 0; h < camera->num_handles; h++) { if (camera->handles[h]->entity_id == cmd->entity_from_id) { sd = camera->handles[h]; found++; } } if (found != 1) { log_error("Could not find handle for entity\n"); break; } if (sd->fd == 0) { sd->fd = open(sd->path, O_RDWR); if (sd->fd < 0) { log_error("Could not open %s\n", sd->path); break; } } if (xioctl(sd->fd, VIDIOC_SUBDEV_S_CROP, &subdev_crop) == -1) { log_error("Could not set crop on entity: %s\n", strerror(errno)); } break; } } struct v4l2_capability cap; if (xioctl(camera->video_fd, VIDIOC_QUERYCAP, &cap) == -1) { log_error("Could not query %s: %s\n", camera->video_path, strerror(errno)); return 0; } if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; format->fmt.pix_mp.width = mode->width; format->fmt.pix_mp.height = mode->height; format->fmt.pix_mp.pixelformat = mode->v4l_pixfmt; format->fmt.pix_mp.field = V4L2_FIELD_ANY; } else { format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format->fmt.pix.width = mode->width; format->fmt.pix.height = mode->height; format->fmt.pix.pixelformat = mode->v4l_pixfmt; format->fmt.pix.field = V4L2_FIELD_ANY; } if (xioctl(camera->video_fd, VIDIOC_S_FMT, format) == -1) { log_error("Could not set mode on %s: %s\n", camera->video_path, strerror(errno)); return 0; } if (xioctl(camera->video_fd, VIDIOC_G_FMT, format) == -1) { log_error("Could not get mode of %s: %s\n", camera->video_path, strerror(errno)); return 0; } if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { log_error("Capture driver changed capture type\n"); return 0; } if (format->fmt.pix_mp.pixelformat != mode->v4l_pixfmt) { log_error("Capture driver changed pixfmt to %c%c%c%c\n", format->fmt.pix_mp.pixelformat & 0xff, format->fmt.pix_mp.pixelformat >> 8 & 0xff, format->fmt.pix_mp.pixelformat >> 16 & 0xff, format->fmt.pix_mp.pixelformat >> 24 & 0xff); return 0; } } else { if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { log_error("Capture driver changed capture type\n"); return 0; } if (format->fmt.pix.pixelformat != mode->v4l_pixfmt) { log_error("Capture driver changed pixfmt to %c%c%c%c\n", format->fmt.pix.pixelformat & 0xff, format->fmt.pix.pixelformat >> 8 & 0xff, format->fmt.pix.pixelformat >> 16 & 0xff, format->fmt.pix.pixelformat >> 24 & 0xff); return 0; } } camera->current_mode = mode; return 1; }libmegapixels-0.2.0/src/util.c000066400000000000000000000034301473252155700162610ustar00rootroot00000000000000#include #include #include #include #include "util.h" #include "log.h" int xioctl(int fd, int request, void *arg) { int r; do { r = ioctl(fd, request, arg); } while (r == -1 && errno == EINTR); return r; } // Copied from drivers/media/v4l2-core/v4l2-ctrls-defs.c, trimmed down const char * v4l2_ctrl_get_name(unsigned int id) { switch (id) { case V4L2_CID_FLASH_LED_MODE: return "LED Mode"; case V4L2_CID_FLASH_STROBE_SOURCE: return "Strobe Source"; case V4L2_CID_FLASH_STROBE: return "Strobe"; default: return "Unknown"; } } // Copied from drivers/media/v4l2-core/v4l2-ctrls-defs.c, trimmed down and slightly modified const char * v4l2_ctrl_get_menu_value(unsigned int id, signed int val) { static const char *const flash_led_mode[] = { "Off", "Flash", "Torch", NULL, }; static const char *const flash_strobe_source[] = { "Software", "External", NULL, }; switch (id) { case V4L2_CID_FLASH_LED_MODE: return flash_led_mode[val]; case V4L2_CID_FLASH_STROBE_SOURCE: return flash_strobe_source[val]; default: return "None/Unknown"; } } // Set a control value, but ignore if it fails void set_control(int fd, unsigned int ctrl_id, signed int ctrl_val) { struct v4l2_control ctrl; ctrl.id = ctrl_id; ctrl.value = ctrl_val; int res = xioctl(fd, VIDIOC_S_CTRL, &ctrl); if (res == -1) { log_debug( "Failed to set %s to %s for flash.\n", v4l2_ctrl_get_name(ctrl_id), v4l2_ctrl_get_menu_value(ctrl_id, ctrl_val) ); } } char * pixfmt_to_str(unsigned int pixfmt) { static char result[5] = " "; result[0] = (char) (pixfmt >> 0 & 0xFF); result[1] = (char) (pixfmt >> 8 & 0xFF); result[2] = (char) (pixfmt >> 16 & 0xFF); result[3] = (char) (pixfmt >> 24 & 0xFF); return result; }libmegapixels-0.2.0/src/util.h000066400000000000000000000002531473252155700162660ustar00rootroot00000000000000#pragma once int xioctl(int fd, int request, void *arg); void set_control(int fd, unsigned int ctrl_id, signed int ctrl_val); char * pixfmt_to_str(unsigned int pixfmt);libmegapixels-0.2.0/tests/000077500000000000000000000000001473252155700155135ustar00rootroot00000000000000libmegapixels-0.2.0/tests/CMakeLists.txt000066400000000000000000000004741473252155700202600ustar00rootroot00000000000000find_package(Check) include_directories(${CHECK_INCLUDE_DIRS}) link_directories(${CHECK_LIBRARY_DIRS}) add_executable(check_mode check_mode.c greatest.h) target_include_directories(check_mode PUBLIC include) target_link_libraries(check_mode PUBLIC libmegapixels check) add_test(NAME check_mode COMMAND check_mode) libmegapixels-0.2.0/tests/check_mode.c000066400000000000000000000031321473252155700177370ustar00rootroot00000000000000#include #include "greatest.h" #include "libmegapixels.h" static enum greatest_test_res check_bytesused( uint32_t pixfmt, uint32_t width, uint32_t height, uint32_t expected_width_to_bytes, uint32_t expected_width_to_padding, uint32_t expected_bytes_used ) { int format = libmegapixels_v4l_pixfmt_to_index(pixfmt); uint32_t width_to_bytes = libmegapixels_mode_width_to_bytes(format, width); uint32_t width_to_padding = libmegapixels_mode_width_to_padding(format, width); uint32_t bytes_used = (width_to_bytes + width_to_padding) * height; // printf( // "\nFormat name: %s, width: %u, height: %u, width to bytes: %u, width to padding: %u, bytes used: %u\n", // libmegapixels_format_name(format), // width, // height, // width_to_bytes, // width_to_padding, // bytes_used // ); ASSERT_EQ(width_to_bytes, expected_width_to_bytes); ASSERT_EQ(width_to_padding, expected_width_to_padding); ASSERT_EQ(bytes_used, expected_bytes_used); PASS(); } TEST bytesused_for_rggb8(void) { CHECK_CALL(check_bytesused(V4L2_PIX_FMT_SRGGB8, 4208, 3120, 4208, 0, 13128960)); PASS(); } TEST bytesused_for_rggb10(void) { CHECK_CALL(check_bytesused(V4L2_PIX_FMT_SRGGB10, 4208, 3120, 8416, 0, 26257920)); PASS(); } TEST bytesused_for_rggb10p(void) { CHECK_CALL(check_bytesused(V4L2_PIX_FMT_SRGGB10P, 4208, 3120, 5260, 4, 16423680)); PASS(); } SUITE (test_suite) { RUN_TEST(bytesused_for_rggb8); RUN_TEST(bytesused_for_rggb10); RUN_TEST(bytesused_for_rggb10p); } GREATEST_MAIN_DEFS(); int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); RUN_SUITE(test_suite); GREATEST_MAIN_END(); } libmegapixels-0.2.0/tests/greatest.h000066400000000000000000002061651473252155700175140ustar00rootroot00000000000000/* * Copyright (c) 2011-2021 Scott Vokes * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GREATEST_H #define GREATEST_H #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) extern "C" { #endif /* 1.5.0 */ #define GREATEST_VERSION_MAJOR 1 #define GREATEST_VERSION_MINOR 5 #define GREATEST_VERSION_PATCH 0 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything * beyond ANSI C89. * * An up-to-date version can be found at: * https://github.com/silentbicycle/greatest/ */ /********************************************************************* * Minimal test runner template *********************************************************************/ #if 0 #include "greatest.h" TEST foo_should_foo(void) { PASS(); } static void setup_cb(void *data) { printf("setup callback for each test case\n"); } static void teardown_cb(void *data) { printf("teardown callback for each test case\n"); } SUITE(suite) { /* Optional setup/teardown callbacks which will be run before/after * every test case. If using a test suite, they will be cleared when * the suite finishes. */ SET_SETUP(setup_cb, voidp_to_callback_data); SET_TEARDOWN(teardown_cb, voidp_to_callback_data); RUN_TEST(foo_should_foo); } /* Add definitions that need to be in the test runner's main file. */ GREATEST_MAIN_DEFS(); /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ int run_tests(void) { GREATEST_INIT(); /* init. greatest internals */ /* List of suites to run (if any). */ RUN_SUITE(suite); /* Tests can also be run directly, without using test suites. */ RUN_TEST(foo_should_foo); GREATEST_PRINT_REPORT(); /* display results */ return greatest_all_passed(); } /* main(), for a standalone command-line test runner. * This replaces run_tests above, and adds command line option * handling and exiting with a pass/fail status. */ int main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ RUN_SUITE(suite); GREATEST_MAIN_END(); /* display results */ } #endif /*********************************************************************/ #include #include #include #include /*********** * Options * ***********/ /* Default column width for non-verbose output. */ #ifndef GREATEST_DEFAULT_WIDTH #define GREATEST_DEFAULT_WIDTH 72 #endif /* FILE *, for test logging. */ #ifndef GREATEST_STDOUT #define GREATEST_STDOUT stdout #endif /* Remove GREATEST_ prefix from most commonly used symbols? */ #ifndef GREATEST_USE_ABBREVS #define GREATEST_USE_ABBREVS 1 #endif /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP #define GREATEST_USE_LONGJMP 0 #endif /* Make it possible to replace fprintf with another * function with the same interface. */ #ifndef GREATEST_FPRINTF #define GREATEST_FPRINTF fprintf #endif #if GREATEST_USE_LONGJMP #include #endif /* Set to 0 to disable all use of time.h / clock(). */ #ifndef GREATEST_USE_TIME #define GREATEST_USE_TIME 1 #endif #if GREATEST_USE_TIME #include #endif /* Floating point type, for ASSERT_IN_RANGE. */ #ifndef GREATEST_FLOAT #define GREATEST_FLOAT double #define GREATEST_FLOAT_FMT "%g" #endif /* Size of buffer for test name + optional '_' separator and suffix */ #ifndef GREATEST_TESTNAME_BUF_SIZE #define GREATEST_TESTNAME_BUF_SIZE 128 #endif /********* * Types * *********/ /* Info for the current running suite. */ typedef struct greatest_suite_info { unsigned int tests_run; unsigned int passed; unsigned int failed; unsigned int skipped; #if GREATEST_USE_TIME /* timers, pre/post running suite and individual tests */ clock_t pre_suite; clock_t post_suite; clock_t pre_test; clock_t post_test; #endif } greatest_suite_info; /* Type for a suite function. */ typedef void greatest_suite_cb(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ typedef void greatest_setup_cb(void *udata); typedef void greatest_teardown_cb(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_equal_cb(const void *expd, const void *got, void *udata); /* Type for a callback that prints a value pointed to by T. * Return value has the same meaning as printf's. * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ typedef int greatest_printf_cb(const void *t, void *udata); /* Callbacks for an arbitrary type; needed for type-specific * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ typedef struct greatest_type_info { greatest_equal_cb *equal; greatest_printf_cb *print; } greatest_type_info; typedef struct greatest_memory_cmp_env { const unsigned char *exp; const unsigned char *got; size_t size; } greatest_memory_cmp_env; /* Callbacks for string and raw memory types. */ extern greatest_type_info greatest_type_info_string; extern greatest_type_info greatest_type_info_memory; typedef enum { GREATEST_FLAG_FIRST_FAIL = 0x01, GREATEST_FLAG_LIST_ONLY = 0x02, GREATEST_FLAG_ABORT_ON_FAIL = 0x04 } greatest_flag_t; /* Internal state for a PRNG, used to shuffle test order. */ struct greatest_prng { unsigned char random_order; /* use random ordering? */ unsigned char initialized; /* is random ordering initialized? */ unsigned char pad_0[6]; unsigned long state; /* PRNG state */ unsigned long count; /* how many tests, this pass */ unsigned long count_ceil; /* total number of tests */ unsigned long count_run; /* total tests run */ unsigned long a; /* LCG multiplier */ unsigned long c; /* LCG increment */ unsigned long m; /* LCG modulus, based on count_ceil */ }; /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; unsigned char running_test; /* guard for nested RUN_TEST calls */ unsigned char exact_name_match; unsigned int tests_run; /* total test count */ /* currently running test suite */ greatest_suite_info suite; /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; /* info to print about the most recent failure */ unsigned int fail_line; unsigned int pad_1; const char *fail_file; const char *msg; /* current setup/teardown hooks and userdata */ greatest_setup_cb *setup; void *setup_udata; greatest_teardown_cb *teardown; void *teardown_udata; /* formatting info for ".....s...F"-style output */ unsigned int col; unsigned int width; /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; const char *test_exclude; const char *name_suffix; /* print suffix with test name */ char name_buf[GREATEST_TESTNAME_BUF_SIZE]; struct greatest_prng prng[2]; /* 0: suites, 1: tests */ #if GREATEST_USE_TIME /* overall timers */ clock_t begin; clock_t end; #endif #if GREATEST_USE_LONGJMP int pad_jmp_buf; unsigned char pad_2[4]; jmp_buf jump_dest; #endif } greatest_run_info; struct greatest_report_t { /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; }; /* Global var for the current testing context. * Initialized by GREATEST_MAIN_DEFS(). */ extern greatest_run_info greatest_info; /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ typedef const char *greatest_enum_str_fun(int value); /********************** * Exported functions * **********************/ /* These are used internally by greatest macros. */ int greatest_test_pre(const char *name); void greatest_test_post(int res); int greatest_do_assert_equal_t(const void *expd, const void *got, greatest_type_info *type_info, void *udata); void greatest_prng_init_first_pass(int id); int greatest_prng_init_second_pass(int id, unsigned long seed); void greatest_prng_step(int id); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); void GREATEST_INIT(void); void GREATEST_PRINT_REPORT(void); int greatest_all_passed(void); void greatest_set_suite_filter(const char *filter); void greatest_set_test_filter(const char *filter); void greatest_set_test_exclude(const char *filter); void greatest_set_exact_name_match(void); void greatest_stop_at_first_fail(void); void greatest_abort_on_fail(void); void greatest_list_only(void); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); void greatest_set_flag(greatest_flag_t flag); void greatest_set_test_suffix(const char *suffix); /******************** * Language Support * ********************/ /* If __VA_ARGS__ (C99) is supported, allow parametric testing * without needing to manually manage the argument struct. */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L) || \ (defined(_MSC_VER) && _MSC_VER >= 1800) #define GREATEST_VA_ARGS #endif /********** * Macros * **********/ /* Define a suite. (The duplication is intentional -- it eliminates * a warning from -Wmissing-declarations.) */ #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) /* Declare a suite, provided by another compilation unit. */ #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) /* Start defining a test function. * The arguments are not included, to allow parametric testing. */ #define GREATEST_TEST static enum greatest_test_res /* PASS/FAIL/SKIP result from a test. Used internally. */ typedef enum greatest_test_res { GREATEST_TEST_RES_PASS = 0, GREATEST_TEST_RES_FAIL = -1, GREATEST_TEST_RES_SKIP = 1 } greatest_test_res; /* Run a suite. */ #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ greatest_test_post(res); \ } \ } while (0) /* Ignore a test, don't warn about it being unused. */ #define GREATEST_IGNORE_TEST(TEST) (void)TEST /* Run a test in the current suite with one void * argument, * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(ENV); \ } \ greatest_test_post(res); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(__VA_ARGS__); \ } \ greatest_test_post(res); \ } \ } while (0) #endif /* Check if the test runner is in verbose mode. */ #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) #define GREATEST_LIST_ONLY() \ (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_ABORT_ON_FAIL() \ (greatest_info.flags & GREATEST_FLAG_ABORT_ON_FAIL) #define GREATEST_FAILURE_ABORT() \ (GREATEST_FIRST_FAIL() && \ (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) #define GREATEST_FAIL() GREATEST_FAILm(NULL) #define GREATEST_SKIP() GREATEST_SKIPm(NULL) #define GREATEST_ASSERT(COND) \ GREATEST_ASSERTm(#COND, COND) #define GREATEST_ASSERT_OR_LONGJMP(COND) \ GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) #define GREATEST_ASSERT_FALSE(COND) \ GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_NEQ(EXP, GOT) \ GREATEST_ASSERT_NEQm(#EXP " == " #GOT, EXP, GOT) #define GREATEST_ASSERT_GT(EXP, GOT) \ GREATEST_ASSERT_GTm(#EXP " <= " #GOT, EXP, GOT) #define GREATEST_ASSERT_GTE(EXP, GOT) \ GREATEST_ASSERT_GTEm(#EXP " < " #GOT, EXP, GOT) #define GREATEST_ASSERT_LT(EXP, GOT) \ GREATEST_ASSERT_LTm(#EXP " >= " #GOT, EXP, GOT) #define GREATEST_ASSERT_LTE(EXP, GOT) \ GREATEST_ASSERT_LTEm(#EXP " > " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) /* The following forms take an additional message argument first, * to be displayed by the test runner. */ /* Fail if a condition is not true, with message. */ #define GREATEST_ASSERTm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if a condition is not true, longjmping out of test. */ #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ do { \ greatest_info.assertions++; \ if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ } while (0) /* Fail if a condition is not false, with message. */ #define GREATEST_ASSERT_FALSEm(MSG, COND) \ do { \ greatest_info.assertions++; \ if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) /* Internal macro for relational assertions */ #define GREATEST__REL(REL, MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ if (!((EXP) REL (GOT))) { GREATEST_FAILm(MSG); } \ } while (0) /* Fail if EXP is not ==, !=, >, <, >=, or <= to GOT. */ #define GREATEST_ASSERT_EQm(MSG,E,G) GREATEST__REL(==, MSG,E,G) #define GREATEST_ASSERT_NEQm(MSG,E,G) GREATEST__REL(!=, MSG,E,G) #define GREATEST_ASSERT_GTm(MSG,E,G) GREATEST__REL(>, MSG,E,G) #define GREATEST_ASSERT_GTEm(MSG,E,G) GREATEST__REL(>=, MSG,E,G) #define GREATEST_ASSERT_LTm(MSG,E,G) GREATEST__REL(<, MSG,E,G) #define GREATEST_ASSERT_LTEm(MSG,E,G) GREATEST__REL(<=, MSG,E,G) /* Fail if EXP != GOT (equality comparison by ==). * Warning: FMT, EXP, and GOT will be evaluated more * than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, printing enum IDs. */ #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ do { \ int greatest_EXP = (int)(EXP); \ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ } while (0) \ /* Fail if GOT not in range of EXP +|- TOL. */ #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ do { \ GREATEST_FLOAT greatest_EXP = (EXP); \ GREATEST_FLOAT greatest_GOT = (GOT); \ GREATEST_FLOAT greatest_TOL = (TOL); \ greatest_info.assertions++; \ if ((greatest_EXP > greatest_GOT && \ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ "\n", \ greatest_EXP, greatest_TOL, greatest_GOT); \ GREATEST_FAILm(MSG); \ } \ } while (0) /* Fail if EXP is not equal to GOT, according to strcmp. */ #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ do { \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, NULL); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to strncmp. */ #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ do { \ size_t size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ &greatest_type_info_string, &size); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to memcmp. */ #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ do { \ greatest_memory_cmp_env env; \ env.exp = (const unsigned char *)EXP; \ env.got = (const unsigned char *)GOT; \ env.size = SIZE; \ GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ &greatest_type_info_memory, &env); \ } while (0) \ /* Fail if EXP is not equal to GOT, according to a comparison * callback in TYPE_INFO. If they are not equal, optionally use a * print callback in TYPE_INFO to print them. */ #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ do { \ greatest_type_info *type_info = (TYPE_INFO); \ greatest_info.assertions++; \ if (!greatest_do_assert_equal_t(EXP, GOT, \ type_info, UDATA)) { \ if (type_info == NULL || type_info->equal == NULL) { \ GREATEST_FAILm("type_info->equal callback missing!"); \ } else { \ GREATEST_FAILm(MSG); \ } \ } \ } while (0) \ /* Pass. */ #define GREATEST_PASSm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_PASS; \ } while (0) /* Fail. */ #define GREATEST_FAILm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ if (GREATEST_ABORT_ON_FAIL()) { abort(); } \ return GREATEST_TEST_RES_FAIL; \ } while (0) /* Optional GREATEST_FAILm variant that longjmps. */ #if GREATEST_USE_LONGJMP #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ do { \ greatest_info.fail_file = __FILE__; \ greatest_info.fail_line = __LINE__; \ greatest_info.msg = MSG; \ longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ } while (0) #endif /* Skip the current test. */ #define GREATEST_SKIPm(MSG) \ do { \ greatest_info.msg = MSG; \ return GREATEST_TEST_RES_SKIP; \ } while (0) /* Check the result of a subfunction using ASSERT, etc. */ #define GREATEST_CHECK_CALL(RES) \ do { \ enum greatest_test_res greatest_RES = RES; \ if (greatest_RES != GREATEST_TEST_RES_PASS) { \ return greatest_RES; \ } \ } while (0) \ #if GREATEST_USE_TIME #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else #define GREATEST_SET_TIME(UNUSED) #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) #endif #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ /*a no-op, since setjmp/longjmp aren't being used */ \ GREATEST_TEST_RES_PASS #endif /* Run every suite / test function run within BODY in pseudo-random * order, seeded by SEED. (The top 3 bits of the seed are ignored.) * * This should be called like: * GREATEST_SHUFFLE_TESTS(seed, { * GREATEST_RUN_TEST(some_test); * GREATEST_RUN_TEST(some_other_test); * GREATEST_RUN_TEST(yet_another_test); * }); * * Note that the body of the second argument will be evaluated * multiple times. */ #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) #define GREATEST_SHUFFLE(ID, SD, BODY) \ do { \ struct greatest_prng *prng = &greatest_info.prng[ID]; \ greatest_prng_init_first_pass(ID); \ do { \ prng->count = 0; \ if (prng->initialized) { greatest_prng_step(ID); } \ BODY; \ if (!prng->initialized) { \ if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ } else if (prng->count_run == prng->count_ceil) { \ break; \ } \ } while (!GREATEST_FAILURE_ABORT()); \ prng->count_run = prng->random_order = prng->initialized = 0; \ } while(0) /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ static int greatest_name_match(const char *name, const char *filter, \ int res_if_none) { \ size_t offset = 0; \ size_t filter_len = filter ? strlen(filter) : 0; \ if (filter_len == 0) { return res_if_none; } /* no filter */ \ if (greatest_info.exact_name_match && strlen(name) != filter_len) { \ return 0; /* ignore substring matches */ \ } \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ return 1; \ } \ } \ offset++; \ } \ \ return 0; \ } \ \ static void greatest_buffer_test_name(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ size_t len = strlen(name), size = sizeof(g->name_buf); \ memset(g->name_buf, 0x00, size); \ (void)strncat(g->name_buf, name, size - 1); \ if (g->name_suffix && (len + 1 < size)) { \ g->name_buf[len] = '_'; \ strncat(&g->name_buf[len+1], g->name_suffix, size-(len+2)); \ } \ } \ \ /* Before running a test, check the name filtering and \ * test shuffling state, if applicable, and then call setup hooks. */ \ int greatest_test_pre(const char *name) { \ struct greatest_run_info *g = &greatest_info; \ int match; \ greatest_buffer_test_name(name); \ match = greatest_name_match(g->name_buf, g->test_filter, 1) && \ !greatest_name_match(g->name_buf, g->test_exclude, 0); \ if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ if (match) { \ GREATEST_FPRINTF(GREATEST_STDOUT, " %s\n", g->name_buf); \ } \ goto clear; \ } \ if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ struct greatest_prng *p = &g->prng[1]; \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ goto clear; /* don't run this test yet */ \ } \ } \ if (g->running_test) { \ fprintf(stderr, "Error: Test run inside another test.\n"); \ return 0; \ } \ GREATEST_SET_TIME(g->suite.pre_test); \ if (g->setup) { g->setup(g->setup_udata); } \ p->count_run++; \ g->running_test = 1; \ return 1; /* test should be run */ \ } else { \ goto clear; /* skipped */ \ } \ clear: \ g->name_suffix = NULL; \ return 0; \ } \ \ static void greatest_do_pass(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ } \ g->suite.passed++; \ } \ \ static void greatest_do_fail(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", g->name_buf, \ g->msg ? g->msg : "", g->fail_file, g->fail_line); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ g->col++; /* add linebreak if in line of '.'s */ \ if (g->col != 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ g->col = 0; \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ g->name_buf, g->msg ? g->msg : "", \ g->fail_file, g->fail_line); \ } \ g->suite.failed++; \ } \ \ static void greatest_do_skip(void) { \ struct greatest_run_info *g = &greatest_info; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ g->name_buf, g->msg ? g->msg : ""); \ } else { \ GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ } \ g->suite.skipped++; \ } \ \ void greatest_test_post(int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ greatest_info.teardown(udata); \ } \ \ greatest_info.running_test = 0; \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ greatest_do_skip(); \ } else if (res == GREATEST_TEST_RES_PASS) { \ greatest_do_pass(); \ } \ greatest_info.name_suffix = NULL; \ greatest_info.suite.tests_run++; \ greatest_info.col++; \ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ fflush(GREATEST_STDOUT); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ greatest_info.suite.passed, \ greatest_info.suite.failed, \ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ \ static void update_counts_and_reset_suite(void) { \ greatest_info.setup = NULL; \ greatest_info.setup_udata = NULL; \ greatest_info.teardown = NULL; \ greatest_info.teardown_udata = NULL; \ greatest_info.passed += greatest_info.suite.passed; \ greatest_info.failed += greatest_info.suite.failed; \ greatest_info.skipped += greatest_info.suite.skipped; \ greatest_info.tests_run += greatest_info.suite.tests_run; \ memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ greatest_info.col = 0; \ } \ \ static int greatest_suite_pre(const char *suite_name) { \ struct greatest_prng *p = &greatest_info.prng[0]; \ if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ || (GREATEST_FAILURE_ABORT())) { return 0; } \ if (p->random_order) { \ p->count++; \ if (!p->initialized || ((p->count - 1) != p->state)) { \ return 0; /* don't run this suite yet */ \ } \ } \ p->count_run++; \ update_counts_and_reset_suite(); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ return 1; \ } \ \ static void greatest_suite_post(void) { \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ const char *suite_name) { \ if (greatest_suite_pre(suite_name)) { \ suite_cb(); \ greatest_suite_post(); \ } \ } \ \ int greatest_do_assert_equal_t(const void *expd, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ if (type_info == NULL || type_info->equal == NULL) { return 0; } \ eq = type_info->equal(expd, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(expd, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ return eq; \ } \ \ static void greatest_usage(const char *name) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Usage: %s [-hlfavex] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ " -h, --help print this Help\n" \ " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ " -a Abort on first failure (implies -f)\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing substring SUITE\n" \ " -t TEST only run tests containing substring TEST\n" \ " -e only run exact name match for -s or -t\n" \ " -x EXCLUDE exclude tests containing substring EXCLUDE\n", \ name); \ } \ \ static void greatest_parse_options(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ if (argv[i][0] == '-') { \ char f = argv[i][1]; \ if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ greatest_usage(argv[0]); exit(EXIT_FAILURE); \ } \ switch (f) { \ case 's': /* suite name filter */ \ greatest_set_suite_filter(argv[i + 1]); i++; break; \ case 't': /* test name filter */ \ greatest_set_test_filter(argv[i + 1]); i++; break; \ case 'x': /* test name exclusion */ \ greatest_set_test_exclude(argv[i + 1]); i++; break; \ case 'e': /* exact name match */ \ greatest_set_exact_name_match(); break; \ case 'f': /* first fail flag */ \ greatest_stop_at_first_fail(); break; \ case 'a': /* abort() on fail flag */ \ greatest_abort_on_fail(); break; \ case 'l': /* list only (dry run) */ \ greatest_list_only(); break; \ case 'v': /* first fail flag */ \ greatest_info.verbosity++; break; \ case 'h': /* help */ \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ default: \ case '-': \ if (0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ } else if (0 == strcmp("--", argv[i])) { \ return; /* ignore following arguments */ \ } \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ void greatest_set_test_filter(const char *filter) { \ greatest_info.test_filter = filter; \ } \ \ void greatest_set_test_exclude(const char *filter) { \ greatest_info.test_exclude = filter; \ } \ \ void greatest_set_suite_filter(const char *filter) { \ greatest_info.suite_filter = filter; \ } \ \ void greatest_set_exact_name_match(void) { \ greatest_info.exact_name_match = 1; \ } \ \ void greatest_stop_at_first_fail(void) { \ greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ } \ \ void greatest_abort_on_fail(void) { \ greatest_set_flag(GREATEST_FLAG_ABORT_ON_FAIL); \ } \ \ void greatest_list_only(void) { \ greatest_set_flag(GREATEST_FLAG_LIST_ONLY); \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ if (report) { \ report->passed = greatest_info.passed; \ report->failed = greatest_info.failed; \ report->skipped = greatest_info.skipped; \ report->assertions = greatest_info.assertions; \ } \ } \ \ unsigned int greatest_get_verbosity(void) { \ return greatest_info.verbosity; \ } \ \ void greatest_set_verbosity(unsigned int verbosity) { \ greatest_info.verbosity = (unsigned char)verbosity; \ } \ \ void greatest_set_flag(greatest_flag_t flag) { \ greatest_info.flags = (unsigned char)(greatest_info.flags | flag); \ } \ \ void greatest_set_test_suffix(const char *suffix) { \ greatest_info.name_suffix = suffix; \ } \ \ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup = cb; \ greatest_info.setup_udata = udata; \ } \ \ void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ \ static int greatest_string_equal_cb(const void *expd, const void *got, \ void *udata) { \ size_t *size = (size_t *)udata; \ return (size != NULL \ ? (0 == strncmp((const char *)expd, (const char *)got, *size)) \ : (0 == strcmp((const char *)expd, (const char *)got))); \ } \ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ greatest_string_equal_cb, greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *expd, const void *got, \ void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ return (0 == memcmp(expd, got, env->size)); \ } \ \ /* Hexdump raw memory, with differences highlighted */ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ const unsigned char *buf = (const unsigned char *)t; \ unsigned char diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ for (i = 0; i < env->size; i+= line_len) { \ diff_mark = ' '; \ line_len = env->size - i; \ if (line_len > 16) { line_len = 16; } \ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ len += GREATEST_FPRINTF(out, "\n%04x %c ", \ (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ len += GREATEST_FPRINTF(out, "%02x%c", \ buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ len += GREATEST_FPRINTF(out, " "); \ } \ GREATEST_FPRINTF(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ } \ } \ len += GREATEST_FPRINTF(out, "\n"); \ return len; \ } \ \ void greatest_prng_init_first_pass(int id) { \ greatest_info.prng[id].random_order = 1; \ greatest_info.prng[id].count_run = 0; \ } \ \ int greatest_prng_init_second_pass(int id, unsigned long seed) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ if (p->count == 0) { return 0; } \ p->count_ceil = p->count; \ for (p->m = 1; p->m < p->count; p->m <<= 1) {} \ p->state = seed & 0x1fffffff; /* only use lower 29 bits */ \ p->a = 4LU * p->state; /* to avoid overflow when */ \ p->a = (p->a ? p->a : 4) | 1; /* multiplied by 4 */ \ p->c = 2147483647; /* and so p->c ((2 ** 31) - 1) is */ \ p->initialized = 1; /* always relatively prime to p->a. */ \ fprintf(stderr, "init_second_pass: a %lu, c %lu, state %lu\n", \ p->a, p->c, p->state); \ return 1; \ } \ \ /* Step the pseudorandom number generator until its state reaches \ * another test ID between 0 and the test count. \ * This use a linear congruential pseudorandom number generator, \ * with the power-of-two ceiling of the test count as the modulus, the \ * masked seed as the multiplier, and a prime as the increment. For \ * each generated value < the test count, run the corresponding test. \ * This will visit all IDs 0 <= X < mod once before repeating, \ * with a starting position chosen based on the initial seed. \ * For details, see: Knuth, The Art of Computer Programming \ * Volume. 2, section 3.2.1. */ \ void greatest_prng_step(int id) { \ struct greatest_prng *p = &greatest_info.prng[id]; \ do { \ p->state = ((p->a * p->state) + p->c) & (p->m - 1); \ } while (p->state >= p->count_ceil); \ } \ \ void GREATEST_INIT(void) { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ (void)greatest_parse_options; \ (void)greatest_prng_step; \ (void)greatest_prng_init_first_pass; \ (void)greatest_prng_init_second_pass; \ (void)greatest_set_test_suffix; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ GREATEST_SET_TIME(greatest_info.begin); \ } \ \ /* Report passes, failures, skipped tests, the number of \ * assertions, and the overall run time. */ \ void GREATEST_PRINT_REPORT(void) { \ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ } \ } \ \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info /* Handle command-line arguments, etc. */ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ greatest_parse_options(argc, argv); \ } while (0) /* Report results, exit with exit status based on results. */ #define GREATEST_MAIN_END() \ do { \ GREATEST_PRINT_REPORT(); \ return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ } while (0) /* Make abbreviations without the GREATEST_ prefix for the * most commonly used symbols. */ #if GREATEST_USE_ABBREVS #define TEST GREATEST_TEST #define SUITE GREATEST_SUITE #define SUITE_EXTERN GREATEST_SUITE_EXTERN #define RUN_TEST GREATEST_RUN_TEST #define RUN_TEST1 GREATEST_RUN_TEST1 #define RUN_SUITE GREATEST_RUN_SUITE #define IGNORE_TEST GREATEST_IGNORE_TEST #define ASSERT GREATEST_ASSERT #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ #define ASSERT_NEQ GREATEST_ASSERT_NEQ #define ASSERT_GT GREATEST_ASSERT_GT #define ASSERT_GTE GREATEST_ASSERT_GTE #define ASSERT_LT GREATEST_ASSERT_LT #define ASSERT_LTE GREATEST_ASSERT_LTE #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm #define ASSERT_NEQm GREATEST_ASSERT_NEQm #define ASSERT_GTm GREATEST_ASSERT_GTm #define ASSERT_GTEm GREATEST_ASSERT_GTEm #define ASSERT_LTm GREATEST_ASSERT_LTm #define ASSERT_LTEm GREATEST_ASSERT_LTEm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm #define PASS GREATEST_PASS #define FAIL GREATEST_FAIL #define SKIP GREATEST_SKIP #define PASSm GREATEST_PASSm #define FAILm GREATEST_FAILm #define SKIPm GREATEST_SKIPm #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp #endif #if GREATEST_USE_LONGJMP #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm #endif #endif /* USE_ABBREVS */ #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) } #endif #endif libmegapixels-0.2.0/tests/meson.build000066400000000000000000000002661473252155700176610ustar00rootroot00000000000000check_mode = executable('check_mode', 'check_mode.c', 'greatest.h', include_directories: inc, link_with: libmegapixels, install: false, ) test('check_mode', check_mode) libmegapixels-0.2.0/util/000077500000000000000000000000001473252155700153265ustar00rootroot00000000000000libmegapixels-0.2.0/util/configlint.c000066400000000000000000000013041473252155700176240ustar00rootroot00000000000000#include #include #include int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } libmegapixels_devconfig *config = {0}; libmegapixels_init(&config); if (!libmegapixels_load_file_lint(config, argv[1], 1)) { return 1; } if (config->count == 0) { fprintf(stderr, "Config file does not define any cameras\n"); return 1; } bool faulty = false; for (int i = 0; i < config->count; i++) { if (config->cameras[i]->num_modes == 0) { fprintf(stderr, "No modes are defined for sensor '%s'\n", config->cameras[i]->name); faulty = true; } } if (faulty) { return 1; } return 0; }libmegapixels-0.2.0/util/findconfig.c000066400000000000000000000050651473252155700176060ustar00rootroot00000000000000#include #include #include #include #include int main(int argc, char *argv[]) { int c; char configpath[PATH_MAX]; int ret; int verbose = 0; while ((c = getopt(argc, argv, "c:v")) != -1) { switch (c) { case 'c': sprintf(configpath, "%s", optarg); ret = 1; break; case 'v': verbose = 1; break; case '?': if (optopt == 'd' || optopt == 'l') { fprintf(stderr, "Option -%c requires an argument.\n", optopt); } else if (isprint(optopt)) { fprintf(stderr, "Unknown option '-%c'\n", optopt); } else { fprintf(stderr, "Unknown option character x%x\n", optopt); } return 1; default: return 1; } } ret = libmegapixels_find_config_verbose(PATH_MAX, configpath, verbose); libmegapixels_devconfig *config = {0}; libmegapixels_init(&config); if (!ret) { printf("No config found\n"); } else { printf("Using config: %s\n", configpath); if (!libmegapixels_load_file(config, configpath)) { printf("Could not load config\n"); } } libmegapixels_load_uvc(config); if (config->count == 0) { return 1; } printf("Device: %s %s\n", config->make, config->model); printf("Found %d cameras\n", config->count); for (int i = 0; i < config->count; i++) { printf("\n----[ Camera %s (%d) ]----\n", config->cameras[i]->name, config->cameras[i]->index); if (config->cameras[i]->bridge_name) { printf("Media : %s (%s)\n", config->cameras[i]->bridge_name, config->cameras[i]->media_path); } if (config->cameras[i]->sensor_name) { printf("Sensor: %s (%s)\n", config->cameras[i]->sensor_name, config->cameras[i]->sensor_path); } printf("Video : %s\n", config->cameras[i]->video_path); if (config->cameras[i]->flash_type == LIBMEGAPIXELS_FLASH_SCREEN) { printf("Flash : screen\n"); } else { if (config->cameras[i]->flash_path) { switch (config->cameras[i]->flash_type) { case LIBMEGAPIXELS_FLASH_LED: printf("Flash : LED (%s)\n", config->cameras[i]->flash_path); break; case LIBMEGAPIXELS_FLASH_V4L: printf("Flash : V4L flash entity (%s)\n", config->cameras[i]->flash_path); break; } } } if (config->cameras[i]->lens_path) { printf("Focus : %s\n", config->cameras[i]->lens_path); } printf("Modes : "); for (int j = 0; j < config->cameras[i]->num_modes; j++) { if (j > 0) { printf(" "); } libmegapixels_mode *mode = config->cameras[i]->modes[j]; printf("%dx%d@%d [%s]\n", mode->width, mode->height, mode->rate, libmegapixels_format_name(mode->format)); } } return 0; }libmegapixels-0.2.0/util/getframe.c000066400000000000000000000163651473252155700172770ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include struct buffer { void *start; size_t length; }; struct buffer *buffers; int xioctl(int fd, int request, void *arg) { int r; do { r = ioctl(fd, request, arg); } while (r == -1 && errno == EINTR); return r; } void usage(char *name) { fprintf(stderr, "Usage: %s [-h] [-n offset] [-c camera] [-o file]\n", name); fprintf(stderr, "Get a frame using libmegapixels\n\n"); fprintf(stderr, "Arguments:\n"); fprintf(stderr, " -n offset Get the Nth frame from the output (defaults to 5)\n"); fprintf(stderr, " -c camera Use a specific camera number\n"); fprintf(stderr, " -m modenum Use another camera mode than the first\n"); fprintf(stderr, " -o file File to store the frame in\n"); fprintf(stderr, " -v Show verbose debug info\n"); fprintf(stderr, " -h Display this help text\n"); } int main(int argc, char *argv[]) { int c; int camera_id = 0; long res; char *end; char *outfile = NULL; int count = 5; int mode_idx = 0; int verbose = 0; while ((c = getopt(argc, argv, "hvc:n:o:m:")) != -1) { switch (c) { case 'c': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -c\n"); return 1; } camera_id = (int) res; break; case 'o': outfile = optarg; break; case 'n': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -n\n"); return 1; } count = (int) res; break; case 'm': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -m\n"); return 1; } mode_idx = (int) res; break; case 'h': usage(argv[0]); return 0; break; case 'v': verbose = 1; break; case '?': if (optopt == 'd' || optopt == 'l') { fprintf(stderr, "Option -%c requires an argument.\n", optopt); } else if (isprint(optopt)) { fprintf(stderr, "Unknown option '-%c'\n", optopt); } else { fprintf(stderr, "Unknown option character x%x\n", optopt); } return 1; default: usage(argv[0]); return 1; } } char configpath[PATH_MAX]; int ret = libmegapixels_find_config(PATH_MAX, configpath); libmegapixels_devconfig *config = {0}; libmegapixels_init(&config); if (verbose) { libmegapixels_loglevel(2); } if (ret) { printf("Using config: %s\n", configpath); libmegapixels_load_file(config, configpath); } else { fprintf(stderr, "No config found for this device\n"); } libmegapixels_load_uvc(config); if (config->count == 0) { fprintf(stderr, "No valid camera configuration\n"); return 1; } if (camera_id > config->count - 1) { fprintf(stderr, "Camera id %d does not exist\n", camera_id); return 1; } libmegapixels_camera *camera = config->cameras[camera_id]; if (libmegapixels_open(camera) != 0) { fprintf(stderr, "Could not open default camera\n"); return 1; } if (mode_idx > camera->num_modes - 1) { fprintf(stderr, "Invalid mode index: %d\n", mode_idx); return 1; } libmegapixels_mode *mode = camera->modes[mode_idx]; struct v4l2_format format = {0}; unsigned int frame_size = libmegapixels_select_mode(camera, mode, &format); if (frame_size == 0) { fprintf(stderr, "Could not select mode\n"); return 1; } // Do the reqular V4L2 stuff to get a frame struct v4l2_capability cap; int mplanes = 0; enum v4l2_buf_type buftype = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(camera->video_fd, VIDIOC_QUERYCAP, &cap) != 0) { fprintf(stderr, "VIDIOC_QUERYCAP failed: %s\n", strerror(errno)); return 1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "Device does not support streaming i/o\n"); return 1; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { mplanes = 1; buftype = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; } else { fprintf(stderr, "Device does not support V4L2_CAP_VIDEO_CAPTURE\n"); return 1; } } struct v4l2_requestbuffers req = {0}; req.count = 4; req.type = buftype; req.memory = V4L2_MEMORY_MMAP; if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) { fprintf(stderr, "VIDIOC_REQBUFS failed: %s\n", strerror(errno)); return 1; } buffers = calloc(req.count, sizeof(*buffers)); for (int i = 0; i < req.count; i++) { struct v4l2_buffer buf = {0}; buf.type = buftype; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; struct v4l2_plane planes[1]; if (mplanes) { buf.m.planes = planes; buf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_QUERYBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_QUERYBUF failed: %s\n", strerror(errno)); return 1; } unsigned int offset; if (mplanes) { buffers[i].length = planes[0].length; offset = planes[0].m.mem_offset; } else { buffers[i].length = buf.length; offset = buf.m.offset; } buffers[i].start = mmap(NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, camera->video_fd, offset); if (buffers[i].start == MAP_FAILED) { fprintf(stderr, "mmap() failed\n"); return 1; } } for (int i = 0; i < req.count; i++) { struct v4l2_buffer qbuf = {0}; qbuf.type = buftype; qbuf.memory = V4L2_MEMORY_MMAP; qbuf.index = i; if (mplanes) { struct v4l2_plane qplanes[1]; qbuf.m.planes = qplanes; qbuf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_QBUF, &qbuf) == -1) { fprintf(stderr, "VIDIOC_QBUF failed: %s\n", strerror(errno)); return 1; } } enum v4l2_buf_type type = buftype; if (xioctl(camera->video_fd, VIDIOC_STREAMON, &type) == -1) { fprintf(stderr, "VIDIOC_STREAMON failed: %s\n", strerror(errno)); return 1; } while (count-- > 0) { while (1) { fd_set(fds); FD_ZERO(&fds); FD_SET(camera->video_fd, &fds); int sr = select(FD_SETSIZE, &fds, NULL, NULL, NULL); if (sr == -1) { if (errno == EINTR) { count++; continue; } fprintf(stderr, "select() failed: %s\n", strerror(errno)); return 1; } struct v4l2_buffer buf = {0}; buf.type = buftype; buf.memory = V4L2_MEMORY_MMAP; if (mplanes) { struct v4l2_plane dqplanes[1]; buf.m.planes = dqplanes; buf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_DQBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_DQBUF failed\n"); return 1; } fprintf(stderr, "received frame\n"); if (count == 0 && outfile != NULL) { FILE *fp = fopen(outfile, "w"); fwrite(buffers[buf.index].start, buffers[buf.index].length, 1, fp); fclose(fp); printf("Stored frame to: %s\n", outfile); printf("Format: %dx%d\n", mode->width, mode->height); char fourcc[5] = {0}; fourcc[0] = (char) (mode->v4l_pixfmt & 0xff); fourcc[1] = (char) ((mode->v4l_pixfmt >> 8) & 0xff); fourcc[2] = (char) ((mode->v4l_pixfmt >> 16) & 0xff); fourcc[3] = (char) ((mode->v4l_pixfmt >> 24) & 0xff); printf("Pixfmt: %s\n", fourcc); } if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_DQBUF failed\n"); return 1; } break; } } return 0; }libmegapixels-0.2.0/util/sensorprofile.c000066400000000000000000000301171473252155700203660ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include struct buffer { void *start; size_t length; }; struct buffer *buffers; struct control { uint32_t id; int64_t min; int64_t max; uint64_t step; int64_t default_value; int32_t value; }; int xioctl(int fd, int request, void *arg) { int r; do { r = ioctl(fd, request, arg); } while (r == -1 && errno == EINTR); return r; } void usage(char *name) { fprintf(stderr, "Usage: %s [-h] [-n offset] [-c camera] [-o file]\n", name); fprintf(stderr, "Measure the linearity of the sensor response\n\n"); fprintf(stderr, "Arguments:\n"); fprintf(stderr, " -n count Number of datapoint to gather, takes 1 second per point\n"); fprintf(stderr, " -c camera Use a specific camera number\n"); fprintf(stderr, " -m modenum Use another camera mode than the first\n"); fprintf(stderr, " -o file File to store the calibration in\n"); fprintf(stderr, " -h Display this help text\n"); } void brightness(const uint8_t *buffer, size_t length, libmegapixels_mode *mode, float *red, float *green, float *blue) { // Get the offset to a single line of pixels at the middle of the frame size_t line = libmegapixels_mode_width_to_bytes(mode->format, mode->width); size_t stride = line + libmegapixels_mode_width_to_padding(mode->format, mode->width); size_t offset = stride * (mode->height / 2); unsigned long long sum_r = 0, sum_g = 0, sum_b = 0; unsigned int total = 0; for (size_t i = 0; i < line; i += 2) { uint8_t p1 = buffer[offset + i]; uint8_t p2 = buffer[offset + i + 1]; uint8_t p3 = buffer[offset + i + stride]; //uint8_t p4 = buffer[offset + i + 1 + stride]; total++; switch (mode->v4l_pixfmt) { case V4L2_PIX_FMT_SGRBG8: sum_r += p2; sum_g += p1; sum_b += p3; break; default: // TODO: Implement the other modes.... fprintf(stderr, "Unsupported v4l pixfmt\n"); exit(1); } } float max = (float) pow(2, libmegapixels_format_bits_per_pixel(mode->format)) - 1.0f; *red = (float) sum_r / (float) total / max; *green = (float) sum_g / (float) total / max; *blue = (float) sum_b / (float) total / max; } int get_control(int sensor_fd, struct control *control) { struct v4l2_ext_control ctrl = {}; ctrl.id = control->id; struct v4l2_ext_controls ctrls = { .ctrl_class = 0, .which = V4L2_CTRL_WHICH_CUR_VAL, .count = 1, .controls = &ctrl, }; if (xioctl(sensor_fd, VIDIOC_G_EXT_CTRLS, &ctrls) == -1) { if (errno != EINVAL) { fprintf(stderr, "VIDIOC_G_EXT_CTRLS\n"); } return 0; } control->value = ctrl.value; return 1; } int set_control(int sensor_fd, struct control *control) { struct v4l2_ext_control ctrl = {}; ctrl.id = control->id; ctrl.value = control->value; if (control->value > control->max || control->value < control->min) { fprintf(stderr, "Value %d is out of range for %ld..%ld\n", control->value, control->min, control->max); return 0; } struct v4l2_ext_controls ctrls = { .ctrl_class = 0, .which = V4L2_CTRL_WHICH_CUR_VAL, .count = 1, .controls = &ctrl, }; if (xioctl(sensor_fd, VIDIOC_S_EXT_CTRLS, &ctrls) == -1) { if (errno != EINVAL) { fprintf(stderr, "VIDIOC_S_EXT_CTRLS\n"); } return 0; } control->value = ctrl.value; return 1; } int query_control(int sensor_fd, struct control *control) { struct v4l2_query_ext_ctrl ctrl = {}; ctrl.id = control->id; if (xioctl(sensor_fd, VIDIOC_QUERY_EXT_CTRL, &ctrl) == -1) { if (errno != EINVAL) { fprintf(stderr, "VIDIOC_QUERY_EXT_CTRL\n"); } return 0; } control->min = ctrl.minimum; control->max = ctrl.maximum; control->step = ctrl.step; control->default_value = ctrl.default_value; return get_control(sensor_fd, control); } int main(int argc, char *argv[]) { int c; int camera_id = 0; long res; char *end; char *outfile = NULL; int mode_idx = 0; int step = 0; int steps = 10; while ((c = getopt(argc, argv, "hc:n:o:m:")) != -1) { switch (c) { case 'c': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -c\n"); return 1; } camera_id = (int) res; break; case 'o': outfile = optarg; break; case 'm': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -m\n"); return 1; } mode_idx = (int) res; break; case 'n': res = strtol(optarg, &end, 10); if (end == optarg || end == NULL || *end != '\0') { fprintf(stderr, "Invalid number for -n\n"); return 1; } steps = (int) res; break; case 'h': usage(argv[0]); return 0; break; case '?': if (optopt == 'd' || optopt == 'l') { fprintf(stderr, "Option -%c requires an argument.\n", optopt); } else if (isprint(optopt)) { fprintf(stderr, "Unknown option '-%c'\n", optopt); } else { fprintf(stderr, "Unknown option character x%x\n", optopt); } return 1; default: usage(argv[0]); return 1; } } char configpath[PATH_MAX]; int ret = libmegapixels_find_config(PATH_MAX, configpath); libmegapixels_devconfig *config = {0}; libmegapixels_init(&config); if (ret) { printf("Using config: %s\n", configpath); libmegapixels_load_file(config, configpath); } else { fprintf(stderr, "No config found for this device\n"); } libmegapixels_load_uvc(config); if (config->count == 0) { fprintf(stderr, "No valid camera configuration\n"); return 1; } if (camera_id > config->count - 1) { fprintf(stderr, "Camera id %d does not exist\n", camera_id); return 1; } libmegapixels_camera *camera = config->cameras[camera_id]; if (libmegapixels_open(camera) != 0) { fprintf(stderr, "Could not open default camera\n"); return 1; } if (mode_idx > camera->num_modes) { fprintf(stderr, "Invalid mode index: %d\n", mode_idx); } libmegapixels_mode *mode = camera->modes[mode_idx]; struct v4l2_format format = {0}; unsigned int frame_size = libmegapixels_select_mode(camera, mode, &format); if (frame_size == 0) { fprintf(stderr, "Could not select mode\n"); return 1; } // Get the handles to the required V4L controls for the calibration struct control shutter = {.id = V4L2_CID_EXPOSURE}; struct control gain = {.id = V4L2_CID_ANALOGUE_GAIN}; if (!query_control(camera->sensor_fd, &shutter)) { fprintf(stderr, "Could not query V4L2_CID_EXPOSURE\n"); return 1; } printf("Exposure: %ld..%ld step %lu val %d\n", shutter.min, shutter.max, shutter.step, shutter.value); if (!query_control(camera->sensor_fd, &gain)) { fprintf(stderr, "Could not query V4L2_CID_ANALOGUE_GAIN\n"); return 1; } printf("Gain: %ld..%ld step %lu val %d\n", gain.min, gain.max, gain.step, gain.value); // Set the controls to the initial state shutter.value = shutter.max; if (!set_control(camera->sensor_fd, &shutter)) { fprintf(stderr, "Could not set the shutter to max\n"); return 1; } // Do the reqular V4L2 stuff to get a frame struct v4l2_capability cap; int mplanes = 0; enum v4l2_buf_type buftype = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(camera->video_fd, VIDIOC_QUERYCAP, &cap) != 0) { fprintf(stderr, "VIDIOC_QUERYCAP failed: %s\n", strerror(errno)); return 1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "Device does not support streaming i/o\n"); return 1; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { mplanes = 1; buftype = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; } else { fprintf(stderr, "Device does not support V4L2_CAP_VIDEO_CAPTURE\n"); return 1; } } struct v4l2_requestbuffers req = {0}; req.count = 4; req.type = buftype; req.memory = V4L2_MEMORY_MMAP; if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) { fprintf(stderr, "VIDIOC_REQBUFS failed: %s\n", strerror(errno)); return 1; } buffers = calloc(req.count, sizeof(*buffers)); for (int i = 0; i < req.count; i++) { struct v4l2_buffer buf = {0}; buf.type = buftype; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; struct v4l2_plane planes[1]; if (mplanes) { buf.m.planes = planes; buf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_QUERYBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_QUERYBUF failed: %s\n", strerror(errno)); return 1; } unsigned int offset; if (mplanes) { buffers[i].length = planes[0].length; offset = planes[0].m.mem_offset; } else { buffers[i].length = buf.length; offset = buf.m.offset; } buffers[i].start = mmap(NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, camera->video_fd, offset); if (buffers[i].start == MAP_FAILED) { fprintf(stderr, "mmap() failed\n"); return 1; } } for (int i = 0; i < req.count; i++) { struct v4l2_buffer qbuf = {0}; qbuf.type = buftype; qbuf.memory = V4L2_MEMORY_MMAP; qbuf.index = i; if (mplanes) { struct v4l2_plane qplanes[1]; qbuf.m.planes = qplanes; qbuf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_QBUF, &qbuf) == -1) { fprintf(stderr, "VIDIOC_QBUF failed: %s\n", strerror(errno)); return 1; } } enum v4l2_buf_type type = buftype; if (xioctl(camera->video_fd, VIDIOC_STREAMON, &type) == -1) { fprintf(stderr, "VIDIOC_STREAMON failed: %s\n", strerror(errno)); return 1; } // Open the target file FILE *outf = fopen(outfile, "w"); if (outf == NULL) { fprintf(stderr, "Could not open output file\n"); return 1; } printf("Performing initial setup...\n"); struct timeval t_start, t_now; gettimeofday(&t_start, NULL); int stage = 1; double point = 1.0; while (stage > 0) { while (1) { fd_set(fds); FD_ZERO(&fds); FD_SET(camera->video_fd, &fds); int sr = select(FD_SETSIZE, &fds, NULL, NULL, NULL); if (sr == -1) { if (errno == EINTR) { continue; } fprintf(stderr, "select() failed: %s\n", strerror(errno)); return 1; } struct v4l2_buffer buf = {0}; buf.type = buftype; buf.memory = V4L2_MEMORY_MMAP; if (mplanes) { struct v4l2_plane dqplanes[1]; buf.m.planes = dqplanes; buf.length = 1; } if (xioctl(camera->video_fd, VIDIOC_DQBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_DQBUF failed\n"); return 1; } if (stage == 1) { // Setup stage to figure out initial brightness gettimeofday(&t_now, NULL); if (t_now.tv_sec - t_start.tv_sec > 1) { gettimeofday(&t_start, NULL); float red, green, blue; brightness(buffers[buf.index].start, buf.bytesused, mode, &red, &green, &blue); printf("Brightness: %f, %f, %f\n", red, green, blue); if (red == 1.0f || green == 1.0f || blue == 1.0f) { // Clipping the sensor. Lower gain if (gain.value == gain.min) { printf("! Lower the light source brightness, out of gain range\n"); } else { gain.value -= gain.step; set_control(camera->sensor_fd, &gain); } } else if (red > 0.9 && green > 0.9 && blue > 0.9) { printf("Set up target hit, continue to calibration...\n\n\n"); stage = 2; } } } else if (stage == 2) { gettimeofday(&t_now, NULL); if (t_now.tv_sec - t_start.tv_sec > 1) { gettimeofday(&t_start, NULL); float red, green, blue; brightness(buffers[buf.index].start, buf.bytesused, mode, &red, &green, &blue); printf("[%4d / %4d] %f: %f, %f, %f\n", step, steps, point, red, green, blue); fprintf(outf, "%f,%f,%f,%f\n", point, red, green, blue); step++; // Get next shutter value point = (double) step / (double) steps; point = 1.0 - point; uint32_t exposure = (uint32_t) (shutter.max * point); if (exposure < shutter.min) { exposure = shutter.min; } // Set the new shutter value for the next iteration shutter.value = exposure; set_control(camera->sensor_fd, &shutter); if (step == steps + 1) { stage = 0; fclose(outf); } } } if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) { fprintf(stderr, "VIDIOC_DQBUF failed\n"); return 1; } break; } } return 0; }