pax_global_header00006660000000000000000000000064121554167730014525gustar00rootroot0000000000000052 comment=57608ce43961c4b8bc4b78805de11e3b88fe3da7 harvid-0.7.3/000077500000000000000000000000001215541677300130115ustar00rootroot00000000000000harvid-0.7.3/COPYING000066400000000000000000000432541215541677300140540ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. harvid-0.7.3/ChangeLog000066400000000000000000000067511215541677300145740ustar00rootroot000000000000002013-06-10 (0.7.3) Robin Gareus * update ffmpeg compatibility wrapper * fix CSV file info (no newline) * fix fileindex' file-ext filter 2013-04-16 (0.7.2) Robin Gareus * fix aspect ratio calculation * update website & upload script 2013-04-07 (0.7.1) Robin Gareus * basically a NOOP -- no new features, no changes in behaviour. * just shuffle around code to break out libharvid * update packaging info (post debian) * clean up auto-build/release/upload scripts * oh and, PTHREAD_SIGMASK is now disabled on OSX - allow sigterm, there :) 2013-03-26 (v0.7.0) Robin Gareus * option to set maxiumum decoder thread count * fix make install-man * various build-script updates for static builds * send 503/retry instead of 500/err if video-cache fails fixes concurrency issue. 2013-03-19 (v0.6.2) Robin Gareus * distinguish decoder-failure (500) and no-decoder thread avail (503/try again) * fix libjpeg detection (bashish echo) * fix various typos (makefile, manpage) 2013-03-09 (v0.6.1) Robin Gareus * fix static build * 32/64bit print size_t fix 2013-03-09 (v0.6.0) Robin Gareus * image-cache for encoded images (jpg, png) * add html/JS seek interface * fix SMPTE duration print - subframes == 0 * fix compiler warnings 2013-03-02 (v0.5.2) Robin Gareus * update README and built-in documentation on homepage * exclude packaging related files from source tar balls * add mtime to file-index * fileindex: add trailing slash to directory URLs * fileindex: fix file-extention filter 2013-02-25 (v0.5.1) Robin Gareus * valid HTML * new OSX icon 2013-02-25 (v0.5.0) Robin Gareus * support for OSX bundle * renice status page html * cope with invalid input data (negative frames, invalid width/height,..) * server process exit status * update release/build scripts 2013-02-24 (v0.4.6) Robin Gareus * usage frequecy statistics (debug mode only) * optional server exit-on-idle timeout (prepare launchd, systemd) * export file-index as CSV and JSON 2013-02-23 (v0.4.5) Robin Gareus * allow to set JPEG quality * fix chroot + chuid/gid lookup and execution order * incremental file-index output * dynamically sized output buffering for info pages 2013-02-23 (v0.4.X) Robin Gareus * prepare website - binary releases * release and build scripts * man page formatting 2013-02-20 (v0.4.0) Robin Gareus * replace linear lists with hash-table - fast lookup * implement "empty frame" rendering * add support for memlock 2013-02-16 (v0.3.0) Robin Gareus * valid HTML * update --help and manual page * add support for CSV info format * sanity check options on startup * add support to choose pixel format dynamically * new banner/logo image * remove cruft from decoder and frame-cache code 2013-02-14 (v0.2.0) Robin Gareus * server ID and built-in version,rc query handlers * portability and x-compile setup * proper shutdown on signal * ffmpeg concurrency improvements * separate file<>ID mapping * rework decoder control, reduce footprint of locked section * reference count decoder locks, non-blocking info calls * clean up logging and log-levels * flexible cache flusing and purging strategies 2013-02-11 (v0.1.0) Robin Gareus * more refactoring, add CSV index 2013-01-30 (v0.0.1) Robin Gareus * refactored sodankyla/icsd harvid-0.7.3/Makefile000066400000000000000000000007641215541677300144600ustar00rootroot00000000000000VERSION?=$(shell git describe --tags HEAD || echo "X.X.X") SUBDIRS = libharvid src doc default: all $(SUBDIRS):: $(MAKE) -C $@ $(MAKECMDGOALS) all clean man install uninstall install-bin install-man uninstall-bin uninstall-man install-lib uninstall-lib: $(SUBDIRS) dist: git archive --format=tar --prefix=harvid-$(VERSION)/ HEAD | gzip -9 > harvid-$(VERSION).tar.gz .PHONY: clean all subdirs install uninstall dist install-bin install-man uninstall-bin uninstall-man install-lib uninstall-lib harvid-0.7.3/README.md000066400000000000000000000066431215541677300143010ustar00rootroot00000000000000harvid -- HTTP Ardour Video Daemon ================================== Harvid decodes still images from movie files and serves them via HTTP. Its intended use-case is to efficiently provide frame-accurate data and act as second level cache for rendering the video-timeline in [Ardour](http://ardour.org). Download -------- Apart from the source-code and packages from your linux-distributor, binaries are available for OSX, Windows and Linux at http://x42.github.com/harvid/ . Usage ----- Harvid is a standalone HTTP server, all interaction takes place via HTTP. After launching it, simply point a web-browser at http://localhost:1554/ The OSX bundle and window installer come with a shortcut link to launch the server. On Linux or with the OSX package, harvid is usually started from a terminal by simply typing `harvid`<enter>. Harvid can also be run directly from the source folder without installing it. Get its build-dependencies (see below), run make ./src/harvid When used from ardour, ardour will automatically start the server when you open a video. Ardour searches $PATH or asks your for where it can find harvid. The easiest way is to simply run: sudo make install Harvid can be launched as system-service (daemonized, chroot, chuid, syslog), and listen on specific interfaces only in case you do not want to expose access to your movie-collection. However, is no per request access control. For available options see `harvid --help` or the included man page which is also available online at http://x42.github.com/harvid/harvid.1.html Build-dependencies ------------------ [ffmpeg](http://ffmpeg.org/) is used to decode the movie. The source code should be compatible and compile with [libav](https://libav.org/). For encoding images, [libpng](http://www.libpng.org/pub/png/libpng.html) and [libjpeg](http://libjpeg.sourceforge.net/) are required. Packaging Information --------------------- A good start is to look in the `debian/` folder that comes with the source code (it is excluded from source archives via .gitattributes). In particular the file `debian/rules` which demonstrates the use of PREFIX and DESTDIR. Internals --------- Harvid is highly concurrent makes use of all available CPUs. It will spawn multiple decoder processes, keep them available for a reasonable time and also cache the video-decoder's output for recurring requests. The cache-size is variable only limited by available memory. All images are served from the cache, so even if you are not planning to use the built-in frame cache, the cache-size defines the minimum number of concurrent connections. Interface --------- The HTTP request interface is documented on the homepage of the server itself: http://localhost:1554/ The default request-handler will respond to `/?file=PATH&frame=NUMBER` requests. Optionally `&w=NUM` and `&h=NUM` can be used to alter the geometry and `&format=FMT` to request specific pixel-formats and/or encodings. `/index[/PATH]` allows to get a list of available files - either as tree or as flat-list with the ?flatindex=1 as recursive list of the server's docroot. `/info?file=PATH` returns information about the video-file. Furthermore there are built-in request handlers for status-information, server-version and configuration as well as admin-tasks such as flushing the cache or closing decoders. The `&format=FMT` also applies for information requests with HTML, JSON, CSV and plain text as available formatting options. harvid-0.7.3/common.mak000066400000000000000000000024621215541677300147770ustar00rootroot00000000000000VERSION?=$(shell git describe --tags HEAD || echo "X.X.X") CFLAGS ?= -Wall -g -O2 PREFIX ?= /usr/local bindir ?= $(PREFIX)/bin mandir ?= $(PREFIX)/share/man libdir ?= $(PREFIX)/lib docdir ?= $(PREFIX)/share/doc includedir ?= $(PREFIX)/include man1dir ?= $(mandir)/man1 hdocdir ?= $(docdir)/harvid ECHO=$(shell which echo) -e ARCHFLAGS= ARCHINCLUDES= ARCHLIBES= LIBEXT=so ifeq ($(ARCH),mingw) CC=i686-w64-mingw32-gcc LD=i686-w64-mingw32-ld AR=i686-w64-mingw32-ar NM=i686-w64-mingw32-nm -B RANLIB=i686-w64-mingw32-ranlib STRIP=i686-w64-mingw32-strip WINEROOT?=$(HOME)/.wine/drive_c/x-prefix PKG_CONFIG_PATH=$(WINEROOT)/lib/pkgconfig/ ARCHINCLUDES=-I$(WINEROOT)/include -DHAVE_WINDOWS ARCHLIBES=-lwsock32 -lws2_32 -lpthreadGC2 LDFLAGS+=-L$(WINEROOT)/lib/ -L$(WINEROOT)/bin UNAME=mingw LIBEXT=dll else RANLIB=ranlib STRIP=strip NM=nm UNAME=$(shell uname) ifeq ($(UNAME),Darwin) ARCHFLAGS=-arch i386 -arch ppc -arch x86_64 ARCHFLAGS+=-isysroot /Developer/SDKs/MacOSX10.5.sdk -mmacosx-version-min=10.5 ARCHFLAGS+=-headerpad_max_install_names ARCHLIBES+=-sectcreate __DATA __doc_harvid_jpg ../doc/harvid.jpg ARCHLIBES+=-sectcreate __DATA __doc_seek_js ../doc/seek.js LOGODEP= ECHO=echo LIBEXT=dylib NM=nm else ARCHLIBES=-lrt -lpthread LIBEXT=so NM=nm -B endif endif harvid-0.7.3/doc/000077500000000000000000000000001215541677300135565ustar00rootroot00000000000000harvid-0.7.3/doc/Makefile000066400000000000000000000013761215541677300152250ustar00rootroot00000000000000include ../common.mak all: install: install-man uninstall: uninstall-man install-man: harvid.1 install -d $(DESTDIR)$(man1dir) install -m644 harvid.1 $(DESTDIR)$(man1dir) uninstall-man: rm -f $(DESTDIR)$(man1dir)/harvid.1 -rmdir $(DESTDIR)$(man1dir) install-bin: uninstall-bin: install-lib: install -d $(DESTDIR)$(hdocdir)/examples install -m 644 libharvid_example.c $(DESTDIR)$(hdocdir)/examples/ gzip -9 $(DESTDIR)$(hdocdir)/examples/libharvid_example.c uninstall-lib: rm -f $(DESTDIR)$(hdocdir)/examples/libharvid_example.c.gz -rmdir $(DESTDIR)$(hdocdir)/examples -rmdir $(DESTDIR)$(hdocdir) -rmdir $(DESTDIR)$(docdir) man: clean: .PHONY: all install uninstall install-man uninstall-man install-bin uninstall-bin install-lib uninstall-lib harvid-0.7.3/doc/harvid.1000066400000000000000000000063361215541677300151250ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. .TH HARVID "1" "June 2013" "harvid v0.7.3" "User Commands" .SH NAME harvid \- video server .SH SYNOPSIS .B harvid [\fIOPTION\fR] [\fIdocument-root\fR] .SH DESCRIPTION harvid \- http ardour video server .SH OPTIONS .TP \fB\-A\fR , \fB\-\-admin\fR space separated list of allowed admin commands. An exclamation\-mark before a command disables it. default: 'flush_cache'; available: flush_cache, purge_cache, shutdown .TP \fB\-c\fR , \fB\-\-chroot\fR change system root \- jails server to this path .TP \fB\-C\fR set initial frame\-cache size (default: 128) .TP \fB\-D\fR, \fB\-\-daemonize\fR fork into background and detach from TTY .TP \fB\-g\fR , \fB\-\-groupname\fR assume this user\-group .TP \fB\-h\fR, \fB\-\-help\fR display this help and exit .TP \fB\-F\fR , \fB\-\-features\fR space separated list of optional features. An exclamation\-mark before a features disables it. default: 'index'; available: index, seek, flatindex, keepraw .TP \fB\-l\fR , \fB\-\-logfile\fR specify file for log messages .TP \fB\-M\fR, \fB\-\-memlock\fR attempt to lock memory (prevent cache paging) .TP \fB\-p\fR , \fB\-\-port\fR TCP port to listen on (default 1554) .TP \fB\-P\fR IP address to listen on (default 0.0.0.0) .TP \fB\-q\fR, \fB\-\-quiet\fR, \fB\-\-silent\fR inhibit usual output (may be used thrice) .TP \fB\-s\fR, \fB\-\-syslog\fR send messages to syslog .TP \fB\-t\fR set maximum decoder\-threads (default: 8) .TP \fB\-T\fR , \fB\-\-timeout\fR set a timeout after which the server will terminate if no new request arrives .TP \fB\-u\fR , \fB\-\-username\fR server will act as this user .TP \fB\-v\fR, \fB\-\-verbose\fR print more information (may be used twice) .TP \fB\-V\fR, \fB\-\-version\fR print version information and exit .PP The default document\-root (if unspecified) is the system root: / or C:\e. .PP If both syslog and logfile are given that last specified option will be used. .PP \fB\-\-verbose\fR and \fB\-\-quiet\fR are additive. The default is to print warnings and above only. Available log\-levels are 'mute', 'critical, 'error', \&'warning' and 'info'. .PP The \fB\-\-features\fR option allows to enable and disable http\-handlers /seek and /index as well as tweak related behaviour. The 'flatindex' option concerns /index&flatindex=1 which recursively indexes all video file. It is disabled by defaults since a recursive search of the default docroot / can takes a very long time. When requesting png or jpeg images harvid decodes a raw RGB frame and then encodes it again. If 'keepraw' feature is enabled, both the raw RGB and encoded image are kept in cache. The default is to invaldate the RGB frame after encoding the image. .SH EXAMPLES harvid \fB\-A\fR '!flush_cache purge_cache shutdown' \fB\-C\fR 256 /tmp/ .PP sudo harvid \fB\-c\fR /tmp/ \fB\-u\fR nobody \fB\-g\fR nogroup / .SH "REPORTING BUGS" Report bugs to or https://github.com/x42/harvid/issues .br Website http://x42.github.com/harvid/ .PP .br Compiled with Lavf54.29.104 Lavc54.59.100 Lavu51.73.101 .SH COPYRIGHT Copyright \(co GPL 2002\-2013 Robin Gareus harvid-0.7.3/doc/harvid.jpg000066400000000000000000000477001215541677300155450ustar00rootroot00000000000000JFIFHHC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222d " lc!N%#/W%^jXeHlUyz;";/WD5X! qR7@hxlqJ2qQތ&tQifQɘ#4\#wW1ux]941R9f üBr;>36@@I{@Cnu9шCώky}Ct#̋?5)/>3nxkwk#\-M&+7jwSD(2?ygZļOΦ=5").\ҖefΝ #/]e(kOfΕyK@F}#xI.}HI!qM0c'I<5VxR TPFw%Kd:|h GzWtc,d c5qZUq,fpQac hczҫg^3Fcc֕\lK:YX|X0kbYע@ц3XWνg218ǭ*ؖu8(4a1=iUijEEŌ f1J6%z, ,>,d c5qZUq,fpQac hczҫg^3Fcc֕\lK:YX|X(qyFx.9޽C"y+_C>\4g$kdY |E,e7ԇpfp5kci[0mZ^YO杵n+זm\ޏ-T ''Ӿe~i2ٜIgsevrvl<ԥᜒ8ab!Yg6a͖sf5[ qэ`Ll6aߗ03(3-![sM[pӛ.ce☙,˗1#51˗hd6bpre䉑Ȍn|+{k/I|+yqlsS̱uꝓ1J-g.d=yR8 yTγ)4XULoա`ӑw0<%- kHЃ_l>Ք-%p)U:nY'o$(9x^; 'K,6љhM̧Hɱ[-Eљ>4j4Z-j{-[Y3+j3+rE""I\ϵHlyj^zeh *̥dMl|խZ7kqKBE(.-dg DSFJ,Ak;F&#{O~? B2A9/e +fB&eUMJ"xYp.6[ ;fxƸ;]. Kk.n.?K}pvXqTݳaA4n>틋+m908QyJ|5FwFoz0$ͮ_+3[9N4a?C~P=c+v(ӏOїcdvqtɎqK[{n/ȟz]ص;{G{AovZj6 o÷he{U-VDJ޵wU@H_+HwuU)Ij2wqƑY/:"^}=9/K}m )lD[:9z2DWl|Vqi'qͥg $fxOkM9kK[5fDrDM.j2(Lax腈t4IA1FBwCN9$iBnڳCN?OVCUՑquEvq]kG5پ.ՓOU=}.¸Sqjڶ2OnbBJf}mNL/>gӫ - ,tFmt&q<9TۛHseDS(cD-&J^Sc=+Zh~kskJ)Ӣ/uO_n:ڤSq,u(jh#fjHuV &3qA_FZsUkGr{a]VەލB`ѻ\/wZܮ땃ȏ\;qjymK"p\N-IέA+[ukIk#`s\/4krVGkp$f\>I7+6x`a\ Qr8WW+܅X `aܮ/ ;r|Vr|<.W~+#qwWxZqwHnWrNqwW2=ʏBpG\-ە`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K`(K$!1 0A@PQ`?XBj*)JTTREEv?LHKX: 63k!:މ6Vv6rSDZ>4o0CgB!B"/CE 7s?5=Tq8#. 2IDPn>ߍ~&Y'-#z7'<$!0A 1Q@P`q?EEGFqdddgqd#88238####] 4&.t:Oe,#F B#$wPE>Q?<^D8H)Y/7JV^{Pk?o~D߱!!Z'8Z1Q< 1!"2A Qaq$3br#0Bs4@R?cH墍<&:0F^"(yHaa/ov*7y 4Zƒ5]z<\أ 1տت|h(cko+ k1ur/bW8VlVba{GO (;L>3QףȾ=^RXyK[݊ƍB<ٺZ18\h L>0nYC~*!>R=[O@|kj{&g yqN*Q0gbH|tMn>QD Tar LQ ݞ7|*[Q.Ɖ:@D:!50nYC~*!>R=[O@|kj{&g yqN*Q0gbH|tMn>QD Tar &_oHv[[i 2я\'܄6Fp,?T8~ӎ_@DqhTm]/h}Q:}&Mό'jwSw5~,Wyj Nq5 :ݪuji5^}p[.&m#jӭ(#u>r?_\)ۉIiEVG 4~hD {G\A5=nAδDߧA151bD{CD؏-ڞb:ҸMGiw#녬GA .9vK)q5B#qj/=`QDL&h;B;ڦb:cN&^ Tt%=-գI#h}`~!HyDyCuSZH)~~~RI9ꥵS%Hĉ6EsxUsLUA +g@_{TT%>{2g}Fd-ν ))P*͗r;ALU"9u&V\*AYq|Q7覮-y '3?+- ڭ\+Dl:`:5:TOaP݆~4}JsZ$%Ϥ0=wLS̨EݿRowJOrjc ދur trMϱ1w^Ms ^w.?o1)ŠҸIR:v Ȩ,d7\JD.%Ĥ"d񢦶-NQ؝2'_ezSAJJ/VUO>+?vz`s:rD:!00,CEN/!M ]7Bv-ܴZhzG2LcvUJNP?mEzl846SkxLv ֏~x/oVVj=J,֮{`N?U޸J+˄ +G|ڸ܁sΊEKzM|[ޢC-_ "LɳRZj.ȳUR3*vi:E"\> B|D)d!ꉒ }9  ]F'5I3.!!`2mwzT9^ Nף]ԫ.<2>D4]]U~>2g҅ ׈i\W X]qBѧ1) O6fxf9A&d@K kkh #yLb4ntR_҆0ൎNtI.-;{@K^HMI}9"t>#aԍC)XW>F_2=[4)a7[rJZ,?chLObtusRkGbeojT~/P8ki'Eצktt)2ts[6‚{CV7RV@F7wG{#pߣEh:,l,.kgq֌H$21bdbŁKaY`RŁKaDG1b+#, X.j ŋ F4X2kyx/(~f~ (CS#9:,\-,;@>(Əm*r|LFps {hƋz?tx]s ;. ^F,\ ,;Pp4;Pŋj:1bicnv>>ψJSF,\,;@̌X:X۝^F,\%,;@̌XJX.vz/p;@z. ^F,\%,;@̌XJX۝^F,\%,;@=p3a֌XZX۝^XJX.vz/1b)`)bi`)d#3 _21b)cnvz/p\2tXZX.vz?1bi`F,\5,;@>(ŋ永>(ŋ永~(ŋ永~(ŋ氬XzX.k ŋ ²1b桑 ,5 (adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES ## ,jn)QcTqH‹FFX0R20Ʃ⑑5L7(adaES !(!1AQaq𑡱 0?! ~H^ O!G?>ЕRAΐלg*c񸱍A>>AdpɄs U*\-yr?#H^ O!G?>ЕRAΐלg*c񸱍A>>AdpɄs U*\-yr?#]AO="^ WBMT%Dw."Huc rqI'b+t.BTG}`$G^X0(O$}"^ WBMT%Dw."Huc rqI'b+t.BTG}`$G^X0(O$}"^ WBMT%Dw."Huc rqI'b+t.BTG}`$G^X0(O$}"^ WBMT%Dw."Huc rqI'b+t.BTG}`$G^X0(O$}"^ WBMT%Dw."Huc rqI'b+t.BTG}`$G^X0(O$}"^ WBMT%Dw."Huc rqI'b+t.BTG}xgYq<P{^!!OJ2A@%c~/W¦͖b#QCp{t|-VkOz&vuVD 0!K,߼( :kjv/~󚇯ܲPT? n A,o"o~n<:@o0*lpeд&Fnyiv'sRz8]뇿,­V[C %&fZ OC87N+/:qwQTHVqBv"q9p𶍍:At~sxǼ)*qІaˁ@j>P^e "l~G Cj ~=l|5F.̷#PF#𝤰zD-oz2:p}.6>L1nPh6 @0^0ZX6m|)ۏ?s} QԈyucHKtg~Hrw@{ob>n{օ+ T-02s f=oh9#h}=[ d0<871n!OuzP]xBX<Mрܣ5# Ť bh0`:{&üq; @ U!dL2ĄLRD]T#q0"cJƼ0FawI`Pp`F[VxѸ3n"8@f, GhChj$67x7lTn0&[',!@Aljڜp jnIX BĀfH$z3=!%mE(,G}Bl/C^vaۈu%fTCǫ8M l?[Ag&dZqv%@9kJ.#P!p <!A@56ǁ,1 .)i8 +62 lzHBL'!1 2(4`rC)$;!0$ 0]4چ@܈4͵3= P@ @cX  G<~r cdΨǒfq8Pu0ZJ,W=@4Y1`vrG11p:<2a'E@}'B}؆zYXqi \da'T~7t={&TA51J "Bs4DɪQ/Df11C# FÄQHĕX @X,ZfJzF<^|"lQi$#\+ l"8b)m(85# z "m mb71'oS28y$o_Ë%lC}Bg*5dK0U#2< r :%D+&,t`r,/%A \ W,rÕISЈo9Xelr|]W+[eaP pXi|>)P(4b!>t0G  U$PVRn-d_#11G|$ >QFO  7􆺗3!HhHxBE,H~ >i"j.#Lq&K< HN |v3" з{  [0FA #\%lVԂ5Cxyp> Y-Mu_"7#43X qb= &&ᇁxیS*xi 2 i~HI';po !t AhOgq'H Cc6F`sO"a0@ ҆Ki'?]#3pkDy psL980c0< < 3(S+N6镧Y7cXVi '*,S+C<ܡ L9xRBU 6i<sL $t͵kh:BB!`W5sv)/ .2:hb=iv$9+ܥ/ei<2i L'QYViUU ٦V)+IviJ4ӬJ48gIVbZOħcL CfZO/ģCL'/k ),S+N+S+In6)ʠxb!5m< 236)J+ӬxeݎJӬ?4ӄL:Tvi(S+C<ܧL9:]ehy'(+C:e;ݚehxWѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E8WѴ^{E' 1-#D4B܃41j.\jVnNλRkN _wU3/$Ƃ">ޏB <*!A7ҏ >c>.3#.A @/phtK3sE(>a4O # PBnG|((3"KA`Q01$śJ9PT밓L<_X>w,   !1QaA @0`?#+<)7*=B; =l==C#,{z T1]17F+lHWSepMC0KЙllȬQ&~%^ b H6 t"yebx0H&N8BMKB}1== GDtGGOL.դ' kŀmn@r;#yam ?i|-Ĝl)/Loryc#N1<^[_vumǁ{tć  8I%l!;.].&_p9SZdr\(!1AaQq 0?z:&>n]A 37!25=DjK)WmZV1@h7x߶H% S,B! b% #xWPmh L|MOr.3 UyVP"Z73č71Icb$7CDBUZ(;3++5QJ哉p."%:X $SH#d^IT}˜Ve&c# Q2TRdq9\&KD6HN2CIHUdw.cYIX¡L:ⲳUY8N@ "SE4;&Eq4G*]<)mRi=!20u(ΰE+N'd$Cdc$4M Dɑ{M%QJO b1[EkHe*D3>++5QJ哉p."%:X $SH#d^IT}˜Ve&c# Q2TRdq9\&KD6HN2CIHUdw.cYIX¡L:ⲳUY8N@ "SE4;&Eq4G*]<)mRi=!20u(ΰE+N'd$Cdc$4M Dɑ{M%QJOtC%U9WLe bj?81gQ;G#<*ca6 ߯h^VTH@$S*HI' f#OזR&Ej6=BjOsSSqU#I!^j u $7$dJƱ"&d84r`nzVPcGE2Hpc퀉m\GSRk 0!'ׄN+Zge:kAQ:j^{7$+L 2(Saq )Uzy5Op<{ GD*wBX &.Jmk2pi<@fKk4n|## ":nV6P2v>Rۡ&P=A?n՗qJٹJĹ,G4/i=܋*CH *D.';̦{Y=KG PPUнQwRZd8B5BiRX"&4i=+?%!#fP+.bfHV BMm4͆ h)WDf,fZP$Ve@Q$VD[^ LQ)o J' E|\`a} WrT2@k/L q w+eh iQ`cDCBH[bfVCR6 }0˼\ %(#fP*Ʌw*1" C{= CK 7(|drn}Dz)17&'ixLiJlJCUg@T?%daW: P!!{Ii>=N>Gc'qdX]Y38hBNc4\l. ,0)Z~\1D5Xj$B"# 6&1L^:NF'l|{`N0H`X<#m5t56ߌ>G#M!-Bцp 0d﬿,p %Ŏ?hv8̈́6k&7y? !Tc?Ƃh5u•!@O ,N1d4~" Ir&)a?9`3vSpg loxN7xȊ hIl?E%BWaC??a ƉaCT"~fG %Gh,Q!ߛO@CJDĻyd ?ɓ@0@xc4Cdd UO+YmQ \xƑ&'>G-` 4Xma@\Eyu;0,WlI"|9R~1[A!f;BDO9p{! !>lM`s~hD\=4K3NX!iNɏՉ?NLRi 5dKĦ1A Ƌ/-5d6~d{LhĈ@./)!yl8LAqN'x,Ѧ2q_7Y\N|E}s?VOK@J~p0i}(&LahpC4GX0q%yA9,MxfM12n78.GoyxDlay %!1`. aIp+a,4n&8vUgtu xDp+='&ܽ@)%(1?R/_(H'rU)!c\<)g+ܳ#KV!YYL,A[1垌MAaIDL!PJ1gБ0:戆G;X  (*c*!>1)mHA"6I@hGaY#LJӬc :f_,LQ|B]AE[?8 9wVtE8.9ĠEx"'>8R!K #x/}F_P8$I23`}C"w~2ľLr.%Ha0 ]_it0(;"g'`W#y5aI2_cE?d*&]'PHDvۂ 1(V>\UU2eCk>ߜ 2(8-8nC_dObzb0Bj!8:Sj 2+J|v'e7) `[?Z@Dh@M)k-!l-Pץ$D.aBauϢSqCБY`ǜQ.'mrӊÕh!?hdZ5^ !P8 qTB& 8>'Ñd)hFFD xƶ\ ~rESrW[3dav FʵK!Gɋpbs| CyLqqT8@!2_(#'0=&9șϟܔbp@x@3w8C$]1h2K+r2oD,H|2|6V `ۄ4 +r#K:їh TM{hV 9"I*r YUsB'PbB=+NBvo^ JX-jPƪ=c:sLTbH- dةߥ`ر^$q9(Ō!/ SIgU{\,a%qÌ&EN 01c"м?,Qh=r28+偊v+lL "[3yB-I|#Ȇ89p Y.J#4)'z V'&QZvcu9ãĢ;Qmv`ebHd։1#qSp^1'E1b$TRk;3G0;gp¥[/VRÜz(0g~ ?(\ $I!Ը7thgrYHF0GߌʥsU2䑐 Og\ {P18s,q<lҘe/@x#Y]~2%全?DOO2s& BY,0o 3L\:4`4B1380WI [ wbN^ɯHØ$ !2h8ͧ92ν𣼧8ۼǷodŋ%fI%l k%l`\;$@ra(=h$ Ek WZ R-g>X )'T[pIe~Ǔ#Jv@mM|03@[S3I4 E(@2I<5DSi Y&@$Isp D#$ JhdD @2QIg YA/0a!(d`Jr$\#:@-EXdA[CX ]S PV2#'UH5mm g3'hQwA(%(Nn*Khk%86@-JQHum&I[B#Q4 J|  1N5v"eC$"qթ%ld^ RVУQ@qn-D0;ye׸Ils'@;Bg_HQptdI[C\.y$ڮ c,& RVУ:l@gYd"_siR-$ EQ?]gLH`3ä4 E(_I R-GRH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4u)$b >JI XORH)`ԤX4harvid-0.7.3/doc/libharvid_example.c000066400000000000000000000071121215541677300174020ustar00rootroot00000000000000/* compile with * * gcc -o libharvid_example libharvid_example.c \ * -I../libharvid/ ../libharvid/libharvid.a \ * `pkg-config --cflags --libs libavcodec libavformat libswscale libavutil` * */ #include #include /* ffmpeg decoder honors these externs */ int want_verbose = 0; int want_quiet = 1; /* dlog implementation required by libharvid */ #ifndef NDEBUG int debug_section = 0; #endif int debug_level = 0; void dlog(int level, const char *format, ...) {} /* this example decodes frame #5 of the given video-file * and write a ppm image to '/tmp/libharvid_example.ppm' */ void dothework(void *dc, void *vc, const char *file_name) { unsigned short vid; // video id void *cptr = NULL; // pointer to cacheline uint8_t *bptr = NULL; // decoded video-frame data VInfo ji; // video file info int err = 0; int decode_fmt = PIX_FMT_RGB24; // see libavutil.h int64_t frame = 5; // video-frame to decode -- start counting at zero. /* get (or create) numeric ID for given file */ vid = dctrl_get_id(vc, dc, file_name); jvi_init(&ji); /* get canonical output width/height and corresponding buffersize * width,height == 0 -> original width, no-scaling */ if ((err=dctrl_get_info_scale(dc, vid, &ji, /*out_width*/ 0 , /*out_height*/ 0, decode_fmt)) || ji.buffersize < 1) { /* no decoder can be found, or the decoder can not provide information */ if (err == 503) { fprintf(stderr, "503/try again -- Service Temporarily Unavailable.\nNo decoder is available. The server is currently busy or overloaded.\n"); } else { fprintf(stderr, "500/service unavailable -- No decoder is available\nFile is invalid (no video track, unknown codec, invalid geometry,..)\n"); } return; } /* get frame from cache - or decode it into the cache * * Note: the 'cache' provides a zero-copy buffer to both * the decoder-backend as well as to this user-code. * -> it is not possible to bypass the cache. */ bptr = vcache_get_buffer(vc, dc, vid, frame, ji.out_width, ji.out_height, decode_fmt, &cptr, &err); if (!bptr) { /* an error occured while decoding the frame */ if (err == 503) { fprintf(stderr, "503/try again -- Service Temporarily Unavailable\nVideo cache is unavailable. The server is currently busy or overloaded.\n"); } else { fprintf(stderr, "500/service unavailable -- No decoder or cache is available\nFile is invalid (no video track, unknown codec, invalid geometry,..)\n"); } return; } /* DO SOMETHING WITH THE VIDEO DATA */ const char * outfn = "/tmp/libharvid_example.ppm"; printf("writing ppm image to '%s' (%d bytes RGB, %dx%d)\n", outfn, ji.buffersize, ji.out_width, ji.out_height); FILE *x = fopen(outfn, "w"); fprintf(x, "P6\n%d %d\n255\n", ji.out_width, ji.out_height); fwrite(bptr, ji.out_height, 3*ji.out_width, x); fclose(x); /* tell cache we're not using data at bptr (in cacheline cptr) anymore */ vcache_release_buffer(vc, cptr); } int main (int argc, char **argv) { const int cache_size = 128; const int max_decoder_threads = 8; void *dc = NULL; // decoder control void *vc = NULL; // video frame cache /* initialize */ ff_initialize(); vcache_create(&vc); vcache_resize(&vc, cache_size); dctrl_create(&dc, max_decoder_threads, cache_size); /* now multiple threads can access the decoder and cache * simultaneously. There can be more user-threads than * max_decoder_threads .. * * However, in this example we don't use pthreads.. */ dothework(dc, vc, "/tmp/test.avi"); /* cleanup */ vcache_destroy(&vc); dctrl_destroy(&dc); ff_cleanup(); return 0; } harvid-0.7.3/doc/seek.js000066400000000000000000000043251215541677300150470ustar00rootroot00000000000000var curframe=-1; var mode=0; var base_url="/"; function show(foo) { document.getElementById(foo).style.display = "block"; } function hide(foo) { document.getElementById(foo).style.display = "none"; } function updateText (elemid, val) { var tn = document.createTextNode(val); document.getElementById(elemid).replaceChild(tn, document.getElementById(elemid).firstChild); } function PadDigits(n, totalDigits) { n = n.toString(); var pd = ''; if (totalDigits > n.length) { for (i=0; i < (totalDigits-n.length); i++) { pd += '0'; } } return pd + n.toString(); } function settc(f) { var fpsi = Math.ceil(fps); var hour,min,sec,frame, s; if (Math.floor(fps * 100) == 2997) { var D = Math.floor(f / 17982); var M = f % 17982; var n = f + 18 * D + 2 * Math.floor((M - 2) / 1798); s = Math.floor(n/30); frame = Math.floor(n%30); } else { s = Math.floor(f/fpsi); frame = f%fpsi; } hour = Math.floor(s/3600); min = Math.floor(s/60)%60; sec = Math.floor(s%60); updateText('frame', PadDigits(frame, 2)); updateText('sec', PadDigits(sec, 2)); updateText('min', PadDigits(min, 2)); updateText('hour', PadDigits(hour, 2)); } function seek(i, f) { if (curframe == f) return; curframe = f; document.getElementById('sframe').src=base_url+'?file='+i+'&frame='+f+'&w=-1&h=300&format=jpeg60'; } function setslider(frame) { var val = 1 + Math.round(500.0 * frame / lastframe); if (val < 1 || val > 501) return; document.getElementById('knob').style.width = val+'px'; } function movestep(e) { var mouse_x = e.clientX; var xoff = document.getElementById('slider').offsetLeft; var spos = mouse_x - xoff; if (mode == 1) { spos = Math.floor(spos/5) * 5; } if (spos < 0 ) spos = 0; if (spos > 500 ) spos = 500; var frame = Math.round(spos * lastframe / 500.0); if (frame == curframe) return; setslider(frame); settc(frame); seek(fileid, frame); } function smode(m) { if (m == 1) { hide('stepper_setup'); show('stepper_active'); mode = 1; document.getElementById('slider').onmousemove = movestep; } else { show('stepper_setup'); hide('stepper_active'); mode = 0; document.getElementById('slider').onmousemove = null; } } harvid-0.7.3/libharvid/000077500000000000000000000000001215541677300147555ustar00rootroot00000000000000harvid-0.7.3/libharvid/Makefile000066400000000000000000000072311215541677300164200ustar00rootroot00000000000000include ../common.mak FLAGS= FLAGS+=$(ARCHINCLUDES) $(ARCHFLAGS) FLAGS+=`pkg-config --cflags libavcodec libavformat libavutil libswscale` LIBHARVID_OBJECTS = \ decoder_ctrl.o \ ffdecoder.o \ frame_cache.o \ image_cache.o \ timecode.o \ vinfo.o LIBHARVID_H = \ decoder_ctrl.h \ ffdecoder.h \ frame_cache.h \ image_cache.h\ ffcompat.h \ timecode.h \ vinfo.h all: libharvid.a libharvid.a: $(LIBHARVID_OBJECTS) $(LIBHARVID_H) ifeq ($(UNAME),Darwin) /usr/bin/libtool -static -o libharvid.a $(LIBHARVID_OBJECTS) else $(AR) cru libharvid.a $(LIBHARVID_OBJECTS) $(RANLIB) libharvid.a endif .libharvid.sym: $(LIBHARVID_OBJECTS) $(LIBHARVID_H) $(NM) $(LIBHARVID_OBJECTS) \ | sed -n -e 's/^.*[ ]\([ABCDGIRSTW][ABCDGIRSTW]*\)[ ][ ]*\([_A-Za-z][_A-Za-z0-9]*\)$$/\1 \2 \2/p' \ | sed '/ __gnu_lto/d' | sed 's/.* //' | sed 's/^_//g' \ | sort | uniq \ | grep -E -e "^(dctrl_|vcache_|jvi_|ff_cleanup|ff_initialize|icache_).*" \ > .libharvid.sym libharvid.dll: $(LIBHARVID_OBJECTS) $(LIBHARVID_H) .libharvid.sym dlog_null.c echo "asm (\".section .drectve\");" > .libharvid_dll.c sed -e "s/\(.*\)/asm (\".ascii \\\\\" -export:\1\\\\\"\");/" .libharvid.sym >> .libharvid_dll.c $(CC) -o libharvid.$(LIBEXT) \ -shared \ $(LIBHARVID_OBJECTS) \ $(CFLAGS) $(FLAGS) \ .libharvid_dll.c dlog_null.c \ $(LDFLAGS) `pkg-config --libs libavcodec libavformat libavutil libswscale` $(ARCHLIBES) $(STRIP) libharvid.$(LIBEXT) libharvid.dylib: $(LIBHARVID_OBJECTS) $(LIBHARVID_H) .libharvid.sym sed 's,^,_,' .libharvid.sym > .libharvid.expsym $(CC) -o libharvid.$(LIBEXT) \ -dynamiclib \ $(LIBHARVID_OBJECTS) \ $(CFLAGS) $(FLAGS) \ -install_name $(libdir)/libltc.0.$(LIBEXT) \ -Wl,-undefined -Wl,dynamic_lookup \ -Wl,-single_module -Wl,-exported_symbols_list,.libharvid.expsym dsymutil libharvid.$(LIBEXT) libharvid.so: $(LIBHARVID_OBJECTS) $(LIBHARVID_H) .libharvid.sym echo "{ global:" > .libharvid.ver sed -e "s/\(.*\)/\1;/" .libharvid.sym >> .libharvid.ver echo "local: *; };" >> .libharvid.ver $(CC) -o libharvid.$(LIBEXT) \ -fPIC -shared \ $(LIBHARVID_OBJECTS) \ $(CFLAGS) $(FLAGS) \ -Wl,-soname -Wl,libharvid.so.0 -Wl,-version-script -Wl,.libharvid.ver $(STRIP) libharvid.$(LIBEXT) %.o: %.c $(LIBHARVID_H) $(CC) -c -o $(@) $(CFLAGS) $(FLAGS) $< install-lib: libharvid.a libharvid.$(LIBEXT) install -d $(DESTDIR)$(libdir)/pkgconfig/ sed 's!@PREFIX@!'$(PREFIX)'!;s!@LIBDIR@!'$(libdir)'!;s!@VERSION@!'$(VERSION)'!' harvid.pc.in > harvid.pc install -m 644 harvid.pc $(DESTDIR)$(libdir)/pkgconfig/ install -m 644 libharvid.$(LIBEXT) $(DESTDIR)$(libdir)/ install -m 644 libharvid.a $(DESTDIR)$(libdir)/ install -d $(DESTDIR)$(includedir)/harvid/ install -m 644 harvid.h $(LIBHARVID_H) $(DESTDIR)$(includedir)/harvid/ install -d $(DESTDIR)$(hdocdir)/examples install -m 644 dlog_null.c $(DESTDIR)$(hdocdir)/examples/ gzip -9 $(DESTDIR)$(hdocdir)/examples/dlog_null.c uninstall-lib: rm -f $(DESTDIR)$(libdir)/pkgconfig/harvid.pc rm -f $(DESTDIR)$(libdir)/libharvid.$(LIBEXT) rm -f $(DESTDIR)$(libdir)/libharvid.a rm -rf $(DESTDIR)$(includedir)/harvid/ rm -f $(DESTDIR)$(hdocdir)/examples/dlog_null.c.gz -rmdir $(DESTDIR)$(libdir)/pkgconfig -rmdir $(DESTDIR)$(libdir) -rmdir $(DESTDIR)$(hdocdir)/examples -rmdir $(DESTDIR)$(hdocdir) -rmdir $(DESTDIR)$(docdir) -rmdir $(DESTDIR)$(includedir) clean: rm -f *.o libharvid.a libharvid.so libharvid.dll libharvid.dylib harvid.pc .libharvid.sym .libharvid.ver .libharvid_dll.c install: uninstall: install-man: uninstall-man: install-bin: uninstall-bin: man: libharvid.a clean: .PHONY: all install uninstall install-man uninstall-man install-bin uninstall-bin install-lib uninstall-lib harvid-0.7.3/libharvid/decoder_ctrl.c000066400000000000000000000644131215541677300175620ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "decoder_ctrl.h" #include "frame_cache.h" #include "ffdecoder.h" #include "ffcompat.h" #include "dlog.h" #define DEFAULT_PIX_FMT (PIX_FMT_RGB24) // TODO global default //#define HASH_EMIT_KEYS 3 #define HASH_FUNCTION HASH_SAX #include "uthash.h" /* Video Object Flags */ #define VOF_USED 1 ///< decoder is currently in use - decoder locked #define VOF_OPEN 2 ///< decoder is idle - decoder is a valid pointer for the file/ID #define VOF_VALID 4 ///< ID and filename are valid (ID may be in use by cache) #define VOF_PENDING 8 ///< decoder is just opening a file (my_open_movie) #define VOF_INFO 16 ///< decoder is currently in use for info (size/fps) lookup only /* id + fmt */ #define CLKEYLEN (offsetof(JVOBJECT, frame) - offsetof(JVOBJECT, id)) typedef struct JVOBJECT { unsigned short id; // file ID from VidMap int fmt; // pixel format int64_t frame; // decoded frame-number time_t lru; // least recently used time int hitcount_decoder; // least-frequently used idea int hitcount_info; // least-frequently used idea pthread_mutex_t lock; // lock to modify flags and refcnt int flags; int infolock_refcnt; void *decoder; // opaque ffdecoder struct JVOBJECT *next; UT_hash_handle hhi; UT_hash_handle hhf; } /*__attribute__((__packed__)) */ JVOBJECT; typedef struct VidMap { unsigned short id; char *fn; time_t lru; UT_hash_handle hh; UT_hash_handle hr; } VidMap; typedef struct JVD { JVOBJECT *jvo; // list of all decoder objects JVOBJECT *jvf; // hash-index of jvo JVOBJECT *jvi; // hash-index of jvo VidMap *vml; // filename -> id map VidMap *vmr; // filename <- id map unsigned short monotonic; // monotonic count for VidMap ID (wrap-around case is handled) int max_objects; // config int cache_size; // config int busycnt; // prevent cache purge/cleanup while decoders are active int purge_in_progress; pthread_mutex_t lock_jvo; // lock to modify (append to) jvo list (TODO consolidate w/ lock_jdh) pthread_rwlock_t lock_jdh; // lock for jvo index-hash pthread_rwlock_t lock_vml; // lock to modify monotonic (TODO consolidate w/ lock_jdh) pthread_mutex_t lock_busy; // lock to modify busycnt; } JVD; /////////////////////////////////////////////////////////////////////////////// // ffdecoder wrappers static inline int my_decode(void *vd, unsigned long frame, uint8_t *b, int w, int h) { int rv; ff_resize(vd, w, h, b, NULL); rv = ff_render(vd, frame, b, w, h, 0, w, w); ff_set_bufferptr(vd, NULL); return rv; } static inline int my_open_movie(void **vd, char *fn, int render_fmt) { if (!fn) { dlog(DLOG_ERR, "DCTL: trying to open file w/o filename.\n"); return -1; } ff_create(vd); assert ( render_fmt == PIX_FMT_YUV420P || render_fmt == PIX_FMT_YUV440P || render_fmt == PIX_FMT_YUYV422 || render_fmt == PIX_FMT_UYVY422 || render_fmt == PIX_FMT_RGB24 || render_fmt == PIX_FMT_BGR24 || render_fmt == PIX_FMT_RGBA || render_fmt == PIX_FMT_ARGB || render_fmt == PIX_FMT_BGRA ); if (!ff_open_movie (*vd, fn, render_fmt)) { debugmsg(DEBUG_DCTL, "DCTL: opened file: '%s'\n", fn); } else { dlog(DLOG_ERR, "DCTL: Cannot open file: '%s'\n", fn); ff_destroy(vd); return(1); } return(0); } static inline void my_destroy(void **vd) { ff_destroy(vd); } static inline void my_get_info(void *vd, VInfo *i) { ff_get_info(vd, i); } static inline void my_get_info_canonical(void *vd, VInfo *i, int w, int h) { ff_get_info_canonical(vd, i, w, h); } /////////////////////////////////////////////////////////////////////////////// // Video object management // static JVOBJECT *newjvo (JVOBJECT *jvo, pthread_mutex_t *appendlock) { debugmsg(DEBUG_DCTL, "DCTL: newjvo() allocated new decoder object\n"); JVOBJECT *n = calloc(1, sizeof(JVOBJECT)); n->fmt = PIX_FMT_NONE; n->frame = -1; pthread_mutex_init(&n->lock, NULL); JVOBJECT *cptr = jvo; pthread_mutex_lock(appendlock); while (cptr && cptr->next) cptr = cptr->next; if (cptr) cptr->next = n; pthread_mutex_unlock(appendlock); return(n); } /* return idle decoder-object for given file-id * prefer decoders with nearby (lower) frame-number * * this function is non-blocking (no locking): * there is no guarantee that the returned object's state * was not changed meanwhile. */ static JVOBJECT *testjvd(JVOBJECT *jvo, unsigned short id, int fmt, int64_t frame) { JVOBJECT *cptr; JVOBJECT *dec_closed = NULL; JVOBJECT *dec_open = NULL; time_t lru_open = time(NULL) + 1; time_t lru_closed = time(NULL) + 1; int64_t framediff = -1; int found = 0, avail = 0; for (cptr = jvo; cptr; cptr = cptr->next) { if (!(cptr->flags&VOF_VALID) || cptr->id != id) { continue; } if (fmt != PIX_FMT_NONE && cptr->fmt != fmt && cptr->fmt != PIX_FMT_NONE ) { continue; } found++; if (cptr->flags&(VOF_USED|VOF_PENDING|VOF_INFO)) { continue; } avail++; if (!(cptr->flags&VOF_OPEN)) { if (cptr->lru < lru_closed) { lru_closed = cptr->lru; dec_closed = cptr; } continue; } if (frame < 0) { // LRU only if (cptr->lru < lru_open) { lru_open = cptr->lru; dec_open = cptr; } } else { // check frame proximity int64_t fd = frame - cptr->frame; if (framediff < 0 || (fd >= 0 && fd == framediff)) { if (cptr->lru < lru_open) { lru_open = cptr->lru; dec_open = cptr; if (fd > 0) framediff = fd; } } else if (fd >= 0 && fd < framediff) { lru_open = cptr->lru; dec_open = cptr; framediff = fd; } } } /* end loop over all decoder objects */ debugmsg(DEBUG_DCTL, "DCTL: found %d avail. from %d total decoder(s) for file-id:%d. [%s]\n", avail, found, id, dec_open?"open":dec_closed?"closed":"N/A"); if (dec_open) { return(dec_open); } if (dec_closed) { return(dec_closed); } return(NULL); } static void hashref_delete_jvo(JVD *jvd, JVOBJECT *jvo) { JVOBJECT *jvx, *jvtmp; pthread_rwlock_wrlock(&jvd->lock_jdh); HASH_ITER(hhf, jvd->jvf, jvx, jvtmp) { if (jvx == jvo) { debugmsg(DEBUG_DCTL, "delete index hash -> i:%d f:%d\n", jvo->id, jvo->fmt); HASH_DELETE(hhi, jvd->jvi, jvo); HASH_DELETE(hhf, jvd->jvf, jvo); memset(&jvo->hhi, 0, sizeof(UT_hash_handle)); memset(&jvo->hhf, 0, sizeof(UT_hash_handle)); break; } } pthread_rwlock_unlock(&jvd->lock_jdh); } /* clear object information if f==4 force also to flush data (even if it's in USE -- may segfault) if f==3 force also to flush data (wait fo it to become unused) if f==2 all /unused objects/ are invalidated and freed. if f==1 all /unused objects/ are invalidated. if f==0 all /unused and open objects/ are closed. (f>=2 must not be used during /normal/ operation -- it is exclusive to multithread operation) @param id ; filter on id ; -1: all in cache. */ static int clearjvo(JVD *jvd, int f, int id, int age, pthread_mutex_t *l) { JVOBJECT *cptr = jvd->jvo; JVOBJECT *prev = jvd->jvo; int total = 0, busy = 0, cleared = 0, freed = 0, count = 0, skipped = 0; time_t now; if (f > 1) { pthread_mutex_lock(&jvd->lock_busy); #if 0 if (jvd->purge_in_progress) { pthread_mutex_unlock(&jvd->lock_busy); return; } #endif jvd->purge_in_progress++; while (jvd->busycnt > 0) { pthread_mutex_unlock(&jvd->lock_busy); mymsleep(5); pthread_mutex_lock(&jvd->lock_busy); } } pthread_mutex_lock(l); now = time(NULL); while (cptr) { JVOBJECT *mem = cptr; total++; if (id > 0 && cptr->id != id) { prev = cptr; cptr = cptr->next; skipped++; continue; } if (age > 0 && cptr->lru + age > now) { prev = cptr; cptr = cptr->next; skipped++; continue; } count++; pthread_mutex_lock(&cptr->lock); if (cptr->flags&(VOF_USED|VOF_PENDING|VOF_INFO)) { if (f < 3) { pthread_mutex_unlock(&cptr->lock); busy++; prev = cptr; cptr = cptr->next; continue; } if (f < 4) { dlog(DLOG_WARNING, "DTCL: waiting for decoder to be unlocked.\n"); do { pthread_mutex_unlock(&cptr->lock); mymsleep(5); pthread_mutex_lock(&cptr->lock); } while (cptr->flags&(VOF_USED|VOF_PENDING|VOF_INFO)); } else { /* we really should not do this */ dlog(DLOG_ERR, "DCTL: request to free an active decoder.\n"); cptr->flags &= ~(VOF_USED|VOF_PENDING|VOF_INFO); cptr->infolock_refcnt = 0; // XXX may trigger assert() in dctrl_release_infolock() } } if (cptr->flags&VOF_OPEN) { my_destroy(&cptr->decoder); cptr->decoder = NULL; cptr->flags &= ~VOF_OPEN; cptr->fmt = PIX_FMT_NONE; } hashref_delete_jvo(jvd, cptr); if (f > 0) { cptr->id = 0; cptr->lru = 0; cptr->hitcount_info = 0; cptr->hitcount_decoder = 0; cptr->frame = -1; cptr->flags &= ~VOF_VALID; } pthread_mutex_unlock(&cptr->lock); cptr = cptr->next; if (f > 1 && mem != jvd->jvo) { prev->next = cptr; pthread_mutex_destroy(&mem->lock); free(mem); freed++; } else { prev = mem; cleared++; } } pthread_mutex_unlock(l); if (f > 1) { jvd->purge_in_progress--; pthread_mutex_unlock(&jvd->lock_busy); } debugmsg(DEBUG_DCTL, "DCTL: GC processed %d (freed: %d, cleared: %d, busy: %d) skipped: %d, total: %d\n", count, freed, cleared, busy, skipped, total); return (cleared); } //get some unused allocated jvo or create one. static JVOBJECT *getjvo(JVD *jvd) { int cnt_total = 0; JVOBJECT *dec_closed = NULL; JVOBJECT *dec_open = NULL; time_t lru = time(NULL) + 1; JVOBJECT *cptr = jvd->jvo; #if 1 // garbage collect, close decoders not used since > 10 mins clearjvo(jvd, 1, -1, 600, &jvd->lock_jvo); #endif while (cptr) { if ((cptr->flags&(VOF_USED|VOF_OPEN|VOF_VALID|VOF_PENDING|VOF_INFO)) == 0) { return (cptr); } if (!(cptr->flags&(VOF_USED|VOF_OPEN|VOF_PENDING|VOF_INFO)) && (cptr->lru < lru)) { lru = cptr->lru; dec_closed = cptr; } else if (!(cptr->flags&(VOF_USED|VOF_PENDING|VOF_INFO)) && (cptr->lru < lru)) { lru = cptr->lru; dec_open = cptr; } cptr = cptr->next; cnt_total++; } if (dec_closed) { cptr = dec_closed; } else if (dec_open) { cptr = dec_open; } debugmsg(DEBUG_DCTL, "DCTL: %d/%d decoders; avail: closed: %s open: %s\n", cnt_total, jvd->max_objects, dec_closed?"Y":"N", dec_open?"Y":"N"); // TODO prefer to allocate a new decoder object IFF // decoder for same file exists but with different format. if (cnt_total < 4 && cnt_total < jvd->max_objects) return(newjvo(jvd->jvo, &jvd->lock_jvo)); if (cptr && !pthread_mutex_trylock(&cptr->lock)) { if (!(cptr->flags&(VOF_USED|VOF_PENDING|VOF_INFO))) { if (cptr->flags&(VOF_OPEN)) { my_destroy(&cptr->decoder); // close it. cptr->decoder = NULL; // not really need.. cptr->fmt = PIX_FMT_NONE; } hashref_delete_jvo(jvd, cptr); cptr->id = 0; cptr->lru = 0; cptr->flags = 0; cptr->frame = -1; cptr->hitcount_info = 0; cptr->hitcount_decoder = 0; assert(cptr->infolock_refcnt == 0); pthread_mutex_unlock(&cptr->lock); return (cptr); } pthread_mutex_unlock(&cptr->lock); } if (cnt_total < jvd->max_objects) return(newjvo(jvd->jvo, &jvd->lock_jvo)); return (NULL); } /////////////////////////////////////////////////////////////////////////////// // Video decoder management // static void clearvid(JVD* jvd, void *vc) { VidMap *vm, *tmp; pthread_rwlock_wrlock(&jvd->lock_vml); HASH_ITER(hh, jvd->vml, vm, tmp) { HASH_DEL(jvd->vml, vm); HASH_DELETE(hr, jvd->vmr, vm); if (vc) vcache_clear(vc, vm->id); clearjvo(jvd, 3, vm->id, -1, &jvd->lock_jvo); free(vm->fn); free(vm); } pthread_rwlock_unlock(&jvd->lock_vml); } static unsigned short get_id(JVD *jvd, const char *fn, void *vc) { VidMap *vm = NULL; int rv; pthread_rwlock_rdlock(&jvd->lock_vml); HASH_FIND_STR(jvd->vml, fn, vm); if (vm) { rv = vm->id; vm->lru = time(NULL); pthread_rwlock_unlock(&jvd->lock_vml); return rv; } pthread_rwlock_unlock(&jvd->lock_vml); /* create a new entry */ pthread_rwlock_wrlock(&jvd->lock_vml); /* check that it has not been added meanwhile */ HASH_FIND_STR(jvd->vml, fn, vm); if (vm) { rv = vm->id; vm->lru = time(NULL); pthread_rwlock_unlock(&jvd->lock_vml); return rv; } if(HASH_COUNT(jvd->vml) >= jvd->cache_size) { /* delete lru */ VidMap *tmp, *vlru = NULL; time_t lru = time(NULL) + 1; HASH_ITER(hh, jvd->vml, vm, tmp) { if (vm->lru < lru) { lru = vm->lru; vlru = vm; } } if (vlru) { HASH_DEL(jvd->vml, vlru); HASH_DELETE(hr, jvd->vmr, vlru); free(vlru->fn); vm = vlru; memset(vm, 0, sizeof(VidMap)); } } if (!vm) vm = calloc(1, sizeof(VidMap)); vm->id = jvd->monotonic++; if (jvd->monotonic == 0) { dlog(LOG_INFO, "monotonic ID counter roll-over\n"); jvd->monotonic = 1; } vm->fn = strdup(fn); vm->lru = time(NULL); rv = vm->id; HASH_ADD_KEYPTR(hh, jvd->vml, vm->fn, strlen(vm->fn), vm); HASH_ADD(hr, jvd->vmr, id, sizeof(unsigned short), vm); /* invalidate frame-cache for this ID*/ if (vc) vcache_clear(vc, vm->id); pthread_rwlock_unlock(&jvd->lock_vml); return rv; } static char *get_fn(JVD *jvd, unsigned short id) { VidMap *vm; pthread_rwlock_rdlock(&jvd->lock_vml); HASH_FIND(hr, jvd->vmr, &id, sizeof(unsigned short), vm); pthread_rwlock_unlock(&jvd->lock_vml); if (vm) return vm->fn; // XXX strdup before unlock return NULL; } static void release_id(JVD *jvd, unsigned short id) { VidMap *vm; pthread_rwlock_wrlock(&jvd->lock_vml); HASH_FIND(hr, jvd->vmr, &id, sizeof(unsigned short), vm); if (vm) { HASH_DEL(jvd->vml, vm); HASH_DELETE(hr, jvd->vmr, vm); pthread_rwlock_unlock(&jvd->lock_vml); free(vm->fn); free(vm); return; } pthread_rwlock_unlock(&jvd->lock_vml); dlog(LOG_ERR, "failed to delete ID from hash table\n"); } static JVOBJECT *new_video_object(JVD *jvd, unsigned short id, int fmt) { JVOBJECT *jvo, *jvx; debugmsg(DEBUG_DCTL, "new_video_object()\n"); do { jvo = getjvo(jvd); if (!jvo) { mymsleep(5); sched_yield(); return NULL; } if (pthread_mutex_trylock(&jvo->lock)) continue; if ((jvo->flags&(VOF_USED|VOF_OPEN|VOF_VALID|VOF_PENDING|VOF_INFO))) { pthread_mutex_unlock(&jvo->lock); continue; } } while (!jvo); jvo->id = id; jvo->fmt = fmt == PIX_FMT_NONE ? DEFAULT_PIX_FMT : fmt; jvo->frame = -1; jvo->flags |= VOF_VALID; pthread_rwlock_wrlock(&jvd->lock_jdh); HASH_FIND(hhi, jvd->jvi, &id, sizeof(unsigned short), jvx); if (!jvx) { debugmsg(DEBUG_DCTL, "linking index hash -> i:%d f:%d\n", id, fmt); HASH_ADD(hhi, jvd->jvi, id, sizeof(unsigned short), jvo); HASH_ADD(hhf, jvd->jvf, id, CLKEYLEN, jvo); } pthread_rwlock_unlock(&jvd->lock_jdh); pthread_mutex_unlock(&jvo->lock); return(jvo); } #if 0 // unused /** * use-case: re-open a file. - flush decoders (and indirectly cache -> new ID) */ static int release_video_object(JVD *jvd, char *fn) { int id; if ((id = get_id(jvd, fn)) > 0) return (clearjvo(jvd, 1, id)); // '0': would close only close and re-use the same ID (cache) return(-1); } #endif /////////////////////////////////////////////////////////////////////////////// // part 2a - video decoder control #define BUSYDEC(jvd) \ pthread_mutex_lock(&jvd->lock_busy); \ jvd->busycnt--; \ pthread_mutex_unlock(&jvd->lock_busy); \ #define BUSYADD(jvd) \ while (jvd->purge_in_progress) mymsleep(5); \ pthread_mutex_lock(&jvd->lock_busy); \ jvd->busycnt++; \ pthread_mutex_unlock(&jvd->lock_busy); \ // lookup or create new decoder for file ID static void * dctrl_get_decoder(void *p, unsigned short id, int fmt, int64_t frame, int *err) { JVD *jvd = (JVD*)p; JVOBJECT *jvo = NULL; *err = 0; BUSYADD(jvd) /* create hash for info lookups, reference first decoder for each id * use it IFF frame == -1 (ie. non-blocking info lookups) */ if (frame < 0) { pthread_rwlock_rdlock(&jvd->lock_jdh); if (fmt == PIX_FMT_NONE) { HASH_FIND(hhi, jvd->jvi, &id, sizeof(unsigned short), jvo); } else { const JVOBJECT jvt = {id, fmt, 0}; HASH_FIND(hhf, jvd->jvf, &jvt, CLKEYLEN, jvo); } pthread_rwlock_unlock(&jvd->lock_jdh); if (jvo) { debugmsg(DEBUG_DCTL, "ID found in hashtable\n"); } } while (1) { debugmsg(DEBUG_DCTL, "DCTL: get_decoder fileid=%i\n", id); if (!jvo) { int timeout = 40; // new_video_object() delays 5ms at a time. do { jvo = testjvd(jvd->jvo, id, fmt, frame); if (!jvo) jvo = new_video_object(jvd, id, fmt); } while (--timeout > 0 && !jvo); } if (!jvo) { dlog(DLOG_ERR, "DCTL: no decoder object available.\n"); BUSYDEC(jvd) *err = 503; // try again return(NULL); } pthread_mutex_lock(&jvo->lock); if ((jvo->flags&(VOF_PENDING))) { pthread_mutex_unlock(&jvo->lock); continue; } jvo->flags |= VOF_PENDING; pthread_mutex_unlock(&jvo->lock); if ((jvo->flags&(VOF_USED|VOF_OPEN|VOF_VALID|VOF_INFO)) == (VOF_VALID)) { if (fmt == PIX_FMT_NONE) fmt = DEFAULT_PIX_FMT; if (!my_open_movie(&jvo->decoder, get_fn(jvd, jvo->id), fmt)) { pthread_mutex_lock(&jvo->lock); jvo->fmt = fmt; jvo->flags |= VOF_OPEN; jvo->flags &= ~VOF_PENDING; pthread_mutex_unlock(&jvo->lock); } else { pthread_mutex_lock(&jvo->lock); jvo->flags &= ~VOF_PENDING; assert(!jvo->decoder); pthread_mutex_unlock(&jvo->lock); release_id(jvd, jvo->id); // mark ID as invalid dlog(DLOG_ERR, "DCTL: opening of movie file failed.\n"); BUSYDEC(jvd) *err = 500; // failed to open format/codec return(NULL); // XXX -> 500/inval } } pthread_mutex_lock(&jvo->lock); jvo->flags &= ~VOF_PENDING; if (frame < 0) { /* we only need info -> decoder may be in use */ if ((jvo->flags&(VOF_OPEN|VOF_VALID)) == (VOF_VALID|VOF_OPEN)) { jvo->infolock_refcnt++; jvo->flags |= VOF_INFO; pthread_mutex_unlock(&jvo->lock); BUSYDEC(jvd) return(jvo); } } else { if ((jvo->flags&(VOF_USED|VOF_OPEN|VOF_VALID)) == (VOF_VALID|VOF_OPEN)) { jvo->flags |= VOF_USED; pthread_mutex_unlock(&jvo->lock); BUSYDEC(jvd) return(jvo); } } pthread_mutex_unlock(&jvo->lock); debugmsg(DEBUG_DCTL, "DCTL: decoder object was busy.\n"); } } static void dctrl_release_decoder(void *dec) { JVOBJECT *jvo = (JVOBJECT *) dec; pthread_mutex_lock(&jvo->lock); jvo->flags &= ~VOF_USED; pthread_mutex_unlock(&jvo->lock); } static void dctrl_release_infolock(void *dec) { JVOBJECT *jvo = (JVOBJECT *) dec; pthread_mutex_lock(&jvo->lock); if (--jvo->infolock_refcnt < 1) { assert(jvo->infolock_refcnt >= 0); jvo->flags &= ~(VOF_INFO); } pthread_mutex_unlock(&jvo->lock); } static inline int xdctrl_decode(void *dec, int64_t frame, uint8_t *b, int w, int h) { JVOBJECT *jvo = (JVOBJECT *) dec; jvo->lru = time(NULL); jvo->hitcount_decoder++; int rv = my_decode(jvo->decoder, frame, b, w, h); jvo->frame = frame; return rv; } /////////////////////////////////////////////////////////////////////////////// // part 2b - video object/decoder API - public API void dctrl_create(void **p, int max_decoders, int cache_size) { JVD *jvd; (*((JVD**)p)) = (JVD*) calloc(1, sizeof(JVD)); jvd = (*((JVD**)p)); jvd->monotonic = 1; jvd->max_objects = max_decoders; jvd->cache_size = cache_size; pthread_mutex_init(&jvd->lock_busy, NULL); pthread_mutex_init(&jvd->lock_jvo, NULL); pthread_rwlock_init(&jvd->lock_vml, NULL); pthread_rwlock_init(&jvd->lock_jdh, NULL); jvd->vml = NULL; jvd->vmr = NULL; jvd->jvi = NULL; jvd->jvf = NULL; jvd->jvo = newjvo(NULL, &jvd->lock_jvo); HASH_ADD(hhi, jvd->jvi, id, sizeof(unsigned short), jvd->jvo); HASH_ADD(hhf, jvd->jvf, id, CLKEYLEN, jvd->jvo); } void dctrl_destroy(void **p) { JVD *jvd = (*((JVD**)p)); clearjvo(jvd, 3, -1, -1, &jvd->lock_jvo); clearvid(jvd, NULL); pthread_mutex_destroy(&jvd->lock_busy); pthread_mutex_destroy(&jvd->lock_jvo); pthread_rwlock_destroy(&jvd->lock_vml); pthread_rwlock_destroy(&jvd->lock_jdh); free(jvd->jvo); free(*((JVD**)p)); *p = NULL; } unsigned short dctrl_get_id(void *vc, void *p, const char *fn) { JVD *jvd = (JVD*)p; return get_id(jvd, fn, vc); } int dctrl_decode(void *p, unsigned short id, int64_t frame, uint8_t *b, int w, int h, int fmt) { int err = 0; void *dec = dctrl_get_decoder(p, id, fmt, frame, &err); if (!dec) { dlog(DLOG_WARNING, "DCTL: no decoder available.\n"); return err; } int rv = xdctrl_decode(dec, frame, b, w, h); dctrl_release_decoder(dec); return (rv); } int dctrl_get_info(void *p, unsigned short id, VInfo *i) { int err = 0; JVOBJECT *jvo = (JVOBJECT*) dctrl_get_decoder(p, id, PIX_FMT_NONE, -1, &err); if (!jvo) return err; my_get_info(jvo->decoder, i); jvo->hitcount_info++; dctrl_release_infolock(jvo); return(0); } int dctrl_get_info_scale(void *p, unsigned short id, VInfo *i, int w, int h, int fmt) { int err = 0; JVOBJECT *jvo = (JVOBJECT*) dctrl_get_decoder(p, id, fmt, -1, &err); if (!jvo) return err; my_get_info_canonical(jvo->decoder, i, w, h); jvo->hitcount_info++; dctrl_release_infolock(jvo); return(0); } void dctrl_cache_clear(void *vc, void *p, int f, int id) { JVD *jvd = (JVD*)p; clearjvo(jvd, f, id, -1, &jvd->lock_jvo); if (vc) clearvid(jvd, vc); } /////////////////////////////////////////////////////////////////////////////// // 2c - video object/decoder stats - public API static char *flags2txt(int f) { char *rv = NULL; size_t off = 0; if (f == 0) { rv = (char*) realloc(rv, (off+2) * sizeof(char)); off += sprintf(rv+off, "-"); return rv; } if (f&VOF_USED) { rv = (char*) realloc(rv, (off+6) * sizeof(char)); off += sprintf(rv+off, "used "); } if (f&VOF_OPEN) { rv = (char*) realloc(rv, (off+6) * sizeof(char)); off += sprintf(rv+off, "open "); } if (f&VOF_VALID) { rv = (char*) realloc(rv, (off+7) * sizeof(char)); off += sprintf(rv+off, "hasID "); } if (f&VOF_PENDING) { rv = (char*) realloc(rv, (off+9) * sizeof(char)); off += sprintf(rv+off, "pending "); } if (f&VOF_INFO) { rv = (char*) realloc(rv, (off+9) * sizeof(char)); off += sprintf(rv+off, "info "); } return rv; } void dctrl_info_html (void *p, char **m, size_t *o, size_t *s, int tbl) { JVOBJECT *cptr = ((JVD*)p)->jvo; int i = 1; VidMap *vm, *tmp; if (tbl&1) { rprintf("

File/ID Mapping:

\n"); rprintf("

max available: %d

\n", ((JVD*)p)->cache_size); rprintf("\n"); } else { if (tbl&2) { rprintf("
\n"); } rprintf("\n"); rprintf("\n", ((JVD*)p)->cache_size); } rprintf("\n"); rprintf("\n"); pthread_rwlock_rdlock(&((JVD*)p)->lock_vml); HASH_ITER(hh, ((JVD*)p)->vml, vm, tmp) { rprintf("\n", i, vm->id, vm->fn?vm->fn:"(null)", (long long)vm->lru); i++; } pthread_rwlock_unlock(&((JVD*)p)->lock_vml); if (tbl&4) { rprintf("

File Mapping:

max available: %d
#file-idFilenameLRU
%d.%i%s%"PRIlld"
\n"); } else { rprintf("\n"); } i = 1; if(tbl&4) { rprintf("

Decoder Objects:

\n"); rprintf("

max available: %d, busy: %d%s

\n", ((JVD*)p)->max_objects, ((JVD*)p)->busycnt, ((JVD*)p)->purge_in_progress?" (purge queued)":""); rprintf("\n"); } else { rprintf("\n"); rprintf("\n", ((JVD*)p)->max_objects, ((JVD*)p)->busycnt, ((JVD*)p)->purge_in_progress?" (purge queued)":""); } rprintf("\n"); rprintf("\n"); pthread_rwlock_rdlock(&((JVD*)p)->lock_jdh); while (cptr) { char *tmp, *fn; if (cptr->id == 0) { cptr = cptr->next; continue; // don't list unused root-node. } tmp = flags2txt(cptr->flags); fn = (cptr->flags&VOF_VALID) ? get_fn((JVD*)p, cptr->id) : NULL; rprintf( "\n", i, cptr->id, tmp, fn?fn:"-", /* (cptr->decoder?LIBAVCODEC_IDENT:"null"), */ cptr->hitcount_info, cptr->hitcount_decoder, ff_fmt_to_text(cptr->fmt), cptr->frame, (long long)cptr->lru); free(tmp); cptr = cptr->next; i++; } pthread_rwlock_unlock(&((JVD*)p)->lock_jdh); if(tbl&8) { rprintf("

Decoder Objects:

max available: %d, busy: %d%s
#file-idFlagsFilenameHitcountPixFmtFrame#LRU
%d.%i%s%si:%d,d:%d%s%"PRId64"%"PRIlld"
\n"); } else { rprintf("\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/libharvid/decoder_ctrl.h000066400000000000000000000054451215541677300175670ustar00rootroot00000000000000/** @file decoder_ctrl.h @brief video object abstraction This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _DECODER_CTRL_H #define _DECODER_CTRL_H #include "vinfo.h" /** create and allocate a decoder control object * @param p pointer to allocated object */ void dctrl_create(void **p, int max_decoders, int cache_size); /** close and destroy a decoder control object * @param p object pointer to free */ void dctrl_destroy(void **p); /** * request a video-object id for the given file * * @param vc pointer to a video-cache object * @param p pointer to a decoder-control object * @param fn file name to look up * @return file-id use with: dctrl_get_info() or dctrl_decode() */ unsigned short dctrl_get_id(void *vc, void *p, const char *fn); /** * HTML format debug info and store at most \a n bytes of the message to \a m * @param p pointer to a decoder-control object * @param m pointer to where result message is stored * @param o pointer current offset in m * @param s pointer max length of message. */ void dctrl_info_html(void *p, char **m, size_t *o, size_t *s, int tbl); /** * request VInfo video-info for given decoder-object * @param p pointer to a decoder-control object * @param id id of the decoder * @param i returned data * @return 0 on success, -1 otherwise */ int dctrl_get_info(void *p, unsigned short id, VInfo *i); /** * set new scaling factors and return updated VInfo * @param p pointer to a decoder-control object * @param id id of the decoder * @param w width or -1 to use aspect-ratio and height * @param h height or -1 to use aspect-ratio and width (if both w and h are -1 - no scaling is performed) * @param i optional - if not NULL \ref dctrl_get_info is called to fill in the data * @return 0 on success, -1 otherwise */ int dctrl_get_info_scale(void *p, unsigned short id, VInfo *i, int w, int h, int fmt); /** * used by the frame-cache to decode a frame */ int dctrl_decode(void *p, unsigned short vid, int64_t frame, uint8_t *b, int w, int h, int fmt); /** */ void dctrl_cache_clear(void *vc, void *p, int f, int id); #endif harvid-0.7.3/libharvid/dlog.h000066400000000000000000000052351215541677300160600ustar00rootroot00000000000000/** @file dlog.h @brief output and logfile abstraction This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _harvid_dlog_H #define _harvid_dlog_H /* some common win/posix issues */ #ifndef WIN32 #define PRIlld "lld" #define mymsleep(ms) usleep((ms) * 1000) #else #include #define PRIlld "I64d" #define mymsleep(ms) Sleep(ms) #endif /* syslog */ #ifndef WIN32 #include #else #define LOG_EMERG 1 #define LOG_CRIT 2 #define LOG_ERR 3 #define LOG_WARNING 4 #define LOG_INFO 6 #endif /* log Levels */ #define DLOG_EMERG LOG_EMERG ///< quiet (system is unusable) #define DLOG_CRIT LOG_CRIT ///< critical conditions -- usually implies exit() or process termination #define DLOG_ERR LOG_ERR ///< error conditions -- recoverable errors #define DLOG_WARNING LOG_WARNING ///< warning conditions #define DLOG_INFO LOG_INFO ///< informational enum {DEBUG_SRV=1, DEBUG_HTTP=2, DEBUG_CON=4, DEBUG_DCTL=8, DEBUG_ICS=16}; #ifndef DAEMON_LOG_SELF extern int debug_level; ///< global debug_level used by @ref dlog() extern int debug_section; ///< global debug_level used by @ref dlog() #endif /** * printf replacement * * @param level log level 0-7 see syslog.h * @param format same as printf(...) */ void dlog(int level, const char *format, ...); #ifdef NDEBUG #define debugmsg(section, ...) {} #else #define debugmsg(section, ...) {if (debug_section§ion) printf(__VA_ARGS__);} #endif #define raprintf(p, off, siz, ...) \ { \ if (siz - off < 256) { siz *= 2; p = realloc(p, siz * sizeof(char)); } \ off += snprintf(p + off, siz - off, __VA_ARGS__); \ } #define rpprintf(p, off, siz, ...) \ { \ while ((*siz) - (*off) <= snprintf((*p) + (*off), 0, __VA_ARGS__)) \ { (*siz) *= 2; (*p) = realloc(*p, (*siz) * sizeof(char)); } \ (*off) += snprintf((*p) + (*off), (*siz) - (*off), __VA_ARGS__); \ assert((*siz) >= (*off)); \ } #define rprintf(...) rpprintf(m,o,s, __VA_ARGS__) #endif harvid-0.7.3/libharvid/dlog_null.c000066400000000000000000000003741215541677300171040ustar00rootroot00000000000000/* ffmpeg decoder honors these externs */ int want_verbose = 0; int want_quiet = 1; /* dlog implementation required by libharvid */ #ifndef NDEBUG int debug_section = 0; #endif int debug_level = 0; void dlog(int level, const char *format, ...) {} harvid-0.7.3/libharvid/ffcompat.h000066400000000000000000000061541215541677300167330ustar00rootroot00000000000000/* ffmpeg compatibility wrappers * * Copyright 2012 Robin Gareus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef FFCOMPAT_H #define FFCOMPAT_H #include #include #include #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(50, 0, 0) #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO #endif #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 2, 0) static inline int avformat_open_input(AVFormatContext **ps, const char *filename, void *fmt, void **options) { return av_open_input_file(ps, filename, NULL, 0, NULL); } #endif #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 5, 0) static inline AVCodecContext * avcodec_alloc_context3(AVCodec *codec __attribute__((unused))) { return avcodec_alloc_context(); } static inline AVStream * avformat_new_stream(AVFormatContext *s, AVCodec *c) { return av_new_stream(s,0); } static inline int avcodec_get_context_defaults3(AVCodecContext *s, AVCodec *codec) { avcodec_get_context_defaults(s); return 0; } #endif #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 123, 0) static inline int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, void **options __attribute__((unused))) { return avcodec_open(avctx, codec); } #endif #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 111, 0) static inline int avformat_find_stream_info(AVFormatContext *ic, void **options) { return av_find_stream_info(ic); } #endif #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 5, 0) static inline void avformat_close_input(AVFormatContext **s) { av_close_input_file(*s); } #endif #endif /* FFCOMPAT_H */ harvid-0.7.3/libharvid/ffdecoder.c000066400000000000000000000645331215541677300170550ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2007-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include /* uint8_t */ #include /* calloc et al.*/ #include /* memset */ #include #include #include #include #include #include #include "vinfo.h" #include "ffdecoder.h" #include "ffcompat.h" #include /* xj5 seek modes */ enum { SEEK_ANY, ///< directly seek to givenvideo frame SEEK_KEY, ///< seek to next keyframe after given frame. SEEK_CONTINUOUS, ///< seek to keframe before this frame and advance to current frame. SEEK_LIVESTREAM, ///< decode until next keyframe in a live-stream and set initial PTS offset; later decode cont. until PTS match }; /* ffmpeg source */ typedef struct { /* file specific decoder settings */ int want_ignstart; //< set before calling ff_open_movie() int want_genpts; int seekflags; /* Video File Info */ int movie_width; ///< original file geometry int movie_height; ///< original file geometry int out_width; ///< aspect scaled geometry int out_height; ///< aspect scaled geometry double duration; double framerate; double file_frame_offset; long frames; char *current_file; /* helper variables */ double tpf; int64_t avprev; int64_t stream_pts_offset; /* */ uint8_t *internal_buffer; //< if !NULL this buffer is free()d on destroy uint8_t *buffer; int buf_width; ///< current geometry for allocated buffer int buf_height; ///< current geometry for allocated buffer int videoStream; int render_fmt; //< pFrame/buffer output format (RGB24) /* ffmpeg internals*/ AVPacket packet; AVFormatContext *pFormatCtx; AVCodecContext *pCodecCtx; AVFrame *pFrame; AVFrame *pFrameFMT; struct SwsContext *pSWSCtx; } ffst; /* Option flags and global variables */ extern int want_quiet; extern int want_verbose; static pthread_mutex_t avcodec_lock; static const AVRational c1_Q = { 1, 1 }; //#define SCALE_UP ///< positive pixel-aspect scales up X axis - else positive pixel-aspect scales down Y-Axis. //-------------------------------------------- // Manage video file //-------------------------------------------- int ff_picture_bytesize(int render_fmt, int w, int h) { const int bs = avpicture_get_size(render_fmt, w, h); if (bs < 0) return 0; return bs; } static int ff_getbuffersize(void *ptr, size_t *s) { ffst *ff = (ffst*)ptr; const int ps = ff_picture_bytesize(ff->render_fmt, ff->out_width, ff->out_height); if (s) *s = ps; return ps; } static void render_empty_frame(ffst *ff, uint8_t* buf, int w, int h, int xoff, int ys) { switch (ff->render_fmt) { case PIX_FMT_UYVY422: { int i; for (i = 0; i < w*h*2; i += 2) { buf[i] = 0x00; buf[i+1] = 0x80; } } break; case PIX_FMT_YUYV422: { int i; for (i = 0; i < w*h*2; i += 2) { buf[i] = 0x80; buf[i+1] = 0x00; } } break; case PIX_FMT_YUV420P: { size_t Ylen = w * h; memset(buf, 0, Ylen); memset(buf+Ylen, 0x80, Ylen/2); } break; case PIX_FMT_YUV440P: { size_t Ylen = w * h; memset(buf, 0, Ylen); memset(buf+Ylen, 0x80, Ylen); } break; case PIX_FMT_BGR24: case PIX_FMT_RGB24: case PIX_FMT_RGBA: case PIX_FMT_BGRA: case PIX_FMT_ARGB: memset(buf, 0, ff_getbuffersize(ff, NULL)); break; default: if (!want_quiet) fprintf(stderr, "render_empty_frame() with unknown render format\n"); break; } #if 1 // draw cross int x,y; switch (ff->render_fmt) { case PIX_FMT_YUV420P: case PIX_FMT_YUV440P: for (x = 0, y = 0; x < w-1; x++, y = h * x / w) { int off = (x + w * y); buf[off]=127; buf[off+1]=127; off = (x + w * (h - y - 1)); buf[off]=127; buf[off+1]=127; } break; case PIX_FMT_YUYV422: case PIX_FMT_UYVY422: for (x = 0, y = 0; x < w-1; x++, y = h * x / w) { int off = (x + w * y) * 2; buf[off] = 127; buf[off+1] = 127; off = (x + w * (h - y - 1)) * 2; buf[off] = 127; buf[off+1] = 127; } break; case PIX_FMT_RGB24: case PIX_FMT_BGR24: for (x = 0, y = 0; x < w-1; x++, y = h * x / w) { int off = 3 * (x + w * y); buf[off]=255; buf[off+1]=255; buf[off+2]=255; off = 3 * (x + w * (h - y - 1)); buf[off]=255; buf[off+1]=255; buf[off+2]=255; } break; case PIX_FMT_RGBA: case PIX_FMT_BGRA: case PIX_FMT_ARGB: { const int O = (ff->render_fmt == PIX_FMT_ARGB) ? 1 : 0; for (x = 0, y = 0; x < w-1; x++, y = h * x / w) { int off = 4 * (x + w * y) + O; buf[off]=255; buf[off+1]=255; buf[off+2]=255; off = 4 * (x + w * (h - y - 1)) + O; buf[off]=255; buf[off+1]=255; buf[off+2]=255; } } break; default: break; } #endif } static double ff_get_aspectratio(void *ptr) { ffst *ff = (ffst*)ptr; double aspect_ratio; if ( ff->pCodecCtx->sample_aspect_ratio.num == 0 || ff->pCodecCtx->sample_aspect_ratio.den == 0) aspect_ratio = 0; else aspect_ratio = av_q2d(ff->pCodecCtx->sample_aspect_ratio) * (double)ff->pCodecCtx->width / (double)ff->pCodecCtx->height; if (aspect_ratio <= 0.0) aspect_ratio = (double)ff->pCodecCtx->width / (double)ff->pCodecCtx->height; return (aspect_ratio); } static void ff_caononicalize_size2(void *ptr, int *w, int *h) { ffst *ff = (ffst*)ptr; double aspect_ratio = ff_get_aspectratio(ptr); if (!w || !h) return; if ((*h) < 16 && (*w) > 15) (*h) = (int) floorf((float)(*w)/aspect_ratio); else if ((*h) > 15 && (*w) < 16) (*w) = (int) floorf((float)(*h)*aspect_ratio); if ((*w) < 16 || (*h) < 16) { #ifdef SCALE_UP (*w) = (int) floor((double)ff->pCodecCtx->height * aspect_ratio); (*h) = ff->pCodecCtx->height; #else (*w) = ff->pCodecCtx->width ; (*h) = (int) floor((double)ff->pCodecCtx->width / aspect_ratio); #endif } } static void ff_caononical_size(void *ptr) { ffst *ff = (ffst*)ptr; ff_caononicalize_size2(ptr, &ff->out_width, &ff->out_height); } static void ff_init_moviebuffer(void *ptr) { size_t numBytes = 0; ffst *ff = (ffst*)ptr; ff_caononical_size(ptr); if (ff->buf_width == ff->out_width && ff->buf_height == ff->out_height) { return; } else if (want_verbose) { fprintf(stdout, "ff_init_moviebuffer %dx%d vs %dx%d\n", ff->buf_width, ff->buf_height, ff->out_width, ff->out_height); } if (ff->internal_buffer) free(ff->internal_buffer); ff_getbuffersize(ff, &numBytes); ff->internal_buffer = (uint8_t *) calloc(numBytes, sizeof(uint8_t)); ff->buffer = ff->internal_buffer; ff->buf_width = ff->out_width; ff->buf_height = ff->out_height; if (!ff->buffer) { #ifdef WIN32 fprintf(stderr, "out of memory (trying to allocate %lu bytes)\n", numBytes); // XXX #else fprintf(stderr, "out of memory (trying to allocate %zu bytes)\n", numBytes); // XXX #endif exit(1); } assert(ff->pFrameFMT); avpicture_fill((AVPicture *)ff->pFrameFMT, ff->buffer, ff->render_fmt, ff->out_width, ff->out_height); } void ff_initialize (void) { if (want_verbose) fprintf(stdout, "FFMPEG: registering codecs.\n"); av_register_all(); avcodec_register_all(); pthread_mutex_init(&avcodec_lock, NULL); if(want_quiet) av_log_set_level(AV_LOG_QUIET); else if (want_verbose) av_log_set_level(AV_LOG_VERBOSE); else av_log_set_level(AV_LOG_ERROR); } void ff_cleanup (void) { pthread_mutex_destroy(&avcodec_lock); } int ff_close_movie(void *ptr) { ffst *ff = (ffst*)ptr; if(ff->current_file) free(ff->current_file); ff->current_file = NULL; if (!ff->pFrameFMT) return(-1); if (ff->out_width < 0 || ff->out_height < 0) { ff->out_width = ff->movie_width; ff->out_height = ff->movie_height; } ff_set_bufferptr(ff, ff->internal_buffer); // restore allocated movie-buffer.. if (ff->internal_buffer) free(ff->internal_buffer); // done in pFrameFMT? if (ff->pFrameFMT) av_free(ff->pFrameFMT); if (ff->pFrame) av_free(ff->pFrame); ff->buffer = NULL;ff->pFrameFMT = ff->pFrame = NULL; pthread_mutex_lock(&avcodec_lock); avcodec_close(ff->pCodecCtx); avformat_close_input(&ff->pFormatCtx); pthread_mutex_unlock(&avcodec_lock); if (ff->pSWSCtx) sws_freeContext(ff->pSWSCtx); return (0); } static void ff_get_framerate(void *ptr, TimecodeRate *fr) { ffst *ff = (ffst*)ptr; AVStream *av_stream; if (!ff->current_file || !ff->pFormatCtx) { return; } av_stream = ff->pFormatCtx->streams[ff->videoStream]; if(av_stream->r_frame_rate.den && av_stream->r_frame_rate.num) { fr->num = av_stream->r_frame_rate.num; fr->den = av_stream->r_frame_rate.den; // if ((ff->framerate < 4 || ff->framerate > 100) && (av_stream->time_base.num && av_stream->time_base.den)) { // fr->num = av_stream->time_base.den // fr->den = av_stream->time_base.num; // } } else { fr->num = av_stream->time_base.den; fr->den = av_stream->time_base.num; } fr->drop = 0; if (floor(ff->framerate * 100.0) == 2997) fr->drop = 1; } static void ff_set_framerate(ffst *ff) { AVStream *av_stream; av_stream = ff->pFormatCtx->streams[ff->videoStream]; if(av_stream->r_frame_rate.den && av_stream->r_frame_rate.num) { ff->framerate = av_q2d(av_stream->r_frame_rate); if ((ff->framerate < 4 || ff->framerate > 100) && (av_stream->time_base.num && av_stream->time_base.den)) ff->framerate = 1.0/av_q2d(av_stream->time_base); } } int ff_open_movie(void *ptr, char *file_name, int render_fmt) { int i; AVCodec *pCodec; ffst *ff = (ffst*) ptr; if (ff->pFrameFMT) { if (ff->current_file && !strcmp(file_name, ff->current_file)) return(0); /* close currently open movie */ if (!want_quiet) fprintf(stderr, "replacing current video file buffer\n"); ff_close_movie(ff); } // initialize values ff->pFormatCtx = NULL; ff->pFrameFMT = NULL; ff->movie_width = 320; ff->movie_height = 180; ff->buf_width = ff->buf_height = 0; ff->movie_height = 180; ff->framerate = ff->duration = ff->frames = 1; ff->file_frame_offset = 0.0; ff->videoStream = -1; ff->tpf = 1.0; ff->avprev = 0; ff->stream_pts_offset = AV_NOPTS_VALUE; ff->render_fmt = render_fmt; /* Open video file */ if(avformat_open_input(&ff->pFormatCtx, file_name, NULL, NULL) <0) { if (!want_quiet) fprintf(stderr, "Cannot open video file %s\n", file_name); return (-1); } #if 1 /// XXX http is not neccesarily a live-stream! // TODO: ff_seeflags(sf) API to set/get value if (!strncmp(file_name, "http://", 7)) { ff->seekflags = SEEK_LIVESTREAM; } else { ff->seekflags = SEEK_CONTINUOUS; } // TODO: live-stream: remember first pts as offset! -> don't use multiple-decoders for the same stream ?! #endif pthread_mutex_lock(&avcodec_lock); /* Retrieve stream information */ if(avformat_find_stream_info(ff->pFormatCtx, NULL) < 0) { if (!want_quiet) fprintf(stderr, "Cannot find stream information in file %s\n", file_name); avformat_close_input(&ff->pFormatCtx); pthread_mutex_unlock(&avcodec_lock); return (-1); } pthread_mutex_unlock(&avcodec_lock); if (want_verbose) av_dump_format(ff->pFormatCtx, 0, file_name, 0); /* Find the first video stream */ for(i = 0; i < ff->pFormatCtx->nb_streams; i++) #if LIBAVFORMAT_BUILD > 0x350000 if(ff->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) #elif LIBAVFORMAT_BUILD > 4629 if(ff->pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) #else if(ff->pFormatCtx->streams[i]->codec.codec_type == CODEC_TYPE_VIDEO) #endif { ff->videoStream = i; break; } if(ff->videoStream == -1) { if (!want_quiet) fprintf(stderr, "Cannot find a video stream in file %s\n", file_name); avformat_close_input(&ff->pFormatCtx); return (-1); } ff_set_framerate(ff); { AVStream *avs = ff->pFormatCtx->streams[ff->videoStream]; #if 0 // DEBUG duration printf("DURATION frames from AVstream: %"PRIi64"\n", avs->nb_frames); printf("DURATION duration from AVstream: %"PRIi64"\n", avs->duration /* * av_q2d(avs->r_frame_rate) * av_q2d(avs->time_base)*/); printf("DURATION duration from FormatContext: %lu\n", ff->frames = ff->pFormatCtx->duration * ff->framerate / AV_TIME_BASE); #endif if (avs->nb_frames != 0) { ff->frames = avs->nb_frames; } else if (avs->duration != avs->duration && avs->duration != 0) // ??? ff->frames = avs->duration * av_q2d(avs->r_frame_rate) * av_q2d(avs->time_base); else { ff->frames = ff->pFormatCtx->duration * ff->framerate / AV_TIME_BASE; } ff->duration = (double) avs->duration * av_q2d(avs->time_base); } ff->tpf = 1.0/(av_q2d(ff->pFormatCtx->streams[ff->videoStream]->time_base)*ff->framerate); ff->file_frame_offset = ff->framerate*((double) ff->pFormatCtx->start_time/ (double) AV_TIME_BASE); if (want_verbose) { fprintf(stdout, "frame rate: %g\n", ff->framerate); fprintf(stdout, "length in seconds: %g\n", ff->duration); fprintf(stdout, "total frames: %ld\n", ff->frames); fprintf(stdout, "start offset: %.0f [frames]\n", ff->file_frame_offset); } // Get a pointer to the codec context for the video stream #if LIBAVFORMAT_BUILD > 4629 ff->pCodecCtx = ff->pFormatCtx->streams[ff->videoStream]->codec; #else ff->pCodecCtx = &(ff->pFormatCtx->streams[ff->videoStream]->codec); #endif // FIXME: don't scale here - announce aspect ratio // out_width/height remains in aspect 1:1 #ifdef SCALE_UP ff->movie_width = (int) floor((double)ff->pCodecCtx->height * ff_get_aspectratio(ff)); ff->movie_height = ff->pCodecCtx->height; #else ff->movie_width = ff->pCodecCtx->width; ff->movie_height = (int) floor((double)ff->pCodecCtx->width / ff_get_aspectratio(ff)); #endif // somewhere around LIBAVFORMAT_BUILD 4630 #ifdef AVFMT_FLAG_GENPTS if (ff->want_genpts) { ff->pFormatCtx->flags |= AVFMT_FLAG_GENPTS; // ff->pFormatCtx->flags |= AVFMT_FLAG_IGNIDX; } #endif if (want_verbose) fprintf(stdout, "movie size: %ix%i px\n", ff->movie_width, ff->movie_height); // Find the decoder for the video stream pCodec = avcodec_find_decoder(ff->pCodecCtx->codec_id); if(pCodec == NULL) { if (!want_quiet) fprintf(stderr, "Cannot find a codec for file: %s\n", file_name); avformat_close_input(&ff->pFormatCtx); return(-1); } // Open codec pthread_mutex_lock(&avcodec_lock); if(avcodec_open2(ff->pCodecCtx, pCodec, NULL) < 0) { if (!want_quiet) fprintf(stderr, "Cannot open the codec for file %s\n", file_name); pthread_mutex_unlock(&avcodec_lock); avformat_close_input(&ff->pFormatCtx); return(-1); } pthread_mutex_unlock(&avcodec_lock); if (!(ff->pFrame = avcodec_alloc_frame())) { if (!want_quiet) fprintf(stderr, "Cannot allocate video frame buffer\n"); avcodec_close(ff->pCodecCtx); avformat_close_input(&ff->pFormatCtx); return(-1); } if (!(ff->pFrameFMT = avcodec_alloc_frame())) { if (!want_quiet) fprintf(stderr, "Cannot allocate display frame buffer\n"); av_free(ff->pFrame); avcodec_close(ff->pCodecCtx); avformat_close_input(&ff->pFormatCtx); return(-1); } ff->out_width = ff->out_height = -1; ff->current_file = strdup(file_name); return(0); } static void reset_video_head(ffst *ff, AVPacket *packet) { int frameFinished = 0; if (!want_quiet) fprintf(stderr, "Resetting decoder - seek/playhead rewind.\n"); #if LIBAVFORMAT_BUILD < 4617 av_seek_frame(ff->pFormatCtx, ff->videoStream, 0); #else av_seek_frame(ff->pFormatCtx, ff->videoStream, 0, AVSEEK_FLAG_BACKWARD); #endif avcodec_flush_buffers(ff->pCodecCtx); while (!frameFinished) { av_read_frame(ff->pFormatCtx, packet); if(packet->stream_index == ff->videoStream) #if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR < 21) avcodec_decode_video(ff->pCodecCtx, ff->pFrame, &frameFinished, packet->data, packet->size); #else avcodec_decode_video2(ff->pCodecCtx, ff->pFrame, &frameFinished, packet); #endif if(packet->data) av_free_packet(packet); } ff->avprev = 0; } // TODO: set this high (>1000) if transport stopped and to a low value (<100) if transport is running. #define MAX_CONT_FRAMES (1000) static int my_seek_frame (ffst *ff, AVPacket *packet, int64_t timestamp) { AVStream *v_stream; int rv = 1; int64_t mtsb = 0; int frameFinished; int nolivelock = 0; static int ffdebug = 0; if (ff->videoStream < 0) return (0); v_stream = ff->pFormatCtx->streams[ff->videoStream]; if (ff->want_ignstart) // timestamps in the file start counting at ..->start_time timestamp += (int64_t) rint(ff->framerate*((double)ff->pFormatCtx->start_time / (double)AV_TIME_BASE)); // TODO: assert 0 <= timestamp + ts_offset - (..->start_time) < length #if LIBAVFORMAT_BUILD > 4629 // verify this version timestamp = av_rescale_q(timestamp, c1_Q, v_stream->time_base); timestamp = av_rescale_q(timestamp, c1_Q, v_stream->r_frame_rate); //< timestamp/=framerate; #endif #if LIBAVFORMAT_BUILD < 4617 rv= av_seek_frame(ff->pFormatCtx, ff->videoStream, timestamp / framerate * 1000000LL); #else if (ff->seekflags == SEEK_ANY) { rv = av_seek_frame(ff->pFormatCtx, ff->videoStream, timestamp, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD) ; avcodec_flush_buffers(ff->pCodecCtx); } else if (ff->seekflags == SEEK_KEY) { rv = av_seek_frame(ff->pFormatCtx, ff->videoStream, timestamp, AVSEEK_FLAG_BACKWARD) ; avcodec_flush_buffers(ff->pCodecCtx); } else if (ff->seekflags == SEEK_LIVESTREAM) { } else /* SEEK_CONTINUOUS */ if (ff->avprev >= timestamp || ((ff->avprev + 32*ff->tpf) < timestamp)) { // NOTE: only seek if last-frame is less then 32 frames behind // else read continuously until we get there :D // FIXME 32: use keyframes interval of video file or cmd-line-arg as threshold. // TODO: do we know if there is a keyframe inbetween now (my_avprev) // and the frame to seek to?? - if so rather seek to that frame than read until then. // and if no keyframe in between my_avprev and ts - prevent backwards seeks even if // timestamp-my_avprev > threshold! - Oh well. // seek to keyframe *BEFORE* this frame rv= av_seek_frame(ff->pFormatCtx, ff->videoStream, timestamp, AVSEEK_FLAG_BACKWARD) ; avcodec_flush_buffers(ff->pCodecCtx); } #endif ff->avprev = timestamp; if (rv < 0) return (0); // seek failed. read_frame: nolivelock++; if(av_read_frame(ff->pFormatCtx, packet) < 0) { if (!want_quiet) { fprintf(stderr, "Reached movie end\n"); } return (0); } #if LIBAVFORMAT_BUILD >= 4616 if (av_dup_packet(packet) < 0) { if (!want_quiet) { fprintf(stderr, "can not allocate packet\n"); } goto read_frame; } #endif if(packet->stream_index != ff->videoStream) { if (packet->data) av_free_packet(packet); goto read_frame; } /* backwards compatible - no cont. seeking (seekmode ANY or KEY ; cmd-arg: -K, -k) * do we want a AVSEEK_FLAG_ANY + SEEK_CONTINUOUS option ?? not now. */ #if LIBAVFORMAT_BUILD < 4617 return (1); #endif if ( ff->seekflags != SEEK_CONTINUOUS && ff->seekflags != SEEK_LIVESTREAM ) return (1); mtsb = packet->pts; if (mtsb == AV_NOPTS_VALUE) { mtsb = packet->dts; if (ffdebug == 0) { ffdebug = 1; if (!want_quiet) fprintf(stderr, "WARNING: video file does not report pts information.\n resorting to ffmpeg decompression timestamps.\n consider to transcode the file or use the --genpts option.\n"); } } if (mtsb == AV_NOPTS_VALUE) { if (ffdebug < 2) { ffdebug = 2; if (!want_quiet) fprintf(stderr, "ERROR: neither the video file nor the ffmpeg decoder were able to\n provide a video frame timestamp."); } av_free_packet(packet); return (0); } #if 1 // experimental /* remember live-stream PTS offset */ if ( ff->seekflags == SEEK_LIVESTREAM && mtsb != AV_NOPTS_VALUE && ff->stream_pts_offset == AV_NOPTS_VALUE && packet->flags&AV_PKT_FLAG_KEY) { ff->stream_pts_offset = mtsb; } if (ff->seekflags == SEEK_LIVESTREAM) { if (ff->stream_pts_offset != AV_NOPTS_VALUE) mtsb -= ff->stream_pts_offset; else mtsb = AV_NOPTS_VALUE; } #endif if (mtsb >= timestamp) { return (1); // ok! } /* skip to next frame */ #if LIBAVCODEC_VERSION_MAJOR < 52 || ( LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR < 21) avcodec_decode_video(ff->pCodecCtx, ff->pFrame, &frameFinished, packet->data, packet->size); #else avcodec_decode_video2(ff->pCodecCtx, ff->pFrame, &frameFinished, packet); #endif av_free_packet(packet); if (!frameFinished) goto read_frame; if (nolivelock < MAX_CONT_FRAMES) goto read_frame; reset_video_head(ff, packet); return (0); // seek failed. } /** * seeks to frame and decodes and scales video frame * * @arg ptr handle / ff-data structure * @arg frame video frame to seek to * @arg buf unused - see ff_get_bufferptr() - soon: optional buffer-pointer to copy data into * @arg w unused - target width -> out_height parameter when opening file * @arg h unused - target height -> out_height parameter when opening file * @arg xoff unused - soon: x-offset of this frame to target buffer * @arg xw unused - really unused * @arg ys unused - soon: y-stride (aka width of container) */ int ff_render(void *ptr, unsigned long frame, uint8_t* buf, int w, int h, int xoff, int xw, int ys) { ffst *ff = (ffst*) ptr; int frameFinished = 0; int64_t timestamp = (int64_t) frame; if (ff->buffer == ff->internal_buffer && (ff->buf_width <= 0 || ff->buf_height <= 0)) { ff_init_moviebuffer(ff); } if (ff->pFrameFMT && ff->pFormatCtx && my_seek_frame(ff, &ff->packet, timestamp)) { while (1) { /* Decode video frame */ frameFinished = 0; if(ff->packet.stream_index == ff->videoStream) #if LIBAVCODEC_VERSION_MAJOR < 52 || ( LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR < 21) avcodec_decode_video(ff->pCodecCtx, ff->pFrame, &frameFinished, ff->packet.data, ff->packet.size); #else avcodec_decode_video2(ff->pCodecCtx, ff->pFrame, &frameFinished, &ff->packet); #endif if(frameFinished) { /* Convert the image from its native format to FMT */ ff->pSWSCtx = sws_getCachedContext(ff->pSWSCtx, ff->pCodecCtx->width, ff->pCodecCtx->height, ff->pCodecCtx->pix_fmt, ff->out_width, ff->out_height, ff->render_fmt, SWS_BICUBIC, NULL, NULL, NULL); sws_scale(ff->pSWSCtx, (const uint8_t * const*) ff->pFrame->data, ff->pFrame->linesize, 0, ff->pCodecCtx->height, ff->pFrameFMT->data, ff->pFrameFMT->linesize); av_free_packet(&ff->packet); break; } else { if(ff->packet.data) av_free_packet(&ff->packet); if(av_read_frame(ff->pFormatCtx, &ff->packet) < 0) { if (!want_quiet) fprintf(stderr, "read error!\n"); reset_video_head(ff, &ff->packet); render_empty_frame(ff, buf, w, h, xoff, ys); break; } #if LIBAVFORMAT_BUILD >= 4616 if (av_dup_packet(&ff->packet) < 0) { if (!want_quiet) fprintf(stderr, "Cannot allocate packet\n"); break; } #endif } } /* end while !frame_finished */ } else { if (ff->pFrameFMT && !want_quiet) fprintf( stderr, "frame seek unsucessful (frame: %lu).\n", frame); } if (!frameFinished) { render_empty_frame(ff, buf, w, h, xoff, ys); return -1; } return 0; } void ff_get_info(void *ptr, VInfo *i) { ffst *ff = (ffst*) ptr; if (!i) return; // TODO check if move is open.. (not needed, dctrl prevents that) i->movie_width = ff->movie_width; i->movie_height = ff->movie_height; i->movie_aspect = ff_get_aspectratio(ptr); i->out_width = ff->out_width; i->out_height = ff->out_height; i->file_frame_offset = ff->file_frame_offset; if (ff->out_height > 0 && ff->out_width > 0) ff_getbuffersize(ptr, &i->buffersize); else i->buffersize = 0; i->frames = ff->frames; // ff->duration * ff->framerate; //fl2ratio(&(i->framerate->num), &(i->framerate->den), ff->framerate); ff_get_framerate(ptr, &i->framerate); } void ff_get_info_canonical(void *ptr, VInfo *i, int w, int h) { ffst *ff = (ffst*) ptr; if (!i) return; ff_get_info(ptr, i); i->out_width = w; i->out_height = h; ff_caononicalize_size2(ptr, &i->out_width, &i->out_height); i->buffersize = ff_picture_bytesize(ff->render_fmt, i->out_width, i->out_height); } void ff_create(void **ff) { (*((ffst**)ff)) = (ffst*) calloc(1, sizeof(ffst)); (*((ffst**)ff))->render_fmt = PIX_FMT_RGB24; (*((ffst**)ff))->seekflags = SEEK_CONTINUOUS; (*((ffst**)ff))->want_ignstart = 0; (*((ffst**)ff))->want_genpts = 0; (*((ffst**)ff))->packet.data = NULL; } void ff_destroy(void **ff) { ff_close_movie(*((ffst**)ff)); free(*((ffst**)ff)); *ff = NULL; } // buf needs to point to an allocated area of ff->out_width, ff->out_height. // ffmpeg will directly decode/scale into this buffer. // if it's NULL an internal buffer will be used. uint8_t *ff_set_bufferptr(void *ptr, uint8_t *buf) { ffst *ff = (ffst*) ptr; if (buf) ff->buffer = buf; else ff->buffer = ff->internal_buffer; avpicture_fill((AVPicture *)ff->pFrameFMT, ff->buffer, ff->render_fmt, ff->out_width, ff->out_height); return (NULL); // return prev. buffer? } uint8_t *ff_get_bufferptr(void *ptr) { ffst *ff = (ffst*) ptr; return ff->buffer; } void ff_resize(void *ptr, int w, int h, uint8_t *buf, VInfo *i) { ffst *ff = (ffst*) ptr; ff->out_width = w; ff->out_height = h; if (!buf) ff_caononical_size(ff); else ff_set_bufferptr(ptr, buf); if (i) ff_get_info(ptr, i); } const char * ff_fmt_to_text(int fmt) { switch (fmt) { case PIX_FMT_NONE: return "-"; case PIX_FMT_BGR24: return "BGR24"; case PIX_FMT_RGB24: return "RGB24"; case PIX_FMT_RGBA: return "RGBA"; case PIX_FMT_BGRA: return "BGRA"; case PIX_FMT_ARGB: return "ARGB"; case PIX_FMT_YUV420P: return "YUV420P"; case PIX_FMT_YUYV422: return "YUYV422"; case PIX_FMT_UYVY422: return "UYVY422"; case PIX_FMT_YUV440P: return "YUV440P"; default: return "?"; } } /* vi:set ts=8 sts=2 sw=2: */ harvid-0.7.3/libharvid/ffdecoder.h000066400000000000000000000026611215541677300170540ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2007-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _FFDECODER_H #define _FFDECODER_H #include void ff_create(void **ff); void ff_destroy(void **ff); void ff_get_info(void *ptr, VInfo *i); void ff_get_info_canonical(void *ptr, VInfo *i, int w, int h); int ff_render(void *ptr, unsigned long frame, uint8_t* buf, int w, int h, int xoff, int xw, int ys); int ff_open_movie(void *ptr, char *file_name, int render_fmt); int ff_close_movie(void *ptr); void ff_initialize (void); void ff_cleanup (void); uint8_t *ff_get_bufferptr(void *ptr); uint8_t *ff_set_bufferptr(void *ptr, uint8_t *buf); void ff_resize(void *ptr, int w, int h, uint8_t *buf, VInfo *i); int ff_picture_bytesize(int render_fmt, int w, int h); const char * ff_fmt_to_text(int fmt); #endif harvid-0.7.3/libharvid/frame_cache.c000066400000000000000000000310551215541677300173420ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include /* uint8_t */ #include #include /* calloc et al.*/ #include /* memset */ #include #include "decoder_ctrl.h" #include "dlog.h" #include "frame_cache.h" #include "ffcompat.h" #include "ffdecoder.h" #include #include #include //#define HASH_EMIT_KEYS 3 #define HASH_FUNCTION HASH_SFH #include "uthash.h" /* FLAGS */ #define CLF_DECODING 1 //< decoder is active #define CLF_INUSE 2 //< currently being served #define CLF_VALID 4 //< cacheline is valid (has decoded frame) #define CLF_RELEASE 8 //= cfg_cachesize) { time_t lru = time(NULL) + 1; videocacheline *tmp, *clru = NULL; HASH_ITER(hh, *cache, cl, tmp) { if (cl->flags == 0) return cl; if (!(cl->flags&(CLF_DECODING|CLF_INUSE)) && (cl->lru < lru)) { lru = cl->lru; clru = cl; } } if (clru) { HASH_DEL(*cache, clru); assert(clru->refcnt == 0); cl = clru; if (cl->b && cl->w == w && cl->h == h && cl->fmt == fmt) { cl->flags = 0; memset(&cl->hh, 0, sizeof(UT_hash_handle)); } else { free(cl->b); memset(cl, 0, sizeof(videocacheline)); } } else { dlog(DLOG_WARNING, "CACHE: cache full - all cache-lines in use.\n"); return NULL; } } if (!cl) cl = calloc(1, sizeof(videocacheline)); cl->id = id; cl->w = w; cl->h = h; cl->fmt = fmt; cl->frame = frame; cl->lru = 0; HASH_ADD(hh, *cache, id, CLKEYLEN, cl); return cl; } /* check if requested data exists in cache */ static videocacheline *testclwh(videocacheline *cache, pthread_rwlock_t *lock, int64_t frame, short w, short h, int fmt, unsigned short id) { videocacheline *rv; const videocacheline cmp = {id, w, h, fmt, frame, 0, 0, 0, NULL }; pthread_rwlock_rdlock(lock); HASH_FIND(hh, cache, &cmp, CLKEYLEN, rv); pthread_rwlock_unlock(lock); return rv; } /* clear cache * if f==1 wait for used cachelines to become unused * if f==0 the cache is flushed objects in use are retained * time a cacheline is needed */ static void clearcache(videocacheline **cache, pthread_rwlock_t *cachelock, int f, int id) { videocacheline *tmp, *cl = NULL; HASH_ITER(hh, *cache, cl, tmp) { if (id >= 0 && cl->id != id) { continue; } if (f) { if (cl->flags & (CLF_DECODING|CLF_INUSE)) { dlog(DLOG_WARNING, "CACHE: waiting for cacheline to be unlocked.\n"); } while (cl->flags & (CLF_DECODING|CLF_INUSE)) { pthread_rwlock_unlock(cachelock); mymsleep(5); pthread_rwlock_wrlock(cachelock); } } if (cl->flags & (CLF_DECODING|CLF_INUSE)) { continue; } HASH_DEL(*cache, cl); assert(cl->refcnt == 0); free(cl->b); free(cl); } } static void realloccl_buf(videocacheline *cptr, int w, int h, int fmt) { if (cptr->b && cptr->w == w && cptr->h == h && cptr->fmt == fmt) return; // already allocated free(cptr->b); cptr->alloc_size = ff_picture_bytesize(fmt, w, h); cptr->b = calloc(cptr->alloc_size, sizeof(uint8_t)); } /////////////////////////////////////////////////////////////////////////////// // Cache Control typedef struct { int cfg_cachesize; videocacheline *vcache; pthread_rwlock_t lock; int cache_hits; int cache_miss; } xjcd; static void fc_initialize_cache (xjcd *cc) { assert(!cc->vcache); cc->vcache = NULL; cc->cache_hits = 0; cc->cache_miss = 0; pthread_rwlock_init(&cc->lock, NULL); } static void fc_flush_cache (xjcd *cc) { pthread_rwlock_wrlock(&cc->lock); clearcache(&cc->vcache, &cc->lock, 1, -1); cc->cache_hits = 0; cc->cache_miss = 0; pthread_rwlock_unlock(&cc->lock); } static videocacheline *fc_readcl(xjcd *cc, void *dc, int64_t frame, short w, short h, int fmt, unsigned short vid, int *err) { /* check if the requested frame is cached */ videocacheline *rv = testclwh(cc->vcache, &cc->lock, frame, w, h, fmt, vid); int ds; if (err) *err = 0; if (rv) { pthread_rwlock_wrlock(&cc->lock); // rdlock should suffice here /* check if it has been recently invalidated by another thread */ if (rv->flags&CLF_VALID) { rv->refcnt++; rv->flags |= CLF_INUSE; pthread_rwlock_unlock(&cc->lock); rv->lru = time(NULL); cc->cache_hits++; return(rv); } pthread_rwlock_unlock(&cc->lock); rv = NULL; } /* too bad, now we need to allocate a new or free an used * cacheline and then decode the video... */ int timeout = 250; /* 1 second to get a buffer */ do { pthread_rwlock_wrlock(&cc->lock); rv = getcl(&cc->vcache, cc->cfg_cachesize, vid, w, h, fmt, frame); if (rv) { rv->flags |= CLF_DECODING; } pthread_rwlock_unlock(&cc->lock); if (!rv) { mymsleep(5); } } while(--timeout > 0 && !rv); if (!rv) { dlog(DLOG_WARNING, "CACHE: no buffer available.\n"); /* no buffer available */ if (err) *err = 503; return NULL; } /* set w,h,fmt and re-alloc buffer if neccesary */ realloccl_buf(rv, w, h, fmt); /* fill cacheline with data - decode video */ if ((ds=dctrl_decode(dc, vid, frame, rv->b, w, h, fmt))) { dlog(DLOG_WARNING, "CACHE: decode failed (%d).\n",ds); /* ds == -1 -> decode error; black frame will be rendered * ds == 503 -> no decoder avail. * ds == 500 -> invalid codec/format * (should not happen here - dctrl_get_info sorts that out) */ if(err) *err = ds; pthread_rwlock_wrlock(&cc->lock); // rdlock should suffice here /* we don't cache decode-errors */ rv->flags &= ~CLF_VALID; rv->flags &= ~CLF_DECODING; if (ds > 0) { /* no decoder available */ rv = NULL; } else { /* decoder available but decoding failed (EOF, invalid geometry...)*/ rv->flags |= CLF_INUSE; rv->refcnt++; } pthread_rwlock_unlock(&cc->lock); return (rv); } rv->lru = time(NULL); pthread_rwlock_wrlock(&cc->lock); // rdlock should suffice here rv->flags |= CLF_VALID|CLF_INUSE; rv->flags &= ~CLF_DECODING; rv->refcnt++; pthread_rwlock_unlock(&cc->lock); cc->cache_miss++; return(rv); } /////////////////////////////////////////////////////////////////////////////// // public API void vcache_clear (void *p, int id) { xjcd *cc = (xjcd*) p; pthread_rwlock_wrlock(&cc->lock); clearcache(&cc->vcache, &cc->lock, 0, id); cc->cache_hits = 0; cc->cache_miss = 0; pthread_rwlock_unlock(&cc->lock); } void vcache_create(void **p) { (*((xjcd**)p)) = (xjcd*) calloc(1, sizeof(xjcd)); (*((xjcd**)p))->cfg_cachesize = 48; fc_initialize_cache((*((xjcd**)p))); } void vcache_resize(void **p, int size) { if (size < (*((xjcd**)p))->cfg_cachesize) fc_flush_cache((*((xjcd**)p))); if (size > 0) (*((xjcd**)p))->cfg_cachesize = size; } void vcache_destroy(void **p) { xjcd *cc = *(xjcd**) p; fc_flush_cache(cc); pthread_rwlock_destroy(&cc->lock); free(cc->vcache); free(cc); *p = NULL; } uint8_t *vcache_get_buffer(void *p, void *dc, unsigned short id, int64_t frame, short w, short h, int fmt, void **cptr, int *err) { videocacheline *cl = fc_readcl((xjcd*)p, dc, frame, w, h, fmt, id, err); if (!cl) { if (cptr) *cptr = NULL; return NULL; } if (cptr) *cptr = cl; return cl->b; } void vcache_release_buffer(void *p, void *cptr) { xjcd *cc = (xjcd*) p; videocacheline *cl = (videocacheline *)cptr; if (!cptr) return; pthread_rwlock_wrlock(&cc->lock); if (--cl->refcnt < 1) { assert(cl->refcnt >= 0); cl->flags &= ~CLF_INUSE; if (cl->flags & CLF_RELEASE) { HASH_DEL(cc->vcache, cl); assert(cl->refcnt == 0); free(cl->b); free(cl); } } // TODO delete cacheline IFF !CLF_VALID (decode failed) ?! pthread_rwlock_unlock(&cc->lock); } void vcache_invalidate_buffer(void *p, void *cptr) { xjcd *cc = (xjcd*) p; videocacheline *cl = (videocacheline *)cptr; if (!cptr) return; pthread_rwlock_wrlock(&cc->lock); cl->flags |= CLF_RELEASE; pthread_rwlock_unlock(&cc->lock); } /////////////////////////////////////////////////////////////////////////////// // statistics static char *flags2txt(int f) { char *rv = NULL; size_t off = 0; if (f == 0) { rv = (char*) realloc(rv, (off+2) * sizeof(char)); off += sprintf(rv+off, "-"); return rv; } if (f&CLF_DECODING) { rv = (char*) realloc(rv, (off+10) * sizeof(char)); off += sprintf(rv+off, "decoding "); } if (f&CLF_VALID) { rv = (char*) realloc(rv, (off+7) * sizeof(char)); off += sprintf(rv+off, "valid "); } if (f&CLF_INUSE) { rv = (char*) realloc(rv, (off+8) * sizeof(char)); off += sprintf(rv+off, "in-use "); } if (f&CLF_RELEASE) { rv = (char*) realloc(rv, (off+8) * sizeof(char)); off += sprintf(rv+off, "to-free "); } return rv; } void vcache_info_html(void *p, char **m, size_t *o, size_t *s, int tbl) { int i = 1; videocacheline *cptr, *tmp; uint64_t total_bytes = 0; char bsize[32]; if (tbl&1) { rprintf("

Raw Video Frame Cache:

\n"); rprintf("

max available: %i\n", ((xjcd*)p)->cfg_cachesize); rprintf("cache-hits: %d, cache-misses: %d

\n", ((xjcd*)p)->cache_hits, ((xjcd*)p)->cache_miss); rprintf("\n"); } else { rprintf("\n"); rprintf("\n", ((xjcd*)p)->cache_hits, ((xjcd*)p)->cache_miss); } rprintf("\n"); /* walk comlete tree */ pthread_rwlock_rdlock(&((xjcd*)p)->lock); HASH_ITER(hh, ((xjcd*)p)->vcache, cptr, tmp) { char *tmp = flags2txt(cptr->flags); rprintf( "\n", i, cptr->id, tmp, cptr->alloc_size, cptr->w, cptr->h, (cptr->b ? ff_fmt_to_text(cptr->fmt) : "null"), cptr->frame, (long long) cptr->lru); free(tmp); total_bytes += cptr->alloc_size; i++; } if ((tbl&1) == 0) { rprintf("\n"); } if (total_bytes < 1024) { sprintf(bsize, "%.0f %s", total_bytes / 1.0, ""); } else if (total_bytes < 1024000 ) { sprintf(bsize, "%.1f %s", total_bytes / 1024.0, "Ki"); } else if (total_bytes < 10485760) { sprintf(bsize, "%.1f %s", total_bytes / 1048576.0, "Mi"); } else if (total_bytes < 1048576000) { sprintf(bsize, "%.2f %s", total_bytes / 1048576.0, "Mi"); } else { sprintf(bsize, "%.2f %s", total_bytes / 1073741824.0, "Gi"); } rprintf("\n", bsize); pthread_rwlock_unlock(&((xjcd*)p)->lock); if (tbl&2) { rprintf("

Raw Video Frame Cache:

max available: %d\n", ((xjcd*)p)->cfg_cachesize); rprintf(", cache-hits: %d, cache-misses: %d
#file-idFlagsAllocated BytesGeometryBufferFrame#LRU
%d.%d%s%d bytes%dx%d%s%"PRId64"%"PRIlld"
cache size: %sB in memory
\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/libharvid/frame_cache.h000066400000000000000000000023631215541677300173470ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _FRAME_CACHE_H #define _FRAME_CACHE_H #include #include void vcache_create(void **p); void vcache_destroy(void **p); void vcache_resize(void **p, int size); void vcache_clear (void *p, int id); uint8_t *vcache_get_buffer(void *p, void *dc, unsigned short id, int64_t frame, short w, short h, int fmt, void **cptr, int *err); void vcache_release_buffer(void *p, void *cptr); void vcache_invalidate_buffer(void *p, void *cptr); void vcache_info_html(void *p, char **m, size_t *o, size_t *s, int tbl); #endif harvid-0.7.3/libharvid/harvid.h000066400000000000000000000023551215541677300164100ustar00rootroot00000000000000/** @file harvid.h @brief libharvid -- video decoder/frame-cache This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _harvid_H #define _harvid_H #ifdef __cplusplus extern "C" { #endif /* pixel-format definitions */ #include /* libharvid public API */ #include "decoder_ctrl.h" #include "frame_cache.h" #include "image_cache.h" /* public ffdecoder.h API */ void ff_initialize (void); void ff_cleanup (void); int picture_bytesize(int render_fmt, int w, int h); #ifdef __cplusplus } #endif #endif harvid-0.7.3/libharvid/harvid.pc.in000066400000000000000000000003601215541677300171620ustar00rootroot00000000000000libdir=@LIBDIR@ includedir=@PREFIX@/include Name: Harvid Version: @VERSION@ Description: video decoder control and frame cache Requires: libavcodec libavformat libswscale libavutil Libs: -L${libdir} -lharvid Cflags: -I${includedir}/harvid harvid-0.7.3/libharvid/image_cache.c000066400000000000000000000205121215541677300173260ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include /* uint8_t */ #include #include /* calloc et al.*/ #include /* memset */ #include #include "dlog.h" #include "image_cache.h" #include #include #include //#define HASH_EMIT_KEYS 3 #define HASH_FUNCTION HASH_SFH #include "uthash.h" /* FLAGS */ #define CLF_VALID 1 //< cacheline is valid (has decoded frame) -- not needed, is it?! #define CLF_INUSE 2 //< currently being served typedef struct { int id; // file ID from VidMap short w; short h; int fmt; // image format int fmt_opt; // image format options (e.g jpeg quality) int64_t frame; int flags; int refcnt; // CLF_INUSE reference count time_t lru; // east recently used time //int hitcount // -- unused; least-frequently used idea uint8_t *b; //< data buffer pointer size_t s; //< data buffer size UT_hash_handle hh; } ImageCacheLine; #define CLKEYLEN (offsetof(ImageCacheLine, flags) - offsetof(ImageCacheLine, id)) /* image cache control */ typedef struct { ImageCacheLine *icache; int cfg_cachesize; pthread_rwlock_t lock; int cache_hits; int cache_miss; } ICC; static void ic_flush_cache (ICC *icc) { ImageCacheLine *cl, *tmp; pthread_rwlock_wrlock(&icc->lock); HASH_ITER(hh, icc->icache, cl, tmp) { HASH_DEL(icc->icache, cl); free(cl->b); free(cl); } icc->cache_hits = 0; icc->cache_miss = 0; pthread_rwlock_unlock(&icc->lock); } //////////// void icache_create(void **p) { ICC *icc; (*((ICC**)p)) = (ICC*) calloc(1, sizeof(ICC)); icc = (*((ICC**)p)); icc->cfg_cachesize = 32; icc->icache = NULL; icc->cache_hits = icc->cache_miss = 0; pthread_rwlock_init(&icc->lock, NULL); } void icache_destroy(void **p) { ICC *icc = (*((ICC**)p)); pthread_rwlock_destroy(&icc->lock); free(icc->icache); free(*((ICC**)p)); *p = NULL; } void icache_resize(void *p, int size) { if (size < ((ICC*)p)->cfg_cachesize) ic_flush_cache((ICC*) p); if (size > 0) ((ICC*)p)->cfg_cachesize = size; } void icache_clear (void *p) { ic_flush_cache((ICC*) p); } uint8_t *icache_get_buffer(void *p, unsigned short id, int64_t frame, int fmt, int fmt_opt, short w, short h, size_t *size, void **cptr) { ICC *icc = (ICC*) p; ImageCacheLine *cl = NULL; const ImageCacheLine cmp = {id, w, h, fmt, fmt_opt, frame, 0, 0, 0, NULL, 0}; pthread_rwlock_rdlock(&icc->lock); HASH_FIND(hh, icc->icache, &cmp, CLKEYLEN, cl); pthread_rwlock_unlock(&icc->lock); if (cl) { pthread_rwlock_wrlock(&icc->lock); if (cl->flags&CLF_VALID) { cl->refcnt++; cl->flags |= CLF_INUSE; pthread_rwlock_unlock(&icc->lock); if (size) *size = cl->s; if (cptr) *cptr = cl; cl->lru = time(NULL); icc->cache_hits++; return cl->b; } pthread_rwlock_unlock(&icc->lock); } /* not found in cache */ icc->cache_miss++; if (size) *size = 0; if (cptr) *cptr = NULL; return NULL; } int icache_add_buffer(void *p, unsigned short id, int64_t frame, int fmt, int fmt_opt, short w, short h, uint8_t *buf, size_t size) { ICC *icc = (ICC*) p; ImageCacheLine *cl = NULL, *tmp; pthread_rwlock_rdlock(&icc->lock); // wrlock ?! if (HASH_COUNT(icc->icache) >= icc->cfg_cachesize) { ImageCacheLine *tmp, *ilru = NULL; time_t lru = time(NULL) + 1; HASH_ITER(hh, icc->icache, cl, tmp) { if (cl->lru < lru && !(cl->flags & CLF_INUSE)) { lru = cl->lru; ilru = cl; } } if (ilru) { HASH_DEL(icc->icache, ilru); free(ilru->b); cl = ilru; memset(cl, 0, sizeof(ImageCacheLine)); } } pthread_rwlock_unlock(&icc->lock); if (!cl) cl = calloc(1, sizeof(ImageCacheLine)); cl->id = id; cl->w = w; cl->h = h; cl->fmt = fmt; cl->fmt_opt = fmt_opt; cl->frame = frame; cl->lru = 0; cl->b = buf; cl->s = size; cl->flags = CLF_VALID; pthread_rwlock_wrlock(&icc->lock); // check if found - added almost simultaneously by other thread HASH_FIND(hh, icc->icache, cl, CLKEYLEN, tmp); if (tmp) { pthread_rwlock_unlock(&icc->lock); free(cl); return -1; // buffer is freed by parent } HASH_ADD(hh, icc->icache, id, CLKEYLEN, cl); pthread_rwlock_unlock(&icc->lock); return 0; } void icache_release_buffer(void *p, void *cptr) { ICC *icc = (ICC*) p; if (!cptr) return; ImageCacheLine *cl = (ImageCacheLine *)cptr; pthread_rwlock_wrlock(&icc->lock); if (--cl->refcnt < 1) { assert(cl->refcnt >= 0); cl->flags &= ~CLF_INUSE; } pthread_rwlock_unlock(&icc->lock); } static char *flags2txt(int f) { char *rv = NULL; size_t off = 0; if (f == 0) { rv = (char*) realloc(rv, (off+2) * sizeof(char)); off += sprintf(rv+off, "-"); return rv; } if (f&CLF_VALID) { rv = (char*) realloc(rv, (off+7) * sizeof(char)); off += sprintf(rv+off, "valid "); } if (f&CLF_INUSE) { rv = (char*) realloc(rv, (off+8) * sizeof(char)); off += sprintf(rv+off, "in-use "); } return rv; } static const char * fmt_to_text(int fmt) { switch (fmt) { case 1: return "JPEG"; case 2: return "PNG"; case 3: return "PPM"; default: return "?"; } } void icache_info_html(void *p, char **m, size_t *o, size_t *s, int tbl) { int i = 1; ImageCacheLine *cptr, *tmp; uint64_t total_bytes = 0; char bsize[32]; if (tbl&1) { rprintf("

Encoded Image Cache:

\n"); rprintf("

max available: %i\n", ((ICC*)p)->cfg_cachesize); rprintf("cache-hits: %d, cache-misses: %d

\n", ((ICC*)p)->cache_hits, ((ICC*)p)->cache_miss); rprintf("\n"); } else { rprintf("\n"); rprintf("\n", ((ICC*)p)->cache_hits, ((ICC*)p)->cache_miss); } rprintf("\n"); /* walk comlete tree */ pthread_rwlock_rdlock(&((ICC*)p)->lock); HASH_ITER(hh, ((ICC*)p)->icache, cptr, tmp) { char *tmp = flags2txt(cptr->flags); #ifdef WIN32 rprintf("", i, cptr->id, tmp, cptr->s, cptr->w, cptr->h); #else rprintf("", i, cptr->id, tmp, cptr->s, cptr->w, cptr->h); #endif if (cptr->fmt == 1) { rprintf("", fmt_to_text(cptr->fmt), cptr->fmt_opt); } else { rprintf("", fmt_to_text(cptr->fmt)); } rprintf("\n", cptr->frame, (long long) cptr->lru); free(tmp); total_bytes += cptr->s; i++; } if ((tbl&1) == 0) { rprintf("\n"); } if (total_bytes < 1024) { sprintf(bsize, "%.0f %s", total_bytes / 1.0, ""); } else if (total_bytes < 1024000 ) { sprintf(bsize, "%.1f %s", total_bytes / 1024.0, "Ki"); } else if (total_bytes < 10485760) { sprintf(bsize, "%.1f %s", total_bytes / 1048576.0, "Mi"); } else if (total_bytes < 1048576000) { sprintf(bsize, "%.2f %s", total_bytes / 1048576.0, "Mi"); } else { sprintf(bsize, "%.2f %s", total_bytes / 1073741824.0, "Gi"); } rprintf("\n", bsize); pthread_rwlock_unlock(&((ICC*)p)->lock); if (tbl&2) { rprintf("

Encoded Image Cache :

max available: %d\n", ((ICC*)p)->cfg_cachesize); rprintf(", cache-hits: %d, cache-misses: %d
#file-idFlagsAllocated BytesGeometryBufferFrame#Last Hit
%d.%d%s%lu bytes%dx%d
%d.%d%s%zu bytes%dx%d%s Q:%d%s%"PRId64"%"PRIlld"
cache size: %sB in memory
\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/libharvid/image_cache.h000066400000000000000000000024751215541677300173430ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _IMAGE_CACHE_H #define _IMAGE_CACHE_H #include #include void icache_create(void **p); void icache_destroy(void **p); void icache_resize(void *p, int size); void icache_clear (void *p); uint8_t *icache_get_buffer(void *p, unsigned short id, int64_t frame, int fmt, int fmt_opt, short w, short h, size_t *size, void **cptr); int icache_add_buffer(void *p, unsigned short id, int64_t frame, int fmt, int fmt_opt, short w, short h, uint8_t *buf, size_t size); void icache_release_buffer(void *p, void *cptr); void icache_info_html(void *p, char **m, size_t *o, size_t *s, int tbl); #endif harvid-0.7.3/libharvid/timecode.c000066400000000000000000000066021215541677300167160ustar00rootroot00000000000000/* Copyright (C) 2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "timecode.h" #define TCtoDbl(r) ( (double)((r)->num) / (double)((r)->den) ) double timecode_rate_to_double(TimecodeRate const * const r) { return TCtoDbl(r); } static void timecode_sample_to_time (TimecodeTime * const t, TimecodeRate const * const r, const double samplerate, const int64_t sample) { const double fps_d = TCtoDbl(r); const int64_t fps_i = ceil(fps_d); if (r->drop) { int64_t frameNumber = floor((double)sample * fps_d / samplerate); /* there are 17982 frames in 10 min @ 29.97df */ const int64_t D = frameNumber / 17982; const int64_t M = frameNumber % 17982; t->subframe = rint(r->subframes * ((double)sample * fps_d / samplerate - (double)frameNumber)); if (t->subframe == r->subframes && r->subframes != 0) { t->subframe = 0; frameNumber++; } frameNumber += 18*D + 2*((M - 2) / 1798); t->frame = frameNumber % 30; t->second = (frameNumber / 30) % 60; t->minute = ((frameNumber / 30) / 60) % 60; t->hour = (((frameNumber / 30) / 60) / 60); } else { double timecode_frames_left_exact; double timecode_frames_fraction; int64_t timecode_frames_left; const double frames_per_timecode_frame = samplerate / fps_d; const int64_t frames_per_hour = (int64_t)(3600 * fps_i * frames_per_timecode_frame); t->hour = sample / frames_per_hour; double sample_d = sample % frames_per_hour; timecode_frames_left_exact = sample_d / frames_per_timecode_frame; timecode_frames_fraction = timecode_frames_left_exact - floor(timecode_frames_left_exact); t->subframe = (int32_t) rint(timecode_frames_fraction * r->subframes); timecode_frames_left = (int64_t) floor (timecode_frames_left_exact); if (t->subframe == r->subframes && r->subframes != 0) { t->subframe = 0; timecode_frames_left++; } t->minute = timecode_frames_left / (fps_i * 60); timecode_frames_left = timecode_frames_left % (fps_i * 60); t->second = timecode_frames_left / fps_i; t->frame = timecode_frames_left % fps_i; } } void timecode_framenumber_to_time (TimecodeTime * const t, TimecodeRate const * const r, const int64_t frameno) { timecode_sample_to_time(t, r, TCtoDbl(r), frameno); } void timecode_time_to_string (char *smptestring, TimecodeTime const * const t) { snprintf(smptestring, 12, "%02d:%02d:%02d:%02d", t->hour, t->minute, t->second, t->frame); } void timecode_framenumber_to_string (char *smptestring, TimecodeRate const * const r, const int64_t frameno) { TimecodeTime t; timecode_framenumber_to_time(&t, r, frameno); timecode_time_to_string(smptestring, &t); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/libharvid/timecode.h000066400000000000000000000040111215541677300167130ustar00rootroot00000000000000/* Copyright (C) 2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _timecode_H #define _timecode_H /* libtimecode -- timecode.h compatible */ #include #include #include #if (!defined int32_t && !defined __int8_t_defined && !defined _INT32_T) typedef int int32_t; #endif #if (!defined int64_t && !defined __int8_t_defined && !defined _UINT64_T) # if __WORDSIZE == 64 typedef long int int64_t; #else typedef long long int int64_t; #endif #endif /** * classical timecode */ typedef struct TimecodeTime { int32_t hour; ///< timecode hours 0..24 int32_t minute; ///< timecode minutes 0..59 int32_t second; ///< timecode seconds 0..59 int32_t frame; ///< timecode frames 0..fps int32_t subframe; ///< timecode subframes 0.. } TimecodeTime; /** * define a frame rate */ typedef struct TimecodeRate { int32_t num; ///< fps numerator int32_t den; ///< fps denominator int drop; ///< 1: use drop-frame timecode (only valid for 2997/100) int32_t subframes; ///< number of subframes per frame } TimecodeRate; double timecode_rate_to_double(TimecodeRate const * const r); void timecode_time_to_string (char *smptestring, TimecodeTime const * const t); void timecode_framenumber_to_time (TimecodeTime * const t, TimecodeRate const * const r, const int64_t frameno); void timecode_framenumber_to_string (char *smptestring, TimecodeRate const * const r, const int64_t frameno); #endif harvid-0.7.3/libharvid/uthash.h000066400000000000000000001633171215541677300164350ustar00rootroot00000000000000/* Copyright (c) 2003-2013, Troy D. Hanson http://uthash.sourceforge.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #include /* memcmp,strlen */ #include /* ptrdiff_t */ #include /* exit() */ /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #ifdef _MSC_VER /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #define DECLTYPE(x) #endif #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #ifdef NO_DECLTYPE #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while(0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while(0) #endif /* a number of the hash function use uint32_t which isn't defined on win32 */ #ifdef _MSC_VER typedef unsigned int uint32_t; typedef unsigned char uint8_t; #else #include /* uint32_t */ #endif #define UTHASH_VERSION 1.9.7 #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhe */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ unsigned _hf_bkt,_hf_hashv; \ out=NULL; \ if (head) { \ HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ keyptr,keylen,out); \ } \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) #define HASH_BLOOM_MAKE(tbl) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) #else #define HASH_BLOOM_MAKE(tbl) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #endif #define HASH_MAKE_TABLE(hh,head) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ sizeof(UT_hash_table)); \ if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ memset((head)->hh.tbl->buckets, 0, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ } while(0) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_bkt; \ (add)->hh.next = NULL; \ (add)->hh.key = (char*)keyptr; \ (add)->hh.keylen = (unsigned)keylen_in; \ if (!(head)) { \ head = (add); \ (head)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh,head); \ } else { \ (head)->hh.tbl->tail->next = (add); \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail = &((add)->hh); \ } \ (head)->hh.tbl->num_items++; \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ (add)->hh.hashv, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ HASH_FSCK(hh,head); \ } while(0) #define HASH_TO_BKT( hashv, num_bkts, bkt ) \ do { \ bkt = ((hashv) & ((num_bkts) - 1)); \ } while(0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ do { \ unsigned _hd_bkt; \ struct UT_hash_handle *_hd_hh_del; \ if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ head = NULL; \ } else { \ _hd_hh_del = &((delptr)->hh); \ if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ (head)->hh.tbl->tail = \ (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ (head)->hh.tbl->hho); \ } \ if ((delptr)->hh.prev) { \ ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ } else { \ DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ } \ if (_hd_hh_del->next) { \ ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ (head)->hh.tbl->hho))->prev = \ _hd_hh_del->prev; \ } \ HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh,head); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ HASH_FIND(hh,head,findstr,strlen(findstr),out) #define HASH_ADD_STR(head,strfield,add) \ HASH_ADD(hh,head,strfield,strlen(add->strfield),add) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head) \ do { \ unsigned _bkt_i; \ unsigned _count, _bkt_count; \ char *_prev; \ struct UT_hash_handle *_thh; \ if (head) { \ _count = 0; \ for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("invalid hh_prev %p, actual %p\n", \ _thh->hh_prev, _prev ); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("invalid bucket count %d, actual %d\n", \ (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("invalid hh item count %d, actual %d\n", \ (head)->hh.tbl->num_items, _count ); \ } \ /* traverse hh in app order; check next/prev integrity, count */ \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev !=(char*)(_thh->prev)) { \ HASH_OOPS("invalid prev %p, actual %p\n", \ _thh->prev, _prev ); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ (head)->hh.tbl->hho) : NULL ); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("invalid app item count %d, actual %d\n", \ (head)->hh.tbl->num_items, _count ); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ #ifdef HASH_FUNCTION #define HASH_FCN HASH_FUNCTION #else #define HASH_FCN HASH_JEN #endif /* The Bernstein hash function, used in Perl prior to v5.6 */ #define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _hb_keylen=keylen; \ char *_hb_key=(char*)(key); \ (hashv) = 0; \ while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ bkt = (hashv) & (num_bkts-1); \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ #define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _sx_i; \ char *_hs_key=(char*)(key); \ hashv = 0; \ for(_sx_i=0; _sx_i < keylen; _sx_i++) \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ bkt = hashv & (num_bkts-1); \ } while (0) #define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _fn_i; \ char *_hf_key=(char*)(key); \ hashv = 2166136261UL; \ for(_fn_i=0; _fn_i < keylen; _fn_i++) \ hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ bkt = hashv & (num_bkts-1); \ } while(0) #define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _ho_i; \ char *_ho_key=(char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ bkt = hashv & (num_bkts-1); \ } while(0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ char *_hj_key=(char*)(key); \ hashv = 0xfeedbeef; \ _hj_i = _hj_j = 0x9e3779b9; \ _hj_k = (unsigned)keylen; \ while (_hj_k >= 12) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12; \ } \ hashv += keylen; \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ case 5: _hj_j += _hj_key[4]; \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ case 1: _hj_i += _hj_key[0]; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ bkt = hashv & (num_bkts-1); \ } while(0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ do { \ char *_sfh_key=(char*)(key); \ uint32_t _sfh_tmp, _sfh_len = keylen; \ \ int _sfh_rem = _sfh_len & 3; \ _sfh_len >>= 2; \ hashv = 0xcafebabe; \ \ /* Main loop */ \ for (;_sfh_len > 0; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = (get16bits (_sfh_key+2) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= _sfh_key[sizeof (uint16_t)] << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ bkt = hashv & (num_bkts-1); \ } while(0) #ifdef HASH_USING_NO_STRICT_ALIASING /* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. * MurmurHash uses the faster approach only on CPU's where we know it's safe. * * Note the preprocessor built-in defines can be emitted using: * * gcc -m64 -dM -E - < /dev/null (on gcc) * cc -## a.c (where a.c is a simple test file) (Sun Studio) */ #if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) #define MUR_GETBLOCK(p,i) p[i] #else /* non intel */ #define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0) #define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1) #define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2) #define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3) #define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) #if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) #define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) #define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) #define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) #else /* assume little endian non-intel */ #define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) #define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) #define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) #endif #define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ MUR_ONE_THREE(p)))) #endif #define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) #define MUR_FMIX(_h) \ do { \ _h ^= _h >> 16; \ _h *= 0x85ebca6b; \ _h ^= _h >> 13; \ _h *= 0xc2b2ae35l; \ _h ^= _h >> 16; \ } while(0) #define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ do { \ const uint8_t *_mur_data = (const uint8_t*)(key); \ const int _mur_nblocks = (keylen) / 4; \ uint32_t _mur_h1 = 0xf88D5353; \ uint32_t _mur_c1 = 0xcc9e2d51; \ uint32_t _mur_c2 = 0x1b873593; \ uint32_t _mur_k1 = 0; \ const uint8_t *_mur_tail; \ const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \ int _mur_i; \ for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \ _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ _mur_k1 *= _mur_c1; \ _mur_k1 = MUR_ROTL32(_mur_k1,15); \ _mur_k1 *= _mur_c2; \ \ _mur_h1 ^= _mur_k1; \ _mur_h1 = MUR_ROTL32(_mur_h1,13); \ _mur_h1 = _mur_h1*5+0xe6546b64; \ } \ _mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \ _mur_k1=0; \ switch((keylen) & 3) { \ case 3: _mur_k1 ^= _mur_tail[2] << 16; \ case 2: _mur_k1 ^= _mur_tail[1] << 8; \ case 1: _mur_k1 ^= _mur_tail[0]; \ _mur_k1 *= _mur_c1; \ _mur_k1 = MUR_ROTL32(_mur_k1,15); \ _mur_k1 *= _mur_c2; \ _mur_h1 ^= _mur_k1; \ } \ _mur_h1 ^= (keylen); \ MUR_FMIX(_mur_h1); \ hashv = _mur_h1; \ bkt = hashv & (num_bkts-1); \ } while(0) #endif /* HASH_USING_NO_STRICT_ALIASING */ /* key comparison function; return 0 if keys equal */ #define HASH_KEYCMP(a,b,len) memcmp(a,b,len) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ do { \ if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \ else out=NULL; \ while (out) { \ if ((out)->hh.keylen == keylen_in) { \ if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \ } \ if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \ else out = NULL; \ } \ } while(0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,addhh) \ do { \ head.count++; \ (addhh)->hh_next = head.hh_head; \ (addhh)->hh_prev = NULL; \ if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ (head).hh_head=addhh; \ if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ && (addhh)->tbl->noexpand != 1) { \ HASH_EXPAND_BUCKETS((addhh)->tbl); \ } \ } while(0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(hh,head,hh_del) \ (head).count--; \ if ((head).hh_head == hh_del) { \ (head).hh_head = hh_del->hh_next; \ } \ if (hh_del->hh_prev) { \ hh_del->hh_prev->hh_next = hh_del->hh_next; \ } \ if (hh_del->hh_next) { \ hh_del->hh_next->hh_prev = hh_del->hh_prev; \ } /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(tbl) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ memset(_he_new_buckets, 0, \ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ tbl->ideal_chain_maxlen = \ (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ tbl->nonideal_items = 0; \ for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ { \ _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ tbl->nonideal_items++; \ _he_newbkt->expand_mult = _he_newbkt->count / \ tbl->ideal_chain_maxlen; \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ _he_thh; \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ tbl->num_buckets *= 2; \ tbl->log2_num_buckets++; \ tbl->buckets = _he_new_buckets; \ tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ (tbl->ineff_expands+1) : 0; \ if (tbl->ineff_expands > 1) { \ tbl->noexpand=1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } while(0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ _hs_psize++; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ if (! (_hs_q) ) break; \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ if (_hs_psize == 0) { \ _hs_e = _hs_q; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_qsize--; \ } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ _hs_e = _hs_p; \ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ ((void*)((char*)(_hs_p->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_psize--; \ } else if (( \ cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ ) <= 0) { \ _hs_e = _hs_p; \ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ ((void*)((char*)(_hs_p->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail ) { \ _hs_tail->next = ((_hs_e) ? \ ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ _hs_e->prev = ((_hs_tail) ? \ ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ _hs_tail->next = NULL; \ if ( _hs_nmerges <= 1 ) { \ _hs_looping=0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2; \ } \ HASH_FSCK(hh,head); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt=NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if (src) { \ for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ if (!dst) { \ DECLTYPE_ASSIGN(dst,_elt); \ HASH_MAKE_TABLE(hh_dst,dst); \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ (dst)->hh_dst.tbl->num_items++; \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst,dst); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if (head) { \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)=NULL; \ } \ } while(0) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \ el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL)) #else #define HASH_ITER(hh,head,el,tmp) \ for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \ el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL)) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1 #define HASH_BLOOM_SIGNATURE 0xb12220f2 typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; char bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ harvid-0.7.3/libharvid/vinfo.c000066400000000000000000000020501215541677300162370ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "vinfo.h" void jvi_init (VInfo *ji) { memset (ji, 0, sizeof(VInfo)); memset(&ji->framerate, 0, sizeof(TimecodeRate)); ji->framerate.num = 25; ji->framerate.den = 1; ji->out_width = ji->out_height = -1; ji->file_frame_offset = 0.0; } void jvi_free (VInfo *i) { ; } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/libharvid/vinfo.h000066400000000000000000000033041215541677300162470ustar00rootroot00000000000000/** @file vinfo.h @brief video information This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _VINFO_H #define _VINFO_H #include #include /* uint8_t */ #include "timecode.h" /** video-file information */ typedef struct { int movie_width; ///< read-only image-size int movie_height; ///< read-only image-size double movie_aspect; ///< read-only original aspect-ratio int out_width; ///< actual output image geometry int out_height; ///< may be overwritten TimecodeRate framerate; ///< framerate num/den&flags int64_t frames; ///< duration of file in frames size_t buffersize; ///< size in bytes used for an image of out_width x out_height at render_rmt (VInfo) double file_frame_offset; } VInfo; /** initialise a VInfo struct * @param i VInfo struct to initialize */ void jvi_init (VInfo*i); /** clear and free VInfo struct * @param i VInfo struct to free */ void jvi_free (VInfo*i); #endif harvid-0.7.3/src/000077500000000000000000000000001215541677300136005ustar00rootroot00000000000000harvid-0.7.3/src/Makefile000066400000000000000000000046271215541677300152510ustar00rootroot00000000000000LOGODEP=logo.o seek.o include ../common.mak CONFIGTEMP=conf.out ifeq ($(shell pkg-config --exists libavcodec libavformat libswscale || echo no), no) $(error "http://ffmpeg.org is required - install libavcodec-dev, libswscale-dev, etc") endif ifeq ($(shell pkg-config --exists libpng || echo no), no) $(error "libpng is required - install libpng-dev") endif ifeq ($(shell $(ECHO) "\#include \n\#include \nint main() { struct jpeg_error_mgr jerr; jpeg_std_error(&jerr); return 0; }" | $(CC) -pipe -x c -o $(CONFIGTEMP) $(ARCHINCLUDES) $(LDFLAGS) - -ljpeg 2>/dev/null || echo no; $(RM) -f $(CONFIGTEMP)), no) $(error "libjpeg is required - install libjpeg-dev, libjpeg8-dev or libjpeg62-dev") endif FLAGS=-I../libharvid/ FLAGS+=$(ARCHINCLUDES) $(ARCHFLAGS) FLAGS+=`pkg-config --cflags libavcodec libavformat libavutil libpng libswscale` LOADLIBES=$(ARCHLIBES) LOADLIBES+=`pkg-config --libs libavcodec libavformat libavutil libpng libswscale` LOADLIBES+=-ljpeg LOADLIBES+=-lz -lm FLAGS+=-DICSVERSION="\"$(VERSION)\"" -DICSARCH="\"$(UNAME)\"" all: harvid HARVID_H = \ daemon_log.h daemon_util.h \ socket_server.h \ enums.h \ favicon.h \ ics_handler.h httprotocol.h htmlconst.h \ image_format.h \ ../libharvid/vinfo.h \ ../libharvid/frame_cache.h \ ../libharvid/image_cache.h\ ../libharvid/ffdecoder.h \ ../libharvid/decoder_ctrl.h \ ../libharvid/ffcompat.h \ ../libharvid/timecode.h HARVID_SRC = \ harvid.c \ daemon_log.c daemon_util.c \ fileindex.c htmlseek.c \ httprotocol.c ics_handler.c \ image_format.c \ socket_server.c \ ../libharvid/libharvid.a logo.o: ../doc/harvid.jpg $(LD) -r -b binary -o logo.o ../doc/harvid.jpg seek.o: ../doc/seek.js $(LD) -r -b binary -o seek.o ../doc/seek.js harvid: $(HARVID_SRC) $(HARVID_H) $(LOGODEP) export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH);\ $(CC) -o $(@) $(CFLAGS) $(FLAGS) $(HARVID_SRC) $(LOGODEP) $(LDFLAGS) $(LOADLIBES) clean: rm -f harvid logo.o seek.o cscope.* tags man: harvid help2man -N -n 'video server' -o ../doc/harvid.1 ./harvid install: install-bin uninstall: uninstall-bin install-bin: harvid install -d $(DESTDIR)$(bindir) install -m755 harvid $(DESTDIR)$(bindir) uninstall-bin: rm -f $(DESTDIR)$(bindir)/harvid -rmdir $(DESTDIR)$(bindir) install-man: uninstall-man: install-lib: uninstall-lib: .PHONY: all install uninstall install-man uninstall-man install-bin uninstall-bin install-lib uninstall-lib harvid-0.7.3/src/daemon_log.c000066400000000000000000000062631215541677300160570ustar00rootroot00000000000000/* Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include // getpid #define DAEMON_LOG_SELF #include "daemon_log.h" int debug_level = DLOG_INFO; int debug_section = 0; int use_syslog = 0; //internal FILE *my_logfile = NULL; //internal #define LOGLEN (1024) void dlog(int level, const char *format, ...) { va_list arglist; char text[LOGLEN], timestamped[LOGLEN]; FILE *out = stderr; int dotimestamp = 0; if(level > debug_level) return; va_start(arglist, format); vsnprintf(text, LOGLEN, format, arglist); va_end(arglist); text[LOGLEN -1] = 0; if (level > 7) { level = 7; } if (level < 0) { level = 0; } #ifndef HAVE_WINDOWS if(use_syslog) { syslog(level, "%s", text); return; } else #endif if (my_logfile) { out = my_logfile; dotimestamp = 1; } else if (level > DLOG_WARNING) { out = stdout; } if(dotimestamp) { struct tm *timeptr; time_t now; now = time(NULL); now = mktime(gmtime(&now)); now += mktime(localtime(&now)) - mktime(gmtime(&now)); // localtime timeptr = localtime(&now); snprintf(timestamped, LOGLEN, "%04d.%02d.%02d %02d:%02d:%02d LOG%d[%lu]: %s", timeptr->tm_year+1900, timeptr->tm_mon+1, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, level, (unsigned long) getpid(), text); } else { snprintf(timestamped, LOGLEN, "%s", text); } timestamped[LOGLEN -1] = 0; fprintf(out, "%s", timestamped); fflush(out); // may kill performance } void dlog_open(char *log_file) { int fd; #ifndef HAVE_WINDOWS if (!log_file) { openlog("harvid", LOG_CONS | LOG_NDELAY | LOG_PID, 0 ? LOG_DAEMON : LOG_USER); use_syslog = 1; return; } #endif if ((fd = open(log_file, O_CREAT|O_WRONLY|O_APPEND, 0640))) { #ifndef HAVE_WINDOWS fcntl(fd, F_SETFD, FD_CLOEXEC); #endif my_logfile = fdopen(fd, "a"); if (my_logfile) return; } dlog(DLOG_ERR, "Unable to open log file: '%s'\n", log_file); } void dlog_close(void) { if(my_logfile) { fclose(my_logfile); my_logfile = NULL; return; } #ifndef HAVE_WINDOWS if(use_syslog) closelog(); #endif } const char *dlog_level_name(int lvl) { switch (lvl) { case DLOG_EMERG: return "quiet"; case DLOG_CRIT: return "critical"; case DLOG_ERR: return "error"; case DLOG_WARNING: return "warning"; case DLOG_INFO: return "info"; default: return "-"; } } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/daemon_log.h000066400000000000000000000031221215541677300160530ustar00rootroot00000000000000/** @file daemon_log.h @brief output and logfile abstraction This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _dlog_H #define _dlog_H #include #ifndef DAEMON_LOG_SELF extern int debug_level; ///< global debug_level used by @ref dlog() extern int debug_section; ///< global debug_level used by @ref dlog() #endif /** * initialise dlog output. * if log_file is NULL - syslog is used - otherwise messages are * written to the specified file. - if dlog_open is not called * dlog writes to stdout (>DLOG_WARNING) or stderr (Warnings, Errors). * * @param log_file file-name or NULL */ void dlog_open(char *log_file); /** * dlog_close() can be called anytime. It closes syslog or log-files if they * were opened. After closing the log, dlog() will write to stdout/stderr. */ void dlog_close(void); const char *dlog_level_name(int lvl); #endif harvid-0.7.3/src/daemon_util.c000066400000000000000000000067351215541677300162570ustar00rootroot00000000000000/* Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #ifndef HAVE_WINDOWS #include #include #endif #include #define DEV_NULL "/dev/null" int daemonize (void) { #ifndef HAVE_WINDOWS switch(fork()) { case -1: /* error */ dlog(DLOG_CRIT, "SYS: fork() failed!\n"); return -1; case 0: /* child */ #ifdef NOSETSID ioctl(0, TIOCNOTTY, 0) ; #else setsid(); #endif chdir("/") ; fclose(stdin); fclose(stdout); fclose(stderr); break; default: /* parent */ exit(0); } #else dlog(DLOG_WARNING, "SYS: windows OS does not support daemon mode.\n"); #endif return 0; } int resolve_uid(const char *setuid_user) { #ifndef HAVE_WINDOWS uid_t uid = 0; struct passwd *pw; if (!setuid_user) return 0; pw = getpwnam(setuid_user); if(pw) uid = pw->pw_uid; else if(atoi(setuid_user)) /* numerical? */ uid = atoi(setuid_user); else { uid = -1; } if (uid < 0) { dlog(DLOG_CRIT, "SYS: failed to lookup UID for user '%s'\n", setuid_user); } return uid; #else return 0; #endif } int resolve_gid(const char *setgid_group) { #ifndef HAVE_WINDOWS gid_t gid = 0; struct group *gr; if(!setgid_group) return 0; gr = getgrnam(setgid_group); if(gr) gid = gr->gr_gid; else if(atoi(setgid_group)) /* numerical? */ gid = atoi(setgid_group); else { gid = -1; } if (gid < 0) { dlog(DLOG_CRIT, "SYS: failed to lookup GID for group '%s'\n", setgid_group); } return gid; #else return 0; #endif } /* set process user and group(s) id */ int drop_privileges(const int uid, const int gid) { #ifndef HAVE_WINDOWS if (getuid()) { dlog(DLOG_WARNING, "SYS: non-suid. Keeping current privileges.\n"); return 0; } if (gid || uid) dlog(DLOG_INFO, "SYS: drop privileges; uid:%i gid:%i -> uid:%i gid:%i\n", getuid(), getgid(), uid, gid); /* Set uid and gid */ if(gid) { if(setgid(gid)) { dlog(DLOG_CRIT, "SYS: setgid failed.\n"); return -3; } } if(uid) { if(setuid(uid)) { dlog(DLOG_CRIT, "SYS: setuid failed.\n"); return -4; } } dlog(DLOG_INFO, "SYS: privs now: uid:%i gid:%i\n", getuid(), getgid()); #else dlog(DLOG_WARNING, "SYS: windows OS does not support privilege uid/gid changes.\n"); #endif return 0; } int do_chroot (char *chroot_dir) { #ifndef HAVE_WINDOWS if(chroot_dir) { if(chroot(chroot_dir)) { dlog(DLOG_CRIT, "SYS: Failed to chroot to '%s'\n", chroot_dir); return -1; } if(chdir("/")) { dlog(DLOG_CRIT, "SYS: Failed to chdir after chroot\n"); return -2; } dlog(DLOG_INFO, "SYS: chroot()ed to '%s'\n", chroot_dir); } #else dlog(DLOG_WARNING, "SYS: windows OS does not support chroot() operation.\n"); #endif return 0; } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/daemon_util.h000066400000000000000000000035211215541677300162520ustar00rootroot00000000000000/** @file daemon_util.h @brief common unix system utility functions This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _dutil_H #define _dutil_H /** * fork the current process in the background and close * standard-file descriptors. * @return 0 if successful, -1 on error (fork failed) */ int daemonize (void); /** * resolve unix-user-name or ID to integer * @param setuid_user unix user name or ID * @return -1 on error, uid otherwise */ int resolve_uid(const char *setuid_user); /** * resolve unix-group-name or ID to integer * @param setgid_group unix group name or ID * @return -1 on error, gid otherwise */ int resolve_gid(const char *setgid_group); /** * assume a differernt user identity - drop root privileges. * @param uid unix user id * @param gid unix group id * @return 0 if successful, negative number on error */ int drop_privileges(const int uid, const int gid); /** * change root - jail the daemon to confined path on the system * @param chroot_dir root directory of the server. * @return 0 if successful, negative number on error */ int do_chroot (char *chroot_dir); #endif harvid-0.7.3/src/enums.h000066400000000000000000000021601215541677300150770ustar00rootroot00000000000000/* Copyright (C) 2008 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _harvid_enums_H #define _harvid_enums_H /* ics_request_args->render_fmt */ enum { /* image output format */ FMT_RAW=0, FMT_JPG, FMT_PNG, FMT_PPM, /* info output format */ OUT_HTML, OUT_JSON, OUT_PLAIN, OUT_CSV }; /* http index option(s) */ enum {OPT_FLAT=1}; /* cfg_adminmask - binary flags */ enum {ADM_FLUSHCACHE=1, ADM_PURGECACHE=2, ADM_SHUTDOWN=4}; enum {USR_INDEX=1, USR_FLATINDEX=2, USR_KEEPRAW=4, USR_WEBSEEK=8}; #endif harvid-0.7.3/src/favicon.h000066400000000000000000000107761215541677300154110ustar00rootroot00000000000000/* Copyright (C) 2008 Robin Gareus 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 2, 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 . */ const uint8_t favicon_data[766] = { 0x00,0x00,0x01,0x00,0x01,0x00,0x20,0x20,0x10,0x00,0x01,0x00,0x04,0x00,0xe8,0x02 ,0x00,0x00,0x16,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x40,0x00 ,0x00,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02 ,0x00,0x00,0x00,0x01,0x3c,0x00,0x55,0x15,0x03,0x00,0x00,0x20,0x00,0x00,0x03,0x00 ,0x9c,0x00,0x95,0x25,0x03,0x00,0x00,0x00,0xc3,0x00,0x01,0x3a,0x00,0x00,0xcd,0x2e ,0x0f,0x00,0x00,0x00,0xff,0x00,0xff,0x3e,0x2d,0x00,0x00,0x6c,0x09,0x00,0x00,0xc5 ,0x00,0x00,0x00,0xfd,0x00,0x00,0xfb,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0xff,0xff ,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff ,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff ,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xee ,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xff ,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xbb ,0x00,0x00,0x00,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x01,0x44,0x44,0x10,0x00,0x00,0xcd ,0xcb,0x30,0x00,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x14,0x99,0x99,0x41,0x00,0x00,0xcd ,0xdd,0xcb,0x30,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x49,0x99,0x99,0x94,0x00,0x00,0xcd ,0xdd,0xdd,0xc7,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x49,0x99,0x99,0x96,0x00,0x00,0xcd ,0xdd,0xdc,0xb3,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x49,0x99,0x99,0x94,0x00,0x00,0xcd ,0xdc,0xb3,0x00,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x49,0x99,0x99,0x94,0x00,0x00,0xcc ,0xb0,0x00,0x00,0x08,0xaa,0xaa,0xaa,0xa2,0x00,0x16,0x99,0x99,0x61,0x00,0x00,0x70 ,0x00,0x00,0x00,0x05,0x88,0x88,0x88,0x82,0x00,0x01,0x44,0x64,0x10,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff ,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xee ,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xee,0xee,0xe0,0x00,0xff ,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0xff,0xff,0xf0,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff ,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff ,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff ,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x3e ,0x3e,0x3e,0x00,0x00,0x00,0x00,0x3e,0x3e,0x3e,0x3e,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x3e,0x3e,0x3e,0x3e,0x00,0x00,0x00,0x00,0x3e,0x3e,0x3e,0x3e,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff }; harvid-0.7.3/src/fileindex.c000066400000000000000000000257361215541677300157300ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "httprotocol.h" #include "htmlconst.h" #include "enums.h" #ifndef MAX_PATH # ifdef PATH_MAX # define MAX_PATH PATH_MAX # else # define MAX_PATH (1024) # endif #endif extern int cfg_usermask; char *url_escape(const char *string, int inlength); // from httprotocol.c char *str_escape(const char *string, int inlength, const char esc) { char *ns; size_t i, o, a; const char *t = string; if (!string) return strdup(""); if (inlength == 0) inlength = strlen(string)+1; a = inlength; while(*t && (t = strchr(t, '"'))) { a++; t++; } ns = malloc(a); for (i = 0, o = 0; i < a; ++i) { if (string[i] == '"') { ns[o++] = esc; ns[o++] = '"'; } else { ns[o++] = string[i]; } } return ns; } static void print_html (int what, const char *burl, const char *path, const char *name, time_t mtime, char **m, size_t *o, size_t *s, int *num) { switch(what) { case 1: { char *u1, *u2; u1 = url_escape(path, 0); u2 = url_escape(name, 0); if (cfg_usermask & USR_WEBSEEK) { rprintf("
  • [F] %s", burl, u1, SL_SEP(path), u2, name); } else { rprintf("
  • [F] %s", burl, u1, SL_SEP(path), u2, name); } rprintf( " [info]", burl, u1, u2 ); rprintf("
  • \n"); if (u1) free(u1); if (u2) free(u2); (*num)++; } break; case 0: { char *u2 = url_escape(name, 0); rprintf( "
  • [D]%s
  • \n", burl, SL_SEP(path), u2, name); free(u2); (*num)++; } break; default: break; } } static void print_csv (int what, const char *burl, const char *path, const char *name, time_t mtime, char **m, size_t *o, size_t *s, int *num) { switch(what) { case 1: { char *u1, *u2, *c1; u1 = url_escape(path, 0); u2 = url_escape(name, 0); c1 = str_escape(name, 0, '"'); rprintf("F,\"%s\",\"%s%s%s\",\"%s\",%"PRIlld"\n", burl, u1, SL_SEP(path), u2, c1, (long long) mtime); free(u1); free(u2); free(c1); (*num)++; } break; case 0: { char *u2, *c1; u2 = url_escape(name, 0); c1 = str_escape(name, 0, '"'); rprintf("D,\"%s%s%s/\",\"%s\",%"PRIlld"\n", burl, SL_SEP(path), u2, c1, (long long) mtime); free(u2); free(c1); (*num)++; } break; default: break; } } static void print_plain (int what, const char *burl, const char *path, const char *name, time_t mtime, char **m, size_t *o, size_t *s, int *num) { switch(what) { case 1: { char *u1, *u2; u1 = url_escape(path, 0); u2 = url_escape(name, 0); rprintf("f %s?file=%s%s%s\n", burl, u1, SL_SEP(path), u2); free(u1); free(u2); (*num)++; } break; case 0: { char *u2; u2 = url_escape(name, 0); rprintf("d %s%s%s/\n", burl, SL_SEP(path), u2); free(u2); (*num)++; } break; default: break; } } static void print_json (int what, const char *burl, const char *path, const char *name, time_t mtime, char **m, size_t *o, size_t *s, int *num) { switch(what) { case 1: { char *u1, *u2, *c1; u1 = url_escape(path, 0); u2 = url_escape(name, 0); c1 = str_escape(name, 0, '\\'); rprintf("%s{\"type\":\"file\", \"baseurl\":\"%s\", \"file\":\"%s%s%s\", \"name\":\"%s\", \"mtime\":%"PRIlld"}", (*num > 0) ? ", ":"", burl, u1, SL_SEP(path), u2, c1, (long long) mtime); free(u1); free(u2); free(c1); (*num)++; } break; case 0: { char *u2, *c1; u2 = url_escape(name, 0); c1 = str_escape(name, 0, '\\'); rprintf("%s{\"type\":\"dir\", \"indexurl\":\"%s%s%s/\", \"name\":\"%s\", \"mtime\":%"PRIlld"}", (*num > 0) ? ", ":"", burl, SL_SEP(path), u2, c1, (long long) mtime); free(u2); free(c1); (*num)++; } break; default: break; } } static void parse_direntry (const char *root, const char *burl, const char *path, const char *name, time_t mtime, int opt, char **m, size_t *o, size_t *s, int *num, void (*print_fn)(const int what, const char*, const char*, const char*, time_t, char**, size_t*, size_t*, int *) ) { const int l3 = strlen(name) - 3; const int l4 = l3 - 1; const int l5 = l4 - 1; const int l6 = l5 - 1; const int l9 = l6 - 3; if ((l4 > 0 && ( !strcmp(&name[l4], ".avi") || !strcmp(&name[l4], ".mov") || !strcmp(&name[l4], ".ogg") || !strcmp(&name[l4], ".ogv") || !strcmp(&name[l4], ".mpg") || !strcmp(&name[l4], ".mov") || !strcmp(&name[l4], ".mp4") || !strcmp(&name[l4], ".mkv") || !strcmp(&name[l4], ".vob") || !strcmp(&name[l4], ".asf") || !strcmp(&name[l4], ".avs") || !strcmp(&name[l4], ".dts") || !strcmp(&name[l4], ".flv") || !strcmp(&name[l4], ".m4v") )) || (l5 > 0 && ( !strcmp(&name[l5], ".h264") || !strcmp(&name[l5], ".webm") )) || (l6 > 0 && ( !strcmp(&name[l6], ".dirac") )) || (l9 > 0 && ( !strcmp(&name[l9], ".matroska") )) || (l3 > 0 && ( !strcmp(&name[l3], ".dv") )) ) { char *url = strdup(burl); char *vurl = strstr(url, "/index"); // TODO - do once per dir. if (vurl) *++vurl = 0; print_fn(1, url, path, name, mtime, m, o, s, num); free(url); } } static int parse_dir (const int fd, const char *root, const char *burl, const char *path, int opt, char **m, size_t *o, size_t *s, int *num, void (*print_fn)(const int what, const char*, const char*, const char*, time_t, char**, size_t*, size_t*, int *) ) { DIR *D; struct dirent *dd; char dn[MAX_PATH]; int rv = 0; snprintf(dn, MAX_PATH, "%s%s%s", root, SL_SEP(root), path); debugmsg(DEBUG_ICS, "IndexDir: indexing '%s'\n", dn); if (!(D = opendir (dn))) { dlog(LOG_WARNING, "IndexDir: could not open dir '%s'\n", dn); return -1; } while ((dd = readdir (D))) { struct stat fs; char rn[MAX_PATH]; // absolute, starting at local root / if (dd->d_name[0] == '.') continue; // make optional #if 0 int delen = strlen(d->d_name); if (delen == 1 && dd->d_name[0] == '.') continue; // '.' if (delen == 2 && dd->d_name[0] == '.' && dd->d_name[1] == '.') continue; // '..' #endif snprintf(rn, MAX_PATH, "%s/%s", dn, dd->d_name); if(stat(rn, &fs) == 0) { // XXX lstat vs stat char fn[MAX_PATH]; // relative to this *root. snprintf(fn, MAX_PATH, "%s%s%s", path, SL_SEP(path), dd->d_name); if (S_ISDIR(fs.st_mode)) { if ((opt&OPT_FLAT) == OPT_FLAT) { char pn[MAX_PATH]; snprintf(pn, MAX_PATH, "%s%s%s/", path, SL_SEP(path), dd->d_name); if ((rv = parse_dir(fd, root, burl, pn, opt, m, o, s, num, print_fn))) { if (rv == -1) rv = 0; // opendir failed -- continue else break; } if (strlen(*m) > 0) { int tx = CSEND(fd, (*m)); if (tx > 0) { (*o) = 0; (*m)[0] = '\0'; } else if (tx < 0) { debugmsg(DEBUG_ICS, "abort indexing\n"); rv = -2; break; } } } else { print_fn(0, burl, path, dd->d_name, fs.st_mtime, m, o, s, num); } } else if ( #ifndef HAVE_WINDOWS S_ISLNK(fs.st_mode) || #endif S_ISREG(fs.st_mode)) { parse_direntry(root, burl, path, dd->d_name, fs.st_mtime, opt, m, o, s, num, print_fn); } } } closedir(D); return rv; } void hdl_index_dir (int fd, const char *root, char *base_url, char *path, int fmt, int opt) { size_t off = 0; size_t ss = 1024; char *sm = malloc(ss * sizeof(char)); const int bo = 5 + strstr(base_url, "/index") - base_url; int bl = strlen(base_url) - 1; int num = 0; sm[0] = '\0'; switch (fmt) { case OUT_PLAIN: break; case OUT_JSON: { char *p1 = str_escape(path, 0, '\\'); raprintf(sm, off, ss, "{\"path\":\"%s\", \"index\":[", p1); free(p1); } break; case OUT_CSV: break; default: raprintf(sm, off, ss, DOCTYPE HTMLOPEN); raprintf(sm, off, ss, "harvid Index\n"); raprintf(sm, off, ss, "\n"); raprintf(sm, off, ss, "\n"); raprintf(sm, off, ss, HTMLBODY); raprintf(sm, off, ss, "

    harvid - Index

    \n"); raprintf(sm, off, ss, "

    Path: %s

    \n
    \n

    Total Entries: %d

    \n", num); raprintf(sm, off, ss, "
    "SERVERVERSION"
    "); raprintf(sm, off, ss, "\n"); break; } if (strlen(sm) > 0) CSEND(fd, sm); free(sm); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/harvid.c000066400000000000000000001037421215541677300152300ustar00rootroot00000000000000/* harvid -- http ardour video daemon Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include // basename #include "daemon_log.h" #include "daemon_util.h" #include "socket_server.h" #include #include "image_format.h" #include "enums.h" #include "ffcompat.h" #ifndef HAVE_WINDOWS #include // inet_addr #include // memlock #endif #ifndef DEFAULT_PORT #define DEFAULT_PORT 1554 #endif char *str_escape(const char *string, int inlength, const char esc); // defined in fileindex.c extern int debug_level; extern int debug_section; char *program_name; int want_quiet = 0; int want_verbose = 0; int cfg_daemonize = 0; int cfg_syslog = 0; int cfg_memlock = 0; int cfg_timeout = 0; int cfg_usermask = USR_INDEX; int cfg_adminmask = ADM_FLUSHCACHE; char *cfg_logfile = NULL; char *cfg_chroot = NULL; char *cfg_username = NULL; char *cfg_groupname = NULL; int initial_cache_size = 128; int max_decoder_threads = 8; unsigned short cfg_port = DEFAULT_PORT; unsigned int cfg_host = 0; /* = htonl(INADDR_ANY) */ static void printversion (void) { printf ("harvid %s\n", ICSVERSION); printf ("Compiled with %s %s %s\n\n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT); printf ("Copyright (C) GPL 2002-2013 Robin Gareus \n"); } static void usage (int status) { printf ("%s - http ardour video server\n\n", basename(program_name)); printf ("Usage: %s [OPTION] [document-root]\n", program_name); printf ("\n" "Options:\n" " -A , --admin \n" " space separated list of allowed admin commands.\n" " An exclamation-mark before a command disables it.\n" " default: 'flush_cache';\n" " available: flush_cache, purge_cache, shutdown\n" " -c , --chroot \n" " change system root - jails server to this path\n" " -C set initial frame-cache size (default: 128)\n" " -D, --daemonize fork into background and detach from TTY\n" " -g , --groupname \n" " assume this user-group\n" " -h, --help display this help and exit\n" " -F , --features \n" " space separated list of optional features.\n" " An exclamation-mark before a features disables it.\n" " default: 'index';\n" " available: index, seek, flatindex, keepraw\n" " -l , --logfile \n" " specify file for log messages\n" " -M, --memlock attempt to lock memory (prevent cache paging)\n" " -p , --port TCP port to listen on (default %i)\n" " -P IP address to listen on (default 0.0.0.0)\n" " -q, --quiet, --silent inhibit usual output (may be used thrice)\n" " -s, --syslog send messages to syslog\n" " -t set maximum decoder-threads (default: 8)\n" " -T , --timeout \n" " set a timeout after which the server will\n" " terminate if no new request arrives\n" " -u , --username \n" " server will act as this user\n" " -v, --verbose print more information (may be used twice)\n" " -V, --version print version information and exit\n" "\n" "The default document-root (if unspecified) is the system root: / or C:\\.\n" "\n" "If both syslog and logfile are given that last specified option will be used.\n" "\n" "--verbose and --quiet are additive. The default is to print warnings\n" "and above only. Available log-levels are 'mute', 'critical, 'error',\n" "'warning' and 'info'.\n" "\n" "The --features option allows to enable and disable http-handlers /seek and\n" "/index as well as tweak related behaviour. The 'flatindex' option concerns\n" "/index&flatindex=1 which recursively indexes all video file. It is disabled\n" "by defaults since a recursive search of the default docroot / can takes a\n" "very long time.\n" "When requesting png or jpeg images harvid decodes a raw RGB frame and then\n" "encodes it again. If 'keepraw' feature is enabled, both the raw RGB and\n" "encoded image are kept in cache. The default is to invaldate the RGB frame\n" "after encoding the image.\n" "\n" "Examples:\n" "harvid -A '!flush_cache purge_cache shutdown' -C 256 /tmp/\n" "\n" "sudo harvid -c /tmp/ -u nobody -g nogroup /\n" "\n" "Report bugs to or https://github.com/x42/harvid/issues\n" "Website http://x42.github.com/harvid/\n" , DEFAULT_PORT ); exit (status); } static struct option const long_options[] = { {"admin", required_argument, 0, 'A'}, {"chroot", required_argument, 0, 'c'}, {"cache-size", required_argument, 0, 'C'}, {"debug", required_argument, 0, 'd'}, {"daemonize", no_argument, 0, 'D'}, {"groupname", required_argument, 0, 'g'}, {"help", no_argument, 0, 'h'}, {"features", required_argument, 0, 'F'}, {"logfile", required_argument, 0, 'l'}, {"memlock", no_argument, 0, 'M'}, {"port", required_argument, 0, 'p'}, {"listenip", required_argument, 0, 'P'}, {"quiet", no_argument, 0, 'q'}, {"silent", no_argument, 0, 'q'}, {"syslog", no_argument, 0, 's'}, {"timeout", required_argument, 0, 'T'}, {"username", required_argument, 0, 'u'}, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, {NULL, 0, NULL, 0} }; /* Set all the option flags according to the switches specified. Return the index of the first non-option argument. */ static int decode_switches (int argc, char **argv) { int c; while ((c = getopt_long (argc, argv, "A:" /* admin */ "c:" /* chroot-dir */ "C:" /* initial cache size */ "d:" /* debug */ "D" /* daemonize */ "g:" /* setGroup */ "h" /* help */ "F:" /* interaction */ "l:" /* logfile */ "M" /* memlock */ "p:" /* port */ "P:" /* IP */ "q" /* quiet or silent */ "s" /* syslog */ "t:" /* threads */ "T:" /* timeout */ "u:" /* setUser */ "v" /* verbose */ "V", /* version */ long_options, (int *) 0)) != EOF) { switch (c) { case 'q': /* --quiet, --silent */ want_quiet = 1; want_verbose = 0; if (debug_level == DLOG_CRIT) debug_level = DLOG_EMERG; else if (debug_level == DLOG_ERR) debug_level = DLOG_CRIT; else debug_level=DLOG_ERR; break; case 'v': /* --verbose */ if (debug_level == DLOG_INFO) want_verbose = 1; debug_level=DLOG_INFO; break; case 'A': /* --admin */ if (strstr(optarg, "shutdown")) cfg_adminmask|=ADM_SHUTDOWN; if (strstr(optarg, "purge_cache")) cfg_adminmask|=ADM_PURGECACHE; if (strstr(optarg, "flush_cache")) cfg_adminmask|=ADM_FLUSHCACHE; if (strstr(optarg, "!shutdown")) cfg_adminmask&=~ADM_SHUTDOWN; if (strstr(optarg, "!purge_cache")) cfg_adminmask&=~ADM_PURGECACHE; if (strstr(optarg, "!flush_cache")) cfg_adminmask&=~ADM_FLUSHCACHE; break; case 'c': /* --chroot */ cfg_chroot = optarg; break; case 'C': initial_cache_size = atoi(optarg); if (initial_cache_size < 2 || initial_cache_size > 65535) initial_cache_size = 128; break; case 'd': /* --debug */ if (strstr(optarg, "SRV")) debug_section|=DEBUG_SRV; if (strstr(optarg, "HTTP")) debug_section|=DEBUG_HTTP; if (strstr(optarg, "CON")) debug_section|=DEBUG_CON; if (strstr(optarg, "DCTL")) debug_section|=DEBUG_DCTL; if (strstr(optarg, "ICS")) debug_section|=DEBUG_ICS; #ifdef NDEBUG fprintf(stderr, "harvid was built with NDEBUG. '-d' has no affect.\n"); #endif break; case 'D': /* --daemonize */ cfg_daemonize = 1; break; case 'F': /* --features */ if (strstr(optarg, "index")) cfg_usermask |= USR_INDEX; if (strstr(optarg, "seek")) cfg_usermask |= USR_WEBSEEK; if (strstr(optarg, "flatindex")) cfg_usermask |= USR_FLATINDEX; if (strstr(optarg, "keepraw")) cfg_usermask |= USR_KEEPRAW; if (strstr(optarg, "!index")) cfg_usermask &= ~USR_INDEX; if (strstr(optarg, "!seek")) cfg_usermask |= USR_WEBSEEK; if (strstr(optarg, "!flatindex")) cfg_usermask &= ~USR_FLATINDEX; if (strstr(optarg, "!keepraw")) cfg_usermask &= ~USR_KEEPRAW; break; case 'g': /* --group */ cfg_groupname = optarg; break; case 'l': /* --logfile */ cfg_syslog = 0; if (cfg_logfile) free(cfg_logfile); cfg_logfile = strdup(optarg); break; case 'M': /* --memlock */ cfg_memlock = 1; break; case 'P': /* --listenip */ cfg_host = inet_addr (optarg); break; case 'p': /* --port */ {int pn = atoi(optarg); if (pn > 0 && pn < 65536) cfg_port = (unsigned short) atoi(optarg); } break; case 's': /* --syslog */ cfg_syslog = 1; if (cfg_logfile) free(cfg_logfile); cfg_logfile = NULL; break; case 't': max_decoder_threads = atoi(optarg); if (max_decoder_threads < 2 || max_decoder_threads > 128) max_decoder_threads = 8; break; case 'T': /* --timeout */ cfg_timeout = atoi(optarg); break; case 'u': /* --username */ cfg_username = optarg; break; case 'V': printversion(); exit(0); case 'h': usage (0); default: usage (1); } } return optind; } // -=-=-=-=-=-=- main -=-=-=-=-=-=- #include "ffcompat.h" void *dc = NULL; // decoder control void *vc = NULL; // video frame cache void *ic = NULL; // encoded image cache int main (int argc, char **argv) { program_name = argv[0]; struct stat sb; int cfg_uid, cfg_gid; char *docroot = "/" ; debug_level = DLOG_WARNING; int exitstatus = 0; // TODO read rc file int i = decode_switches (argc, argv); if ((i+1)== argc) docroot = argv[i]; else if (docroot && i==argc) ; // use default else usage(1); // TODO read additional rc file (from options) if (cfg_daemonize && !cfg_logfile && !cfg_syslog) { dlog(DLOG_WARNING, "daemonizing without log file or syslog.\n"); } if (cfg_daemonize || cfg_syslog || cfg_logfile) { /* ffdecoder prints to stdout */ want_verbose = 0; want_quiet = 1; } /* initialize */ if (cfg_logfile || cfg_syslog) dlog_open(cfg_logfile); /* resolve before doing chroot() */ cfg_uid = resolve_uid(cfg_username); cfg_gid = resolve_gid(cfg_groupname); if (cfg_uid < 0 || cfg_gid < 0) {exitstatus = -1; goto errexit;} if (cfg_chroot) { if (do_chroot(cfg_chroot)) {exitstatus = -1; goto errexit;} } if (stat(docroot, &sb) || !S_ISDIR(sb.st_mode)) { dlog(DLOG_CRIT, "document-root is not a directory\n"); exitstatus = -1; goto errexit; } if (cfg_daemonize) { if (daemonize()) {exitstatus = -1; goto errexit;} } ff_initialize(); vcache_create(&vc); vcache_resize(&vc, initial_cache_size); icache_create(&ic); icache_resize(ic, initial_cache_size*4); dctrl_create(&dc, max_decoder_threads, initial_cache_size); if (cfg_memlock) { #ifndef HAVE_WINDOWS if (mlockall(MCL_CURRENT|MCL_FUTURE)) { dlog(LOG_WARNING, "failed to lock memory.\n"); } #else dlog(LOG_WARNING, "memory locking is not available on windows.\n"); #endif } /* all systems go */ dlog(DLOG_INFO, "Initialization complete. Starting server.\n"); exitstatus = start_tcp_server(cfg_host, cfg_port, docroot, cfg_uid, cfg_gid, cfg_timeout, NULL); /* cleanup */ ff_cleanup(); dctrl_destroy(&dc); vcache_destroy(&vc); icache_destroy(&ic); errexit: dlog_close(); return(exitstatus); } // -=-=-=-=-=-=- video server callbacks -=-=-=-=-=-=- /* * these are called from protocol_handler() in httprotocol.c */ #include "httprotocol.h" #include "ics_handler.h" #include "htmlconst.h" #define HPSIZE 4096 // max size of homepage in bytes. char *hdl_homepage_html (CONN *c) { char *msg = malloc(HPSIZE * sizeof(char)); int off = 0; off+=snprintf(msg+off, HPSIZE-off, DOCTYPE HTMLOPEN); off+=snprintf(msg+off, HPSIZE-off, "harvid\n"); off+=snprintf(msg+off, HPSIZE-off, HTMLBODY); off+=snprintf(msg+off, HPSIZE-off, CENTERDIV); off+=snprintf(msg+off, HPSIZE-off, "

    Built-in handlers

    \n"); off+=snprintf(msg+off, HPSIZE-off, "
    "); if (cfg_adminmask) off+=snprintf(msg+off, HPSIZE-off, "

    Admin Tasks:

      \n"); if (cfg_adminmask&ADM_FLUSHCACHE) off+=snprintf(msg+off, HPSIZE-off, "
    • Flush Cache
    • \n"); if (cfg_adminmask&ADM_PURGECACHE) off+=snprintf(msg+off, HPSIZE-off, "
    • Purge Cache
    • \n"); if (cfg_adminmask&ADM_SHUTDOWN) off+=snprintf(msg+off, HPSIZE-off, "
    • Server Shutdown
    • \n"); if (cfg_adminmask) off+=snprintf(msg+off, HPSIZE-off, "
    \n
    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    The default request handler decodes images and requires a ?frame=NUM&file=PATH URL query or post parameters. Video frames are counted starting at zero. Default options are w=0&h=0&format=png which serves the image pre-scaled to its effective size as png.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    The /info request handler requires a ?file=PATH query parameter and optionally takes a format (default is html). All other handlers (/status, /rc, /version, /admin/) take no arguments.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    Available query parameters: frame, w, h, file, format.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    Frame (frame-number), w (width) and h (height) are unsigned integers.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    Supported image output pixel formats:

    \n"); off+=snprintf(msg+off, HPSIZE-off, "
      \n
    • Encoded: jpg, jpeg, png, ppm
    • \n"); off+=snprintf(msg+off, HPSIZE-off, "
    • Raw RGB: rgb, bgr, rgba, argb, bgra
    • \n"); off+=snprintf(msg+off, HPSIZE-off, "
    • Raw YUV: yuv, yuv420, yuv440, yuv422, uyv422
    • \n
    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    Available info output formats:

    \n"); off+=snprintf(msg+off, HPSIZE-off, "
      \n
    • Human Readable: html, xhtml
    • \n"); off+=snprintf(msg+off, HPSIZE-off, "
    • Machine Readable: json, csv, plain
    • \n
    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    The jpg (and jpeg) format parameter can be postfixed number to specify the jpeg quality. e.g. &format=jpeg90. The default is 75. Note that 'jpg' is just an alias for 'jpeg', and 'html' is an alias for 'xhtml'.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    If either only width or height is specified with a value greater than 15, the other is calculated according to the movie's effective aspect-ratio. However the minimum size is 16x16, requesting geometries smaller than 16x16 will return the image in its original size.

    \n"); off+=snprintf(msg+off, HPSIZE-off, "

    harvid @ GitHub

    \n"); off+=snprintf(msg+off, HPSIZE-off, "\n"); off+=snprintf(msg+off, HPSIZE-off, HTMLFOOTER, c->d->local_addr, c->d->local_port); off+=snprintf(msg+off, HPSIZE-off, "\n\n"); return msg; } char *hdl_server_status_html (CONN *c) { size_t ss = 1024; size_t off = 0; char *sm = malloc(ss * sizeof(char)); raprintf(sm, off, ss, DOCTYPE HTMLOPEN); raprintf(sm, off, ss, "harvid status\n"); raprintf(sm, off, ss, "\n"); raprintf(sm, off, ss, "\n"); raprintf(sm, off, ss, HTMLBODY); raprintf(sm, off, ss, "

    harvid status

    \n"); raprintf(sm, off, ss, "\n"); // ardour3 reads this raprintf(sm, off, ss, "

    Concurrent connections: (current / max-seen / limit) %d / %d / %d

    \n", c->d->num_clients, c->d->max_clients, MAXCONNECTIONS); #ifdef USAGE_FREQUENCY_STATISTICS time_t i; const time_t n = time(NULL); double avg1, avg5, avgA; avg1 = avg5 = avgA = 0.0; for (i = 0; i < FREQ_LEN; ++i) { avgA += c->d->req_stats[i]; } for (i = 0; i < 60; ++i) { avg1 += c->d->req_stats[(n-i)%FREQ_LEN]; } for (i = 0; i < 300; ++i) { avg5 += c->d->req_stats[(n-i)%FREQ_LEN]; } avgA /= (double) FREQ_LEN; avg1 /= 60.0; avg5 /= 300.0; long int uptime = (long int) (n - c->d->stat_start); raprintf(sm, off, ss, "

    Requests/sec: (1min avg / 5min avg / 1h avg / all time) %.2f / %.2f / %.2f / %.3f

    \n", avg1, avg5, avgA, c->d->stat_count / difftime(n, c->d->stat_start)); raprintf(sm, off, ss, "

    Total requests: %d, uptime: %ld day%s, %02ld:%02ld:%02ld

    \n", c->d->stat_count, uptime / 86400, (uptime / 86400) == 1 ? "": "s", (uptime % 86400) / 3600, (uptime % 3600) / 60, uptime %60); #endif dctrl_info_html(dc, &sm, &off, &ss, 2); vcache_info_html(vc, &sm, &off, &ss, 0); icache_info_html(ic, &sm, &off, &ss, 2); raprintf(sm, off, ss, HTMLFOOTER, c->d->local_addr, c->d->local_port); raprintf(sm, off, ss, "\n"); return (sm); } #define NFOSIZ (256) static char *file_info_json (CONN *c, ics_request_args *a, VInfo *ji) { char *im = malloc(NFOSIZ * sizeof(char)); int off = 0; off+=snprintf(im+off, NFOSIZ-off, "{"); //off+=snprintf(im+off, NFOSIZ-off, "\"geometry\":[%i,%i],", ji->movie_width, ji->movie_height); off+=snprintf(im+off, NFOSIZ-off, "\"width\":%i", ji->movie_width); off+=snprintf(im+off, NFOSIZ-off, ",\"height\":%i", ji->movie_height); off+=snprintf(im+off, NFOSIZ-off, ",\"aspect\":%.3f", ji->movie_aspect); off+=snprintf(im+off, NFOSIZ-off, ",\"framerate\":%.3f", timecode_rate_to_double(&ji->framerate)); off+=snprintf(im+off, NFOSIZ-off, ",\"duration\":%"PRId64, ji->frames); off+=snprintf(im+off, NFOSIZ-off, "}"); jvi_free(ji); return (im); } static char *file_info_csv (CONN *c, ics_request_args *a, VInfo *ji) { char *im = malloc(NFOSIZ * sizeof(char)); int off = 0; off+=snprintf(im+off, NFOSIZ-off, "1"); // FORMAT VERSION off+=snprintf(im+off, NFOSIZ-off, ",%i", ji->movie_width); off+=snprintf(im+off, NFOSIZ-off, ",%i", ji->movie_height); off+=snprintf(im+off, NFOSIZ-off, ",%f", ji->movie_aspect); off+=snprintf(im+off, NFOSIZ-off, ",%.3f", timecode_rate_to_double(&ji->framerate)); off+=snprintf(im+off, NFOSIZ-off, ",%"PRId64, ji->frames); off+=snprintf(im+off, NFOSIZ-off, "\n"); jvi_free(ji); return (im); } #define FIHSIZ 8192 static char *file_info_html (CONN *c, ics_request_args *a, VInfo *ji) { char *im = malloc(FIHSIZ * sizeof(char)); int off = 0; char smpte[14], *tmp; timecode_framenumber_to_string(smpte, &ji->framerate, ji->frames); off+=snprintf(im+off, FIHSIZ-off, DOCTYPE HTMLOPEN); off+=snprintf(im+off, FIHSIZ-off, "harvid file info\n"); off+=snprintf(im+off, FIHSIZ-off, HTMLBODY); off+=snprintf(im+off, FIHSIZ-off, CENTERDIV); off+=snprintf(im+off, FIHSIZ-off, "

    File info

    \n\n"); tmp = url_escape(a->file_qurl, 0); if (cfg_usermask & USR_WEBSEEK) { off+=snprintf(im+off, FIHSIZ-off, "

    File: %s

      \n", tmp, a->file_qurl); free(tmp); } else { off+=snprintf(im+off, FIHSIZ-off, "

      File: %s

        \n", tmp, a->file_qurl); free(tmp); } off+=snprintf(im+off, FIHSIZ-off, "
      • Geometry: %ix%i
      • \n", ji->movie_width, ji->movie_height); off+=snprintf(im+off, FIHSIZ-off, "
      • Aspect-Ratio: %.3f
      • \n", ji->movie_aspect); off+=snprintf(im+off, FIHSIZ-off, "
      • Framerate: %.2f
      • \n", timecode_rate_to_double(&ji->framerate)); off+=snprintf(im+off, FIHSIZ-off, "
      • Duration: %s
      • \n", smpte); off+=snprintf(im+off, FIHSIZ-off, "
      • Duration: %.2f sec
      • \n", (double)ji->frames/timecode_rate_to_double(&ji->framerate)); off+=snprintf(im+off, FIHSIZ-off, "
      • Duration: %"PRId64" frames
      • \n", ji->frames); off+=snprintf(im+off, FIHSIZ-off, "
      \n\n"); off+=snprintf(im+off, FIHSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port); off+=snprintf(im+off, FIHSIZ-off, "\n"); jvi_free(ji); return (im); } static char *file_info_raw (CONN *c, ics_request_args *a, VInfo *ji) { char *im = malloc(NFOSIZ * sizeof(char)); int off = 0; char smpte[14]; timecode_framenumber_to_string(smpte, &ji->framerate, ji->frames); off+=snprintf(im+off, NFOSIZ-off, "1\n"); // FORMAT VERSION off+=snprintf(im+off, NFOSIZ-off, "%.3f\n", timecode_rate_to_double(&ji->framerate)); // fps off+=snprintf(im+off, NFOSIZ-off, "%"PRId64"\n", ji->frames); // duration off+=snprintf(im+off, NFOSIZ-off, "0.0\n"); // start-offset TODO off+=snprintf(im+off, NFOSIZ-off, "%f\n", ji->movie_aspect); jvi_free(ji); return (im); } char *hdl_file_info (CONN *c, ics_request_args *a) { VInfo ji; unsigned short vid; int err = 0; vid = dctrl_get_id(vc, dc, a->file_name); jvi_init(&ji); if ((err=dctrl_get_info(dc, vid, &ji))) { if (err == 503) { httperror(c->fd, 503, "Service Temporarily Unavailable", "

      No decoder is available. The server is currently busy or overloaded.

      "); } else { httperror(c->fd, 500, "Service Unavailable", "

      No decoder is available: File is invalid (no video track, unknown codec, invalid geometry,..)

      "); } return NULL; } switch (a->render_fmt) { case OUT_PLAIN: return file_info_raw(c, a, &ji); case OUT_JSON: return file_info_json(c, a, &ji); case OUT_CSV: return file_info_csv(c, a, &ji); default: return file_info_html(c, a, &ji); } } ///////////// #define SINFOSIZ 2048 char *hdl_server_info (CONN *c, ics_request_args *a) { char *info = malloc(SINFOSIZ * sizeof(char)); int off = 0; switch (a->render_fmt) { case OUT_PLAIN: off+=snprintf(info+off, SINFOSIZ-off, "%s\n", c->d->docroot); off+=snprintf(info+off, SINFOSIZ-off, "%s\n", c->d->local_addr); off+=snprintf(info+off, SINFOSIZ-off, "%d\n", c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, "%d\n", initial_cache_size); break; case OUT_JSON: { char *tmp; off+=snprintf(info+off, SINFOSIZ-off, "{"); tmp = str_escape(c->d->docroot, 0, '\\'); off+=snprintf(info+off, SINFOSIZ-off, "\"docroot\":\"%s\"", tmp); free(tmp); off+=snprintf(info+off, SINFOSIZ-off, ",\"listenaddr\":\"%s\"", c->d->local_addr); off+=snprintf(info+off, SINFOSIZ-off, ",\"listenport\":%d", c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, ",\"cachesize\":%d", initial_cache_size); off+=snprintf(info+off, SINFOSIZ-off, ",\"infohandlers\":[\"/info\", \"/rc\", \"/status\", \"/version\"%s\"", cfg_usermask & USR_INDEX ? ",\"index\"":""); off+=snprintf(info+off, SINFOSIZ-off, ",\"admintasks\":[\"/check\"%s%s%s]", (cfg_adminmask & ADM_FLUSHCACHE) ? ",\"/flush_cache\"" : "", (cfg_adminmask & ADM_PURGECACHE) ? ",\"/purge_cache\"" : "", (cfg_adminmask & ADM_SHUTDOWN) ? ",\"/shutdown\"" : "" ); off+=snprintf(info+off, SINFOSIZ-off, "}"); } break; case OUT_CSV: { char *tmp = str_escape(c->d->docroot, 0, '"'); off+=snprintf(info+off, SINFOSIZ-off, "\"%s\"", tmp); free(tmp); off+=snprintf(info+off, SINFOSIZ-off, ",%s", c->d->local_addr); off+=snprintf(info+off, SINFOSIZ-off, ",%d", c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, ",%d", initial_cache_size); off+=snprintf(info+off, SINFOSIZ-off, ",\"/info /rc /status /version%s\"", cfg_usermask & USR_INDEX ? " index":""); off+=snprintf(info+off, SINFOSIZ-off, ",\"/check%s%s%s\"", (cfg_adminmask & ADM_FLUSHCACHE) ? " /flush_cache" : "", (cfg_adminmask & ADM_PURGECACHE) ? " /purge_cache" : "", (cfg_adminmask & ADM_SHUTDOWN) ? " /shutdown" : "" ); off+=snprintf(info+off, SINFOSIZ-off, "\n"); } break; default: // HTML off+=snprintf(info+off, SINFOSIZ-off, DOCTYPE HTMLOPEN); off+=snprintf(info+off, SINFOSIZ-off, "harvid server info\n"); off+=snprintf(info+off, SINFOSIZ-off, HTMLBODY); off+=snprintf(info+off, SINFOSIZ-off, CENTERDIV); off+=snprintf(info+off, SINFOSIZ-off, "

      harvid server info

      \n\n"); off+=snprintf(info+off, SINFOSIZ-off, "
        \n"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Docroot: %s
      • \n", c->d->docroot); off+=snprintf(info+off, SINFOSIZ-off, "
      • ListenAddr: %s
      • \n", c->d->local_addr); off+=snprintf(info+off, SINFOSIZ-off, "
      • ListenPort: %d
      • \n", c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, "
      • CacheSize: %d
      • \n", initial_cache_size); off+=snprintf(info+off, SINFOSIZ-off, "
      • File Index: %s
      • \n", cfg_usermask & USR_INDEX ? "Yes" : "No"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Admin-task(s): /check%s%s%s
      • \n", (cfg_adminmask & ADM_FLUSHCACHE) ? " /flush_cache" : "", (cfg_adminmask & ADM_PURGECACHE) ? " /purge_cache" : "", (cfg_adminmask & ADM_SHUTDOWN) ? " /shutdown" : "" ); #ifndef NDEBUG // possibly sensitive information off+=snprintf(info+off, SINFOSIZ-off, "
      • Memlock: %s
      • \n", cfg_memlock ? "Yes" : "No"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Daemonized: %s
      • \n", cfg_daemonize ? "Yes" : "No"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Chroot: %s
      • \n", cfg_chroot ? cfg_chroot : "-"); off+=snprintf(info+off, SINFOSIZ-off, "
      • SetUid/Gid: %s/%s
      • \n", cfg_username ? cfg_username : "-", cfg_groupname ? cfg_groupname : "-"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Log: %s
      • \n", cfg_syslog ? "(syslog)" : (cfg_logfile ? cfg_logfile : "(stdout)")); off+=snprintf(info+off, SINFOSIZ-off, "
      • Loglevel: %s
      • \n", dlog_level_name(debug_level)); off+=snprintf(info+off, SINFOSIZ-off, "
      • AVLog (stdout): %s
      • \n", want_quiet ? "quiet" : want_verbose ? "verbose" :"error"); #endif off+=snprintf(info+off, SINFOSIZ-off, "
      \n\n"); off+=snprintf(info+off, SINFOSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, "\n"); break; } return info; } char *hdl_server_version (CONN *c, ics_request_args *a) { char *info = malloc(SINFOSIZ * sizeof(char)); int off = 0; switch (a->render_fmt) { case OUT_PLAIN: off+=snprintf(info+off, SINFOSIZ-off, "%s\n", SERVERVERSION); off+=snprintf(info+off, SINFOSIZ-off, "%s %s %s\n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT); break; case OUT_JSON: off+=snprintf(info+off, SINFOSIZ-off, "{"); off+=snprintf(info+off, SINFOSIZ-off, "\"version\":\"%s\"", ICSVERSION); off+=snprintf(info+off, SINFOSIZ-off, ",\"os\":\"%s\"", ICSARCH); #ifdef NDEBUG off+=snprintf(info+off, SINFOSIZ-off, ",\"debug\":false"); #else off+=snprintf(info+off, SINFOSIZ-off, ",\"debug\":true"); #endif off+=snprintf(info+off, SINFOSIZ-off, ",\"ffmpeg\":[\"%s\",\"%s\",\"%s\"]", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT); off+=snprintf(info+off, SINFOSIZ-off, "}"); break; case OUT_CSV: off+=snprintf(info+off, SINFOSIZ-off, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", ICSVERSION, SERVERVERSION, LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT); break; default: // HTML off+=snprintf(info+off, SINFOSIZ-off, DOCTYPE HTMLOPEN); off+=snprintf(info+off, SINFOSIZ-off, "harvid server version\n"); off+=snprintf(info+off, SINFOSIZ-off, HTMLBODY); off+=snprintf(info+off, SINFOSIZ-off, CENTERDIV); off+=snprintf(info+off, SINFOSIZ-off, "

      harvid server version

      \n\n"); off+=snprintf(info+off, SINFOSIZ-off, "
        \n"); off+=snprintf(info+off, SINFOSIZ-off, "
      • Version: %s
      • \n", ICSVERSION); off+=snprintf(info+off, SINFOSIZ-off, "
      • Operating System: %s
      • \n", ICSARCH); #ifdef NDEBUG off+=snprintf(info+off, SINFOSIZ-off, "
      • Debug enabled: No
      • \n"); #else off+=snprintf(info+off, SINFOSIZ-off, "
      • Debug enabled: Yes
      • \n"); #endif off+=snprintf(info+off, SINFOSIZ-off, "
      • ffmpeg: %s %s %s
      • \n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT); off+=snprintf(info+off, SINFOSIZ-off, "\n
      \n\n"); off+=snprintf(info+off, SINFOSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port); off+=snprintf(info+off, SINFOSIZ-off, "\n"); break; } return info; } ///////////// int hdl_decode_frame(int fd, httpheader *h, ics_request_args *a) { VInfo ji; unsigned short vid; void *cptr = NULL; uint8_t *optr = NULL; size_t olen = 0; uint8_t *bptr = NULL; int err = 0; vid = dctrl_get_id(vc, dc, a->file_name); jvi_init(&ji); if (a->frame < 0) a->frame = 0; // return error instead? if (a->out_width < 0 || a->out_width > 16384) a->out_width = 0; if (a->out_height < 0 || a->out_height > 16384) a->out_height = 0; /* get canonical output width/height and corresponding buffersize */ if ((err=dctrl_get_info_scale(dc, vid, &ji, a->out_width, a->out_height, a->decode_fmt)) || ji.buffersize < 1) { if (err == 503) { dlog(DLOG_WARNING, "VID: no decoder available (server overload).\n", fd); httperror(fd, 503, "Service Temporarily Unavailable", "

      No decoder is available. The server is currently busy or overloaded.

      "); } else { dlog(DLOG_WARNING, "VID: no decoder available (invalid file or unsupported codec).\n", fd); httperror(fd, 500, "Service Unavailable", "

      No decoder is available: File is invalid (no video track, unknown codec, invalid geometry,..)

      "); } return 0; } /* try encoded cache if a->render_fmt != FMT_RAW */ if (a->render_fmt != FMT_RAW) { optr = icache_get_buffer(ic, vid, a->frame, a->render_fmt, a->misc_int, ji.out_width, ji.out_height, &olen, &cptr); } if (olen == 0) { /* get frame from cache - or decode it into the cache */ bptr = vcache_get_buffer(vc, dc, vid, a->frame, ji.out_width, ji.out_height, a->decode_fmt, &cptr, &err); if (!bptr) { dlog(DLOG_ERR, "VID: error decoding video file for fd:%d err:%d\n", fd, err); if (err == 503) { httperror(fd, 503, "Service Temporarily Unavailable", "

      Video cache is unavailable. The server is currently busy or overloaded.

      "); } else { httperror(fd, 500, "Service Unavailable", "

      No decoder or cache is available: File is invalid (no video track, unknown codec, invalid geometry,..)

      "); } return 0; } switch (a->render_fmt) { case FMT_RAW: olen = ji.buffersize; optr = bptr; break; default: olen = format_image(&optr, a->render_fmt, a->misc_int, &ji, bptr); break; } } if(olen > 0 && optr) { debugmsg(DEBUG_ICS, "VID: sending %li bytes to fd:%d.\n", (long int) olen, fd); switch (a->render_fmt) { case FMT_RAW: h->ctype = "image/raw"; break; case FMT_JPG: h->ctype = "image/jpeg"; break; case FMT_PNG: h->ctype = "image/png"; break; case FMT_PPM: h->ctype = "image/ppm"; break; default: h->ctype = "image/unknown"; } http_tx(fd, 200, h, olen, optr); if (bptr && a->render_fmt != FMT_RAW) { /* image was read from raw frame cache end encoded just now */ if (icache_add_buffer(ic, vid, a->frame, a->render_fmt, a->misc_int, ji.out_width, ji.out_height, optr, olen)) { /* image was not added to image cache -> unreference the buffer */ free(optr); } else if (! (cfg_usermask & USR_KEEPRAW)) { /* delete raw frame when encoded frame was cached */ vcache_invalidate_buffer(vc, cptr); } } } else { dlog(DLOG_ERR, "VID: error formatting image for fd:%d\n", fd); httperror(fd, 500, NULL, NULL); } if (bptr) vcache_release_buffer(vc, cptr); else icache_release_buffer(ic, cptr); jvi_free(&ji); return (0); } void hdl_clear_cache() { vcache_clear(vc, -1); icache_clear(ic); } void hdl_purge_cache() { vcache_clear(vc, -1); icache_clear(ic); dctrl_cache_clear(vc, dc, 2, -1); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/htmlconst.h000066400000000000000000000016001215541677300157610ustar00rootroot00000000000000 #ifdef NDEBUG #define SERVERVERSION "harvid " ICSVERSION " ["ICSARCH"]" #else #define SERVERVERSION "harvid " ICSVERSION " ["ICSARCH" debug]" #endif #define HTMLBODYPERC(P) \ "" \ "
      \"Harvid\"
      \n" #define HTMLBODY HTMLBODYPERC("%") #define CENTERDIV \ "
      \n" #define HTMLFOOTER \ "
      "SERVERVERSION" at %s:%i
      " #define ERRFOOTER \ "
      "SERVERVERSION"
      \n" #define OK200MSG(TXT) \ DOCTYPE HTMLOPEN "harvid admin" HTMLBODYPERC("") "

      OK. " TXT " command successful

      " ERRFOOTER harvid-0.7.3/src/htmlseek.c000066400000000000000000000102121215541677300155540ustar00rootroot00000000000000#include #include //#include #include "socket_server.h" #include "httprotocol.h" #include "ics_handler.h" #include "htmlconst.h" extern void *dc; // decoder control extern void *vc; // video cache #define FIHSIZ 4096 char *hdl_file_seek (CONN *c, ics_request_args *a) { VInfo ji; unsigned short vid; int err = 0; vid = dctrl_get_id(vc, dc, a->file_name); jvi_init(&ji); if ((err=dctrl_get_info(dc, vid, &ji))) { if (err == 503) { httperror(c->fd, 503, "Service Temporarily Unavailable", "

      No decoder is available. The server is currently busy or overloaded.

      "); } else { httperror(c->fd, 500, "Service Unavailable", "

      No decoder is available: File is invalid (no video track, unknown codec, invalid geometry,..)

      "); } return NULL; } char *im = malloc(FIHSIZ * sizeof(char)); int off = 0; char smpte[14]; timecode_framenumber_to_string(smpte, &ji.framerate, ji.frames); off+=snprintf(im+off, FIHSIZ-off, DOCTYPE HTMLOPEN); off+=snprintf(im+off, FIHSIZ-off, "harvid file info\n"); off+=snprintf(im+off, FIHSIZ-off, "\n" \ "", timecode_rate_to_double(&ji.framerate), ji.frames - 1, a->file_qurl); off+=snprintf(im+off, FIHSIZ-off, "\n"); off+=snprintf(im+off, FIHSIZ-off, HTMLBODY); off+=snprintf(im+off, FIHSIZ-off, "
      \n"); off+=snprintf(im+off, FIHSIZ-off, "

      "); off+=snprintf(im+off, FIHSIZ-off, "File: %s
      \n", a->file_qurl); off+=snprintf(im+off, FIHSIZ-off, "Geometry: %ix%i, \n", ji.movie_width, ji.movie_height); off+=snprintf(im+off, FIHSIZ-off, "Aspect-Ratio: %.3f, \n", ji.movie_aspect); off+=snprintf(im+off, FIHSIZ-off, "Framerate: %.2f, \n", timecode_rate_to_double(&ji.framerate)); off+=snprintf(im+off, FIHSIZ-off, "Duration: %s\n", smpte); off+=snprintf(im+off, FIHSIZ-off, "

      \n"); off+=snprintf(im+off, FIHSIZ-off, "
      \"\"
      \n" \ "
      \n" \ "
      \n" \ "
      \n" \ "
      \n" \ "
      \n" \ "\n" \ "
      \n" \ "
      \n" \ " \n" \ "
      \n" \ "
      \n" \ " \n" \ "
      \n" \ "
      \n" \ "
      \n" \ "
      \n" \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "
      00:00:00.00
      \n" \ "\n" \ "
      \n" \ "\n"); off+=snprintf(im+off, FIHSIZ-off, "\n", ji.frames/3, ji.frames/3, a->file_qurl, ji.frames/3); off+=snprintf(im+off, FIHSIZ-off, "
      \n"); off+=snprintf(im+off, FIHSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port); off+=snprintf(im+off, FIHSIZ-off, "\n"); jvi_free(&ji); return (im); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/httprotocol.c000066400000000000000000000352531215541677300163350ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2002,2003,2008-2013 Robin Gareus This file contains GPL code from mini-http, micro-http and libcurl. by Jef Poskanzer and Daniel Stenberg, . 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "socket_server.h" #include "httprotocol.h" #include "htmlconst.h" #include "ics_handler.h" /* -=-=-=-=-=-=-=-=-=-=- HTTP helper functions */ const char * send_http_status_fd (int fd, int status) { char http_head[128]; const char *title; switch (status) { case 200: title = "OK"; break; //case 302: title = "Found"; break; //case 304: title = "Not Modified"; break; case 400: title = "Bad Request"; break; //case 401: title = "Unauthorized"; break; case 403: title = "Forbidden"; break; case 404: title = "Not Found"; break; case 415: title = "Unsupported Media Type"; break; //case 408: title = "Request Timeout"; break; case 500: title = "Internal Server Error"; break; case 501: title = "Not Implemented"; break; case 503: title = "Service Temporarily Unavailable"; break; default: title = "Internal Server Error"; status = 500; break; } snprintf(http_head, sizeof(http_head), "%s %d %s\015\012", PROTOCOL, status, title); CSEND(fd, http_head); return title; } #define HTHSIZE (1024) void send_http_header_fd(int fd , int s, httpheader *h) { char hd[HTHSIZE]; int off = 0; time_t now; char timebuf[100]; now = time(NULL); strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now)); off += snprintf(hd+off, HTHSIZE-off, "Date: %s\n", timebuf); off += snprintf(hd+off, HTHSIZE-off, "Server: %s\r\n", SERVERVERSION); if (h && h->ctype) off += snprintf(hd+off, HTHSIZE-off, "Content-type: %s\r\n", h->ctype); else off += snprintf(hd+off, HTHSIZE-off, "Content-type: text/html; charset=UTF-8\r\n"); if (h && h->encoding) off += snprintf(hd+off, HTHSIZE-off, "Content-Encoding: %s\r\n", h->encoding); if (h && h->extra) off += snprintf(hd+off, HTHSIZE-off, "%s\r\n", h->extra); if (h && h->length > 0) #ifdef HAVE_WINDOWS off += snprintf(hd+off, HTHSIZE-off, "Content-Length:%lu\r\n", (unsigned long) h->length); #else off += snprintf(hd+off, HTHSIZE-off, "Content-Length:%zu\r\n", h->length); #endif if (h && h->retryafter) off += snprintf(hd+off, HTHSIZE-off, "Retry-After:%s\r\n", h->retryafter); else if (s == 503) off += snprintf(hd+off, HTHSIZE-off, "Retry-After:5\r\n"); if (h && h->mtime) { strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&h->mtime)); off += snprintf(hd+off, HTHSIZE-off, "Last-Modified: %s\r\n", timebuf); } off += snprintf(hd+off, HTHSIZE-off, "Connection: close\r\n"); off += snprintf(hd+off, HTHSIZE-off, "\r\n"); CSEND(fd, hd); } void httperror(int fd , int s, const char *title, const char *str) { char hd[HTHSIZE]; int off = 0; const char *t = send_http_status_fd(fd, s); send_http_header_fd(fd, s, NULL); if (!title) title = t; off += snprintf(hd+off, HTHSIZE-off, DOCTYPE HTMLOPEN); off += snprintf(hd+off, HTHSIZE-off, "Error %i %s", s, title); off += snprintf(hd+off, HTHSIZE-off, "

      %s

      ", title); if (str && strlen(str)>0) { off += snprintf(hd+off, HTHSIZE-off, "

      %s

      \r\n", str); } else { off += snprintf(hd+off, HTHSIZE-off, "

      %s

      \r\n", "Sorry."); } off += snprintf(hd+off, HTHSIZE-off, ERRFOOTER); CSEND(fd, hd); } int http_tx(int fd, int s, httpheader *h, size_t len, const uint8_t *buf) { h->length = len; send_http_status_fd(fd, s); send_http_header_fd(fd, s, h); // select #define WRITE_TIMEOUT (50) // TODO make configurable int timeout = WRITE_TIMEOUT; size_t offset = 0; while (timeout > 0) { fd_set rd_set, wr_set; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 200000; FD_ZERO(&rd_set); FD_ZERO(&wr_set); FD_SET(fd, &wr_set); int ready = select(fd+1, &rd_set, &wr_set, NULL, &tv); if(ready < 0) return (-1); // error if(!ready) timeout--; else { #ifndef HAVE_WINDOWS int rv = write(fd, buf+offset, len-offset); debugmsg(DEBUG_HTTP, " written (%d/%zu) @%zu on fd:%i\n", rv, len-offset, offset, fd); #else int rv = send(fd, (const char*) (buf+offset), (size_t) (len-offset), 0); debugmsg(DEBUG_HTTP, " written (%d/%lu) @%lu on fd:%i\n", rv, (unsigned long)(len-offset), (unsigned long) offset, fd); #endif if (rv < 0) { dlog(DLOG_WARNING, "HTTP: write to socket failed: %s\n", strerror(errno)); break; // TODO: don't break on EAGAIN, ENOBUFS, ENOMEM or similar } else if (rv != len-offset) { dlog(DLOG_WARNING, "HTTP: short-write (%u/%u) @%u on fd:%i\n", rv, len-offset, offset, fd); timeout = WRITE_TIMEOUT; offset += rv; } else { offset += rv; break; } } } if (!timeout) dlog(DLOG_ERR, "HTTP: write timeout fd:%i\n", fd); if (offset != len) { dlog(DLOG_WARNING, "HTTP: write to fd:%d failed at (%u/%u) = %.2f%%\n", fd, offset, len, (float)offset*100.0/(float)len); return (1); } return (0); } // from libcurl - thanks to GPL and Daniel Stenberg char *url_escape(const char *string, int inlength) { if (!string) return strdup(""); size_t alloc = (inlength?(size_t)inlength:strlen(string))+1; char *ns; char *testing_ptr = NULL; unsigned char in; /* we need to treat the characters unsigned */ size_t newlen = alloc; int strindex = 0; size_t length; ns = malloc(alloc); if(!ns) return NULL; length = alloc-1; while(length--) { in = *string; switch (in) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': ns[strindex++] = in; break; default: newlen += 2; /* the size grows with two, since this'll become a %XX */ if(newlen > alloc) { alloc *= 2; testing_ptr = realloc(ns, alloc); if(!testing_ptr) { free(ns); return NULL; } else { ns = testing_ptr; } } snprintf(&ns[strindex], 4, "%%%02X", in); strindex += 3; break; } string++; } ns[strindex] = 0; /* terminate it */ return ns; } #include #define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x))) // also adapted from libcurl char *url_unescape(const char *string, int length, int *olen) { if (!string) return strdup(""); int alloc = (length?length:(int)strlen(string))+1; char *ns = malloc(alloc); unsigned char in; int strindex = 0; long hex; if(!ns) return NULL; while(--alloc > 0) { in = *string; if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { /* this is two hexadecimal digits following a '%' */ char hexstr[3]; char *ptr; hexstr[0] = string[1]; hexstr[1] = string[2]; hexstr[2] = 0; hex = strtol(hexstr, &ptr, 16); in = (unsigned char)hex; /* this long is never bigger than 255 anyway */ string += 2; alloc -= 2; } ns[strindex++] = in; string++; } ns[strindex] = 0; /* terminate it */ /* store output size */ if(olen) *olen = strindex; return ns; } /* -=-=-=-=-=-=-=-=-=-=- protocol handler implementation */ /* * HTTP protocol handler implements virtual * int protocol_error(int fd, int status); */ void protocol_error(int fd, int status, char *msg) { httperror(fd, status, "Error", msg?msg:"Unspecified Error."); } void protocol_response(int fd, char *msg) { send_http_status_fd(fd, 200); \ send_http_header_fd(fd, 200, NULL); \ CSEND(fd, msg); } /* parse HTTP protocol header */ static char *get_next_line(char **str) { char *t = *str; char *xx = strpbrk(t, "\n\r"); if (!xx) return (NULL); *xx++ = '\0'; while (xx && (*xx == '\r' || *xx == '\n')) xx++; *str = xx; return t; } /* check accept for image/png[;..] */ static int compare_accept(char *line) { int rv = 0; char *tmp; if ((tmp = strchr(line, ';'))) *tmp = '\0'; // ignore opt. parameters if (!strncmp(line, "image/", 6)) { rv |= 1; debugmsg(DEBUG_HTTP, "HTTP: accept image: %s\n", line); } else if (!strcmp(line, "*/*")) { rv |= 2; debugmsg(DEBUG_HTTP, "HTTP: accept all: %s\n", line); } if (tmp) *tmp = ';'; return rv; } /* * HTTP protocol handler implements virtual * int protocol_handler(fd_set rd_set, CONN *c); * for: HTTP & ics-query */ int protocol_handler(CONN *c, void *unused) { #ifndef HAVE_WINDOWS int num = read(c->fd, c->buf, BUFSIZ); #else int num = recv(c->fd, c->buf, BUFSIZ, 0); #endif if (num < 0 && (errno == EINTR || errno == EAGAIN)) return(0); if (num < 0) return(-1); if (num == 0) return(-1); // end of input c->buf[num] = '\0'; #if 0 // non HTTP commands - security issue if (!strncmp(c->buf, "quit", 4)) {c->run = 0; return(0);} else if (!strncmp(c->buf, "shutdown", 8)) { c->d->run = 0; return(0);} #endif debugmsg(DEBUG_HTTP, "HTTP: CON raw-input: '%s'\n", c->buf); char *method_str; char *path, *protocol, *query; /* Parse the first line of the request. */ method_str = c->buf; if (method_str == (char*) 0) { httperror(c->fd, 400, "Bad Request", "Can't parse request method."); c->run = 0; return(0); } path = strpbrk(method_str, " \t\012\015"); if (path == (char*) 0) { httperror(c->fd, 400, "Bad Request", "Can't parse request path."); c->run = 0; return(0); } *path++ = '\0'; path += strspn(path, " \t\012\015"); protocol = strpbrk(path, " \t\012\015"); if (protocol == (char*) 0) { httperror(c->fd, 400, "Bad Request", "Can't parse request protocol."); c->run = 0; return(0); } *protocol++ = '\0'; protocol += strspn(protocol, " \t\012\015"); query = strchr(path, '?'); if (query == (char*) 0) query = ""; else *query++ = '\0'; char *header = strpbrk(protocol, "\n\r \t\012\015"); if (!header && strncmp(protocol, "HTTP/0.9", 8)) { httperror(c->fd, 400, "Bad Request", "Can't parse request header."); c->run = 0; return(0); } else if (!header) header = ""; else { *header++ = '\0'; while (header && (*header == '\r' || *header == '\n')) header++; } #ifdef HAVE_WINDOWS debugmsg(DEBUG_HTTP, "HTTP: CON header-len: %lu\n", (long unsigned) strlen(header)); #else debugmsg(DEBUG_HTTP, "HTTP: CON header-len: %zu\n", strlen(header)); #endif debugmsg(DEBUG_HTTP, "HTTP: CON header: ''%s''\n", header); char *cookie = NULL, *host = NULL, *referer = NULL, *useragent = NULL; char *contenttype = NULL, *accept = NULL; long int contentlength = 0; char *cp, *line; /* Parse the rest of the request headers. */ while ((line = get_next_line(&header))) { if (line[0] == '\0') break; else if (strncasecmp(line, "Accept:", 7) == 0) { cp = &line[7]; cp += strspn(cp, " \t"); accept = cp; } else if (strncasecmp(line, "Cookie:", 7) == 0) { cp = &line[7]; cp += strspn(cp, " \t"); cookie = cp; } else if (strncasecmp(line, "Host:", 5) == 0) { cp = &line[5]; cp += strspn(cp, " \t"); host = cp; if (strchr(host, '/') != (char*) 0 || host[0] == '.') { httperror(c->fd, 400, "Bad Request", "Can't parse request."); c->run = 0; return(0); } } else if (strncasecmp(line, "Referer:", 8) == 0) { cp = &line[8]; cp += strspn(cp, " \t"); referer = cp; } else if (strncasecmp(line, "User-Agent:", 11) == 0) { cp = &line[11]; cp += strspn(cp, " \t"); useragent = cp; } else if (strncasecmp(line, "Content-Type:", 13) == 0) { cp = &line[13]; cp += strspn(cp, " \t"); contenttype = cp; } else if (strncasecmp(line, "Content-Length:", 15) == 0) { cp = &line[15]; cp += strspn(cp, " \t"); contentlength = atoll(cp); } else debugmsg(DEBUG_HTTP, "HTTP: CON header not parsed: '%s'\n", line); } debugmsg(DEBUG_HTTP, "HTTP: CON header co='%s' ho='%s' re='%s' ua='%s' ac='%s'\n", cookie, host, referer, useragent, accept); /* process headers */ int ac = accept?0:-1; line = accept; while (line && (cp = strchr(line, ','))) { *cp = '\0'; ac |= compare_accept(line); line = cp+1; } if (line) ac |= compare_accept(line); if (ac == 0) { httperror(c->fd, 415, "", "Your client does not accept any files that this server can produce.\n"); c->run = 0; return(0); } debugmsg(DEBUG_CON, "HTTP: Proto: '%s', method: '%s', path: '%s' query:'%s'\n", protocol, method_str, path, query); /* pre-process request */ if (!strcmp("POST", method_str) && (contenttype && !strcmp(contenttype, "application/x-www-form-urlencoded")) && (contentlength > 0 && contentlength <= strlen(header) /* - (header-c->buf) */ /* -num */) && (num < BUFSIZ) ) { header[contentlength] = '\0'; #ifdef HAVE_WINDOWS debugmsg(DEBUG_CON, "HTTP: translate POST->GET query - length data:%lu cl:%ld\n", (long unsigned) strlen(header), contentlength); #else debugmsg(DEBUG_CON, "HTTP: translate POST->GET query - length data:%zu cl:%ld\n", strlen(header), contentlength); #endif debugmsg(DEBUG_CON, "HTTP: x-www-form-urlencoded:'%s'\n", header); query = header; method_str = "GET"; } /* process request */ ics_http_handler(c, host, protocol, path, method_str, query, cookie); return(0); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/httprotocol.h000066400000000000000000000067471215541677300163500ustar00rootroot00000000000000/** @file httprotocol.h @brief HTTP protocol This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _HTTPROTOCOL_H #define _HTTPROTOCOL_H #include #include #include #ifdef HAVE_WINDOWS #include #include #endif #define DOCTYPE "\n" #define HTMLOPEN "\n\n" #define PROTOCOL "HTTP/1.0" ///< HTTP protocol version for replies #define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT" ///< time format used in HTTP header #define SL_SEP(string) (strlen(string)>0?(string[strlen(string)-1]=='/')?"":"/":"") #ifndef uint8_t #define uint8_t unsigned char #endif #ifndef socklen_t #define socklen_t int #endif #ifndef HAVE_WINDOWS #define CSEND(FD,MSG) write(FD, MSG, strlen(MSG)) #else #define CSEND(FD,MSG) send(FD, MSG, strlen(MSG), 0) #endif /** * @brief HTTP header * * interanl representation of a HTTP header to be sent with http_tx() */ typedef struct { size_t length; ///< Content-Length (default: 0 - not sent) time_t mtime; ///< Last-Modified (default: 0 - don't send this header) char *extra; ///< any additional HTTP header "key:value" - if not NULL '\\r\\n' is appended to this string. char *encoding; ///< Content-Encoding (default: NUll - not sent) char *ctype; ///< Content-type (default: text/html) char *retryafter; ///< for 503 errors: Retry-After time value in seconds (default: 5) } httpheader; /** * send a HTTP error reply. * @param fd socket file descriptor * @param s HTTP status code * @param title optional HTTP status-code message (may be NULL) * @param str optional text body explaining the error (may be NULL) */ void httperror(int fd , int s, const char *title, const char *str); /** * send HTTP reply status, header and transmit data. * @param fd socket file descriptor * @param s HTTP status code (usually 200) * @param h HTTP header information to send * @param len number of bytes to send * @param buf data to send */ int http_tx(int fd, int s, httpheader *h, size_t len, const uint8_t *buf); /** * internal, private function to send the HTTP status line * @param fd socket file descriptor * @param status HTTP status code */ const char * send_http_status_fd (int fd, int status); /** * internal function to format and send HTTP header * @param fd socket file descriptor * @param s HTTP status code * @param h HTTP header information to send */ void send_http_header_fd(int fd , int s, httpheader *h); /** */ char *url_unescape(const char *string, int length, int *olen); /** */ char *url_escape(const char *string, int inlength); #endif harvid-0.7.3/src/ics_handler.c000066400000000000000000000342141215541677300162230ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include // harvid.h #include "httprotocol.h" #include "ics_handler.h" #include "htmlconst.h" #include "enums.h" extern int cfg_usermask; extern int cfg_adminmask; /** Compare Transport Protocol request */ #define CTP(CMPPATH) \ ( strncasecmp(protocol, "HTTP/", 5) == 0 \ && strncasecmp(path, CMPPATH, strlen(CMPPATH)) == 0 \ && strcasecmp (method_str, "GET") == 0) #define SEND200(MSG) \ send_http_status_fd(c->fd, 200); \ send_http_header_fd(c->fd, 200, NULL); \ CSEND(c->fd, MSG); #define SEND200CT(MSG,CT) \ { \ httpheader h; \ memset(&h, 0, sizeof(httpheader)); \ h.ctype = CT; \ send_http_status_fd(c->fd, 200); \ send_http_header_fd(c->fd, 200, &h); \ CSEND(c->fd, MSG); \ } #define CONTENT_TYPE_SWITCH(fmt) \ (fmt) == OUT_PLAIN ? "text/plain" : \ ((fmt) == OUT_JSON ? "application/json" : \ ((fmt) == OUT_CSV ? "text/csv" : "text/html; charset=UTF-8")) /** * check for invalid or potentially malicious path. */ static int check_path(char *f) { int len; if (!f) return -1; len = strlen(f); if (len == 0) return 0; /* check for possible 'escape docroot' trickery */ if ( f[0] == '/' || strcmp(f, "..") == 0 || strncmp( f, "../", 3) == 0 || strstr(f, "/../") != (char*) 0 || strcmp(&(f[len-3]), "/..") == 0) return -1; return 0; } /////////////////////////////////////////////////////////////////// struct queryparserstate { ics_request_args *a; char *fn; int doit; }; void parse_param(struct queryparserstate *qps, char *kvp) { char *sep; if (!(sep = strchr(kvp, '='))) return; *sep = '\0'; char *val = sep+1; if (!val || strlen(val) < 1 || strlen(kvp) <1) return; debugmsg(DEBUG_ICS, "QUERY '%s'->'%s'\n", kvp, val); if (!strcmp (kvp, "frame")) { qps->a->frame = atoi(val); qps->doit |= 1; } else if (!strcmp (kvp, "w")) { qps->a->out_width = atoi(val); } else if (!strcmp (kvp, "h")) { qps->a->out_height = atoi(val); } else if (!strcmp (kvp, "file")) { qps->fn = url_unescape(val, 0, NULL); qps->doit |= 2; } else if (!strcmp (kvp, "flatindex")) { qps->a->idx_option |= OPT_FLAT; } else if (!strcmp (kvp, "format")) { if (!strncmp(val, "jpg",3)) {qps->a->render_fmt = FMT_JPG; qps->a->misc_int = atoi(&val[3]);} else if (!strncmp(val, "jpeg",4)) {qps->a->render_fmt = FMT_JPG; qps->a->misc_int = atoi(&val[4]);} else if (!strcmp(val, "png")) qps->a->render_fmt = FMT_PNG; else if (!strcmp(val, "ppm")) qps->a->render_fmt = FMT_PPM; else if (!strcmp(val, "yuv")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_YUV420P;} else if (!strcmp(val, "yuv420")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_YUV420P;} else if (!strcmp(val, "yuv440")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_YUV440P;} else if (!strcmp(val, "yuv422")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_YUYV422;} else if (!strcmp(val, "uyv422")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_UYVY422;} else if (!strcmp(val, "rgb")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_RGB24;} else if (!strcmp(val, "bgr")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_BGR24;} else if (!strcmp(val, "rgba")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_RGBA;} else if (!strcmp(val, "argb")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_ARGB;} else if (!strcmp(val, "bgra")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = PIX_FMT_BGRA;} /* info, version, rc,... format */ else if (!strcmp(val, "html")) qps->a->render_fmt = OUT_HTML; else if (!strcmp(val, "xhtml")) qps->a->render_fmt = OUT_HTML; else if (!strcmp(val, "json")) qps->a->render_fmt = OUT_JSON; else if (!strcmp(val, "csv")) qps->a->render_fmt = OUT_CSV; else if (!strcmp(val, "plain")) qps->a->render_fmt = OUT_PLAIN; } } static void parse_http_query_params(struct queryparserstate *qps, char *query) { char *t, *s = query; while(s && (t = strpbrk(s, "&?"))) { *t = '\0'; parse_param(qps, s); s = t+1; } if (s) parse_param(qps, s); } static int parse_http_query(CONN *c, char *query, httpheader *h, ics_request_args *a) { struct queryparserstate qps = {a, NULL, 0}; a->decode_fmt = PIX_FMT_RGB24; a->render_fmt = FMT_PNG; a->frame = 0; a->misc_int = 0; a->out_width = a->out_height = -1; // auto-set parse_http_query_params(&qps, query); /* check for illegal paths */ if (!qps.fn || check_path(qps.fn)) { httperror(c->fd, 404, "File not found.", "File not found."); return(-1); } /* sanity checks */ if (qps.doit&3) { if (qps.fn) { a->file_name = malloc(1+strlen(c->d->docroot)+strlen(qps.fn)*sizeof(char)); sprintf(a->file_name, "%s%s", c->d->docroot, qps.fn); a->file_qurl = qps.fn; } /* test if file exists or send 404 */ struct stat sb; if (stat(a->file_name, &sb)) { dlog(DLOG_WARNING, "CON: file not found: '%s'\n", a->file_name); httperror(c->fd, 404, "Not Found", "file not found."); return(-1); } /* check file permissions */ if (access(a->file_name, R_OK)) { dlog(DLOG_WARNING, "CON: permission denied for file: '%s'\n", a->file_name); httperror(c->fd, 403, NULL, NULL); return(-1); } if (h) h->mtime = sb.st_mtime; debugmsg(DEBUG_ICS, "serving '%s' f:%"PRId64" @%dx%d\n", a->file_name, a->frame, a->out_width, a->out_height); } return qps.doit; } ///////////////////////////////////////////////////////////////////// // Callbacks -- request handlers // harvid.c int hdl_decode_frame (int fd, httpheader *h, ics_request_args *a); char *hdl_homepage_html (CONN *c); char *hdl_server_status_html (CONN *c); char *hdl_file_info (CONN *c, ics_request_args *a); char *hdl_file_seek (CONN *c, ics_request_args *a); char *hdl_server_info (CONN *c, ics_request_args *a); char *hdl_server_version (CONN *c, ics_request_args *a); void hdl_clear_cache(); void hdl_purge_cache(); // fileindex.c void hdl_index_dir (int fd, const char *root, char *base_url, const char *path, int fmt, int opt); // logo.o #ifdef __APPLE__ #include #define EXTLD(NAME) \ extern const unsigned char _section$__DATA__ ## NAME []; #define LDVAR(NAME) _section$__DATA__ ## NAME #define LDLEN(NAME) (getsectbyname("__DATA", "__" #NAME)->size) #elif (defined HAVE_WINDOWS) /* mingw */ #define EXTLD(NAME) \ extern const unsigned char binary____ ## NAME ## _start[]; \ extern const unsigned char binary____ ## NAME ## _end[]; #define LDVAR(NAME) \ binary____ ## NAME ## _start #define LDLEN(NAME) \ ((binary____ ## NAME ## _end) - (binary____ ## NAME ## _start)) #else /* gnu ld */ #define EXTLD(NAME) \ extern const unsigned char _binary____ ## NAME ## _start[]; \ extern const unsigned char _binary____ ## NAME ## _end[]; #define LDVAR(NAME) \ _binary____ ## NAME ## _start #define LDLEN(NAME) \ ((_binary____ ## NAME ## _end) - (_binary____ ## NAME ## _start)) #endif EXTLD(doc_harvid_jpg) EXTLD(doc_seek_js) ///////////////////////////////////////////////////////////////////// /** main http request handler / dispatch requests */ void ics_http_handler( CONN *c, char *host, char *protocol, char *path, char *method_str, char *query, char *cookie ) { if (CTP("/status")) { char *status = hdl_server_status_html(c); SEND200(status); free(status); c->run = 0; } else if (CTP("/favicon.ico")) { #include "favicon.h" httpheader h; memset(&h, 0, sizeof(httpheader)); h.ctype = "image/x-icon"; h.length = sizeof(favicon_data); h.mtime = 1361225638 ; // TODO compile time check image timestamp http_tx(c->fd, 200, &h, sizeof(favicon_data), favicon_data); c->run = 0; } else if (CTP("/logo.jpg")) { httpheader h; memset(&h, 0, sizeof(httpheader)); h.ctype = "image/jpeg"; h.length = LDLEN(doc_harvid_jpg); h.mtime = 1361225638 ; // TODO compile time check image timestamp http_tx(c->fd, 200, &h, h.length, LDVAR(doc_harvid_jpg)); c->run = 0; } else if ((cfg_usermask & USR_WEBSEEK) && CTP("/seek.js")) { httpheader h; memset(&h, 0, sizeof(httpheader)); h.ctype = "application/javascript"; h.length = LDLEN(doc_seek_js); h.mtime = 1361225638 ; // TODO compile time check image timestamp http_tx(c->fd, 200, &h, h.length, LDVAR(doc_seek_js)); c->run = 0; } else if ((cfg_usermask & USR_WEBSEEK) && CTP("/seek")) { ics_request_args a; memset(&a, 0, sizeof(ics_request_args)); int rv = parse_http_query(c, query, NULL, &a); if (rv < 0) { ; } else if (rv&2) { char *info = hdl_file_seek(c, &a); if (info) { SEND200(info); free(info); } } else { httperror(c->fd, 400, "Bad Request", "

      Insufficient query parameters.

      "); } if (a.file_name) free(a.file_name); if (a.file_qurl) free(a.file_qurl); c->run = 0; } else if (CTP("/info")) { /* /info -> /file/info !! */ ics_request_args a; memset(&a, 0, sizeof(ics_request_args)); int rv = parse_http_query(c, query, NULL, &a); if (rv < 0) { ; } else if (rv&2) { char *info = hdl_file_info(c, &a); if (info) { SEND200CT(info, CONTENT_TYPE_SWITCH(a.render_fmt)); free(info); } } else { httperror(c->fd, 400, "Bad Request", "

      Insufficient query parameters.

      "); } if (a.file_name) free(a.file_name); if (a.file_qurl) free(a.file_qurl); c->run = 0; } else if (CTP("/rc")) { ics_request_args a; struct queryparserstate qps = {&a, NULL, 0}; memset(&a, 0, sizeof(ics_request_args)); parse_http_query_params(&qps, query); char *info = hdl_server_info(c, &a); SEND200CT(info, CONTENT_TYPE_SWITCH(a.render_fmt)); free(info); free(qps.fn); c->run = 0; } else if (CTP("/version")) { ics_request_args a; struct queryparserstate qps = {&a, NULL, 0}; memset(&a, 0, sizeof(ics_request_args)); parse_http_query_params(&qps, query); char *info = hdl_server_version(c, &a); SEND200CT(info, CONTENT_TYPE_SWITCH(a.render_fmt)); free(info); free(qps.fn); c->run = 0; } else if (CTP("/index/")) { /* /index/ -> /file/index/ ?! */ struct stat sb; char *dp = url_unescape(&(path[7]), 0, NULL); char *abspath = malloc((strlen(c->d->docroot) + strlen(dp) + 2) * sizeof(char)); sprintf(abspath, "%s/%s", c->d->docroot, dp); if (! (cfg_usermask & USR_INDEX)) { httperror(c->fd, 403, NULL, NULL); } else if (!dp || check_path(dp)) { httperror(c->fd, 400, "Bad Request", "Illegal filename."); } else if (stat(abspath, &sb) || !S_ISDIR(sb.st_mode)) { dlog(DLOG_WARNING, "CON: dir not found: '%s'\n", abspath); httperror(c->fd, 404, "Not Found", "file not found."); } else if (access(abspath, R_OK)) { dlog(DLOG_WARNING, "CON: permission denied for dir: '%s'\n", abspath); httperror(c->fd, 403, NULL, NULL); } else { ics_request_args a; char base_url[1024]; struct queryparserstate qps = {&a, NULL, 0}; memset(&a, 0, sizeof(ics_request_args)); parse_http_query_params(&qps, query); snprintf(base_url, 1024, "http://%s%s", host, path); if (! (cfg_usermask & USR_FLATINDEX)) a.idx_option &= ~OPT_FLAT; SEND200CT("", CONTENT_TYPE_SWITCH(a.render_fmt)); hdl_index_dir(c->fd, c->d->docroot, base_url, dp, a.render_fmt, a.idx_option); free(dp); free(abspath); free(qps.fn); } c->run = 0; } else if (CTP("/admin")) { /* /admin/ */ if (strncasecmp(path, "/admin/check", 12) == 0) { SEND200("ok\n"); } else if (strncasecmp(path, "/admin/flush_cache", 18) == 0) { if (cfg_adminmask & ADM_FLUSHCACHE) { hdl_clear_cache(); SEND200(OK200MSG("cache flushed\n")); } else { httperror(c->fd, 403, NULL, NULL); } } else if (strncasecmp(path, "/admin/purge_cache", 18) == 0) { if (cfg_adminmask & ADM_PURGECACHE) { hdl_purge_cache(); SEND200(OK200MSG("cache purged\n")); } else { httperror(c->fd, 403, NULL, NULL); } } else if (strncasecmp(path, "/admin/shutdown", 15) == 0) { if (cfg_adminmask & ADM_SHUTDOWN) { SEND200(OK200MSG("shutdown queued\n")); c->d->run = 0; } else { httperror(c->fd, 403, NULL, NULL); } } else { httperror(c->fd, 400, "Bad Request", "Nonexistant admin command."); } c->run = 0; } else if (CTP("/") && !strcmp(path, "/") && strlen(query) == 0) { /* HOMEPAGE */ char *msg = hdl_homepage_html(c); SEND200(msg); free(msg); c->run = 0; } else if ( (strncasecmp(protocol, "HTTP/", 5) == 0) /* /?file= -> /file/frame?.. !! */ &&(strcasecmp (method_str, "GET") == 0) ) { ics_request_args a; httpheader h; memset(&a, 0, sizeof(ics_request_args)); memset(&h, 0, sizeof(httpheader)); int rv = parse_http_query(c, query, &h, &a); if (rv < 0) { ; } else if (rv == 3) { hdl_decode_frame(c->fd, &h, &a); } else { httperror(c->fd, 400, "Bad Request", "

      Insufficient query parameters.

      "); } if (a.file_name) free(a.file_name); if (a.file_qurl) free(a.file_qurl); c->run = 0; } else { httperror(c->fd, 500, "", "server does not know what to make of this.\n"); c->run = 0; } } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/ics_handler.h000066400000000000000000000024501215541677300162250ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _ics_handler_H #define _ics_handler_H #include "socket_server.h" /** * @brief request parameters * * request arguments as parsed by the ICS protocol handler * mix of JVARGS and VInfo-request parameters */ typedef struct { char *file_name; char *file_qurl; int64_t frame; int decode_fmt; int render_fmt; int out_width; int out_height; int idx_option; int misc_int; // currently used for jpeg quality only } ics_request_args; void ics_http_handler( CONN *c, char *host, char *protocol, char *path, char *method_str, char *query, char *cookie ); #endif harvid-0.7.3/src/image_format.c000066400000000000000000000140721215541677300164020ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus This file contains some GPL source from vgrabbj by Jens Gecius 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 2, 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 . */ #ifdef HAVE_WINDOWS #include #define HAVE_BOOLEAN 1 #endif #include #include #include #include #include #include #include #include // harvid.h #include "enums.h" // TODO global config or per request setting #define JPEG_QUALITY 75 static int write_jpeg(VInfo *ji, uint8_t *buffer, int quality, FILE *x) { uint8_t *line; int n, y = 0, i, line_width; struct jpeg_compress_struct cjpeg; struct jpeg_error_mgr jerr; JSAMPROW row_ptr[1]; line = malloc(ji->out_width * 3); if (!line) { dlog(DLOG_CRIT, "IMF: OUT OF MEMORY, Exiting...\n"); exit(1); } cjpeg.err = jpeg_std_error(&jerr); jpeg_create_compress (&cjpeg); cjpeg.image_width = ji->out_width; cjpeg.image_height = ji->out_height; cjpeg.input_components = 3; //cjpeg.smoothing_factor = 0; // 0..100 cjpeg.in_color_space = JCS_RGB; jpeg_set_defaults (&cjpeg); jpeg_set_quality (&cjpeg, quality, TRUE); cjpeg.dct_method = quality > 90? JDCT_DEFAULT : JDCT_FASTEST; jpeg_simple_progression(&cjpeg); jpeg_stdio_dest (&cjpeg, x); jpeg_start_compress (&cjpeg, TRUE); row_ptr[0] = line; line_width = ji->out_width * 3; n = 0; for (y = 0; y < ji->out_height; y++) { for (i = 0; i< line_width; i += 3) { line[i] = buffer[n]; line[i+1] = buffer[n+1]; line[i+2] = buffer[n+2]; n += 3; } jpeg_write_scanlines (&cjpeg, row_ptr, 1); } jpeg_finish_compress (&cjpeg); jpeg_destroy_compress (&cjpeg); free(line); return(0); } static int write_png(VInfo *ji, uint8_t *image, FILE *x) { register int y; png_bytep rowpointers[ji->out_height]; png_infop info_ptr; png_structp png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) return(1); info_ptr = png_create_info_struct (png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return(1); } if (setjmp(png_jmpbuf(png_ptr))) { /* If we get here, we had a problem reading the file */ png_destroy_write_struct(&png_ptr, &info_ptr); return (1); } png_init_io (png_ptr, x); png_set_IHDR (png_ptr, info_ptr, ji->out_width, ji->out_height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info (png_ptr, info_ptr); for (y = 0; y < ji->out_height; y++) { rowpointers[y] = image + y*ji->out_width*3; } png_write_image(png_ptr, rowpointers); png_write_end (png_ptr, info_ptr); png_destroy_write_struct (&png_ptr, &info_ptr); return(0); } static int write_ppm(VInfo *ji, uint8_t *image, FILE *x) { fprintf(x, "P6\n%d %d\n255\n", ji->out_width, ji->out_height); fwrite(image, ji->out_height, 3*ji->out_width, x); return(0); } static FILE *open_outfile(char *filename) { if (!strcmp(filename, "-")) return stdout; return fopen(filename, "w+"); } size_t format_image(uint8_t **out, int render_fmt, int misc_int, VInfo *ji, uint8_t *buf) { #ifdef __USE_XOPEN2K8 size_t rs = 0; FILE *x = open_memstream((char**) out, &rs); #elif defined HAVE_WINDOWS char tfn[L_tmpnam]; // = "C:\\icsd.tmp" tmpnam(tfn); FILE *x = fopen(tfn, "w+b"); #else FILE *x = tmpfile(); #endif if (!x) { dlog(LOG_ERR, "IMF: tmpfile() creation failed.\n"); return(0); } switch (render_fmt) { case 1: { if (misc_int < 5 || misc_int > 100) misc_int = JPEG_QUALITY; if (write_jpeg(ji, buf, misc_int, x)) dlog(LOG_ERR, "IMF: Could not write jpeg\n"); break; } case 2: if (write_png(ji, buf, x)) dlog(LOG_ERR, "IMF: Could not write png\n"); break; case 3: if (write_ppm(ji, buf, x)) dlog(LOG_ERR, "IMF: Could not write ppm\n"); break; default: dlog(LOG_ERR, "IMF: Unknown outformat %d\n", render_fmt); fclose(x); return 0; break; } #ifdef __USE_XOPEN2K8 fclose(x); return rs; #elif defined HAVE_WINDOWS fclose(x); x = fopen(tfn, "rb"); #else fflush(x); #endif /* re-read image from tmp-file */ fseek (x , 0 , SEEK_END); long int rsize = ftell (x); rewind(x); if (fseek(x, 0L, SEEK_SET) < 0) { dlog(LOG_WARNING, "IMF: fseek failed\n"); } fflush(x); *out = (uint8_t*) malloc(rsize*sizeof(uint8_t)); if (fread(*out, sizeof(char), rsize, x) != rsize) { dlog(LOG_WARNING, "IMF: short read. - possibly incomplete image\n"); } fclose(x); #ifdef HAVE_WINDOWS unlink(tfn); #endif return (rsize); } void write_image(char *file_name, int render_fmt, VInfo *ji, uint8_t *buf) { FILE *x; if ((x = open_outfile(file_name))) { switch (render_fmt) { case FMT_JPG: if (write_jpeg(ji, buf, JPEG_QUALITY, x)) dlog(LOG_ERR, "IMF: Could not write jpeg: %s\n", file_name); break; case FMT_PNG: if (write_png(ji, buf, x)) dlog(LOG_ERR, "IMF: Could not write png: %s\n", file_name); break; case FMT_PPM: if (write_ppm(ji, buf, x)) dlog(LOG_ERR, "IMF: Could not write ppm: %s\n", file_name); break; default: dlog(LOG_ERR, "IMF: Unknown outformat %d\n", render_fmt); break; } if (strcmp(file_name, "-")) fclose(x); dlog(LOG_INFO, "IMF: Outputfile %s closed\n", file_name); } else dlog(LOG_ERR, "IMF: Could not open outfile: %s\n", file_name); return; } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/image_format.h000066400000000000000000000024261215541677300164070ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _image_format_H #define _image_format_H #include "vinfo.h" /** write image to memory-buffer * @param out pointer to memory-area for the formatted image * @param ji input data description (width, height, stride,..) * @param buf raw image data to format */ size_t format_image(uint8_t **out, int render_fmt, int misc_int, VInfo *ji, uint8_t *buf); /** write image to file * @param ji input data description (width, height, stride,..) * @param buf raw image data to format */ void write_image(char *file_name, int render_fmt, VInfo *ji, uint8_t *buf); #endif harvid-0.7.3/src/socket_server.c000066400000000000000000000303611215541677300166250ustar00rootroot00000000000000/* Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #ifdef HAVE_WINDOWS #include #include #else #include #include #include #include #include #endif #include #include "daemon_log.h" #include "daemon_util.h" #include "socket_server.h" #ifndef uint8_t #define uint8_t unsigned char #endif #ifndef HAVE_WINDOWS #include #include #else #ifndef socklen_t #define socklen_t int #endif #endif #ifndef HAVE_WINDOWS #ifndef __APPLE__ #define HAVE_PTHREAD_SIGMASK #endif #define CATCH_SIGNALS #endif //#define VERBOSE_SHUTDOWN 1 /** called to spawn thread for an incoming connection */ static int create_client(void *(*cli)(void *), void *arg) { pthread_t thread; #ifdef HAVE_PTHREAD_SIGMASK sigset_t newmask, oldmask; /* The idea is that only the main thread handles all the signals with * posix threads. Signals are blocked for any other thread. */ sigemptyset(&newmask); sigaddset(&newmask, SIGCHLD); sigaddset(&newmask, SIGPIPE); // ignore server loss - close on read. sigaddset(&newmask, SIGTERM); sigaddset(&newmask, SIGQUIT); sigaddset(&newmask, SIGINT); sigaddset(&newmask, SIGHUP); //sigaddset(&newmask, SIGALRM); pthread_sigmask(SIG_BLOCK, &newmask, &oldmask); /* block signals */ #endif /* HAVE_PTHREAD_SIGMASK */ pthread_attr_t pth_attr; pthread_attr_init(&pth_attr); pthread_attr_setdetachstate(&pth_attr, PTHREAD_CREATE_DETACHED); if(pthread_create(&thread, &pth_attr, cli, arg)) { #ifdef HAVE_PTHREAD_SIGMASK pthread_sigmask(SIG_SETMASK, &oldmask, NULL); /* restore the mask */ #endif /* HAVE_PTHREAD_SIGMASK */ return -1; } #ifdef HAVE_PTHREAD_SIGMASK pthread_sigmask(SIG_SETMASK, &oldmask, NULL); /* restore the mask */ #endif /* HAVE_PTHREAD_SIGMASK */ return 0; } /* -=-=-=-=-=-=-=-=-=-=- TCP socket daemon */ static void setnonblock(int sock, unsigned long l) { #ifdef HAVE_WINDOWS //WSAAsyncSelect(sock, 0, 0, FD_CONNECT|FD_CLOSE|FD_WRITE|FD_READ|FD_OOB|FD_ACCEPT); //setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); if(ioctlsocket(sock, FIONBIO, &l)<0) #else if(ioctl(sock, FIONBIO, &l)<0) #endif dlog(DLOG_WARNING, "SRV: unable to set (non)blocking mode: %s\n", strerror(errno)); else debugmsg(DEBUG_SRV, "SRV: set fd:%d in %sblocking mode\n", sock, l ? "non-" : ""); } static void server_sockaddr(ICI *d, struct sockaddr_in *addr) { memset(addr, 0, sizeof(addr)); addr->sin_family = AF_INET; addr->sin_addr.s_addr = d->listenaddr; addr->sin_port = d->listenport; d->local_addr = strdup(inet_ntoa(addr->sin_addr)); d->local_port = ntohs(d->listenport); } /** called once to init server */ static int create_server_socket(void) { int s, val = 1; if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { dlog(DLOG_CRIT, "SRV: unable to create local socket: %s\n", strerror(errno)); return -1; } setnonblock(s, 1); #ifndef HAVE_WINDOWS setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); #else setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*) &val, sizeof(int)); #endif return(s); } /** called once after server socket has been created */ static int server_bind(ICI *d, struct sockaddr_in addr) { if(bind(d->fd, (struct sockaddr *)&addr, sizeof(addr))) { dlog(DLOG_CRIT, "SRV: Error binding to %s:%d\n", d->local_addr, d->local_port); return -1; } dlog(DLOG_INFO, "SRV: bound to %s:%d\n", d->local_addr, d->local_port); if(listen(d->fd, (MAXCONNECTIONS>>1))) { dlog(DLOG_CRIT, "SRV: Error listening on socket.\n"); return -2; } return 0; } /* -=-=-=-=-=-=-=-=-=-=- TCP socket connection */ #define SLEEP_STEP (2) //#define CON_TIMEOUT (cfg->timeout) // -- TODO - configuration param //#define CON_TIMEOUT (30) // -- HTTP 30 sec #define CON_TIMEOUT (300) // ICSP 5 min static int global_shutdown = 0; #ifdef CATCH_SIGNALS void catchsig (int sig) { //signal(SIGHUP, catchsig); /* reset signal */ //signal(SIGINT, catchsig); dlog(DLOG_INFO, "SRV: caught signal, shutting down\n"); global_shutdown = 1; } #endif /* this is the main client connection loop - one for each connection */ static void *socket_handler(void *cn) { CONN *c = (CONN*) cn; c->buf_len = 0; c->timeout_cnt = 0; debugmsg(DEBUG_SRV, "SRV: socket-handler starting up for fd:%d\n", c->fd); while(c->run && c->d->run) { // keep-alive fd_set rd_set, wr_set; struct timeval tv; tv.tv_sec = SLEEP_STEP; tv.tv_usec = 0; FD_ZERO(&rd_set); FD_ZERO(&wr_set); FD_SET(c->fd, &rd_set); #ifdef SOCKET_WRITE if (c->cq != NULL) FD_SET(c->fd, &wr_set); #endif int ready = select(c->fd+1, &rd_set, &wr_set, NULL, &tv); if(ready<0) { dlog(DLOG_WARNING, "SRV: connection select error: %s\n", strerror(errno)); break; } if(!ready) { /* Timeout */ c->timeout_cnt += SLEEP_STEP; if (c->timeout_cnt > CON_TIMEOUT) { dlog(DLOG_INFO, "SRV: connection timeout: connection reset\n"); break; } continue; } // preform socket read/write on c->fd // NOTE: set c->run = 0; is preferred to return(!0) in protocol_handler; if (FD_ISSET(c->fd, &rd_set)) { debugmsg(DEBUG_SRV, "SRV: read..\n"); if (protocol_handler(c, c->d->userdata)) break; } #ifdef SOCKET_WRITE else // check again if we can write now. if (FD_ISSET(c->fd, &wr_set)) { if (protocol_droid(c, c->d->userdata)) break; } #endif debugmsg(DEBUG_SRV, "SRV: loop:%d\n", c->fd); } debugmsg(DEBUG_SRV, "SRV: protocol ended. closing connection fd:%d\n", c->fd); #ifndef HAVE_WINDOWS close(c->fd); #else closesocket(c->fd); #endif pthread_mutex_lock(&c->d->lock); c->d->num_clients--; pthread_mutex_unlock(&c->d->lock); dlog(DLOG_INFO, "SRV: closed client connection (%u) from %s:%d.\n", c->fd, c->client_address, c->client_port); debugmsg(DEBUG_SRV, "SRV: now %i connections active\n", c->d->num_clients); if (c->client_address) free(c->client_address); free(c); return NULL; /* end close connection */ } /**launch handler for each incoming connection. */ static void start_child(ICI *d, int fd, char *rh, unsigned short rp) { pthread_mutex_lock(&d->lock); d->num_clients++; if (d->num_clients > d->max_clients) d->max_clients = d->num_clients; pthread_mutex_unlock(&d->lock); CONN *c = calloc(1, sizeof(CONN)); c->run = 1; c->fd = fd; c->d = d; c->client_address = strdup(rh); c->client_port = rp; #ifdef SOCKET_WRITE c->cq = NULL; #endif c->userdata = NULL; if(create_client(&socket_handler, c)) { if(fd >= 0) #ifndef HAVE_WINDOWS close(fd); #else closesocket(fd); #endif dlog(DLOG_ERR, "SRV: Protocol handler child fork failed\n"); pthread_mutex_lock(&d->lock); d->num_clients--; pthread_mutex_unlock(&d->lock); free(c); debugmsg(DEBUG_SRV, "SRV: Connection terminated: now %i connections active\n", d->num_clients); return; } debugmsg(DEBUG_SRV, "SRV: Connection started: now %i connections active\n", d->num_clients); } /** handshake - accept incoming connection */ static int accept_connection(ICI *d, char **remotehost, unsigned short *rport) { struct sockaddr_in addr; int s; socklen_t addrlen = sizeof(addr); debugmsg(DEBUG_SRV, "SRV: waiting for accept on server-fd:%d\n", d->fd); do { s = accept(d->fd, (struct sockaddr *)&addr, &addrlen); } while(s < 0 && errno == EINTR); *remotehost = inet_ntoa(addr.sin_addr); *rport = ntohs(addr.sin_port); if(s<0) { dlog(DLOG_WARNING, "SRV: socket accept error: %s\n", strerror(errno)); return (-1); } dlog(DLOG_INFO, "SRV: Connection accepted %s:%d\n", *remotehost, *rport); // pthread_mutex_lock(&d->lock); ? not needed if (d->num_clients >= MAXCONNECTIONS) { protocol_error(s, 503, "Too many open connections. Please try again later."); #ifndef HAVE_WINDOWS close(s); #else closesocket(s); #endif dlog(DLOG_WARNING, "SRV: refused client. max number of connections (%i) readed.\n", MAXCONNECTIONS); return (-1); } // check if we should use SO_KEEPALIVE here //int val = 1; setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(int)); // or set non-blocking i/o ... //setnonblock(s, 1); return(s); } static int main_loop (void *arg) { ICI *d = arg; struct sockaddr_in addr; int rv = 0; #ifndef HAVE_WINDOWS signal(SIGPIPE, SIG_IGN); #endif if ((d->fd = create_server_socket()) < 0) {rv = -1; goto daemon_end;} server_sockaddr(d, &addr); if(server_bind(d, addr)) {rv = -1; goto daemon_end;} if (d->uid || d->gid) { if (drop_privileges(d->uid, d->gid)) {rv = -1; goto daemon_end;} } if (access(d->docroot, R_OK)) { dlog(DLOG_CRIT, "SRV: can not read document-root (permission denied)\n"); rv = -1; goto daemon_end; } global_shutdown = 0; #ifdef CATCH_SIGNALS signal(SIGHUP, catchsig); signal(SIGINT, catchsig); #endif #ifdef USAGE_FREQUENCY_STATISTICS d->stat_start = time(NULL); #endif while(d->run && !global_shutdown) { fd_set rfds; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&rfds); FD_SET(d->fd, &rfds); // select() returns 0 on timeout, -1 on error. if((select(d->fd+1, &rfds, NULL, NULL, &tv))<0) { dlog(DLOG_WARNING, "SRV: unable to select the socket: %s\n", strerror(errno)); if (errno != EINTR) { rv = -1; goto daemon_end; } else { continue; } } char *rh = NULL; unsigned short rp = 0; int s = -1; if(FD_ISSET(d->fd, &rfds)) { s = accept_connection(d, &rh, &rp); } else { d->age++; #ifdef USAGE_FREQUENCY_STATISTICS /* may not be accurate, select() may skip a second once in a while */ d->req_stats[time(NULL) % FREQ_LEN] = 0; #endif } if (s >= 0) { start_child(d, s, rh, rp); d->age=0; #ifdef USAGE_FREQUENCY_STATISTICS d->stat_count++; d->req_stats[time(NULL) % FREQ_LEN]++; #endif continue; // no need to check age. } if (d->timeout > 0 && d->age > d->timeout) { dlog(DLOG_INFO, "SRV: no request since %d seconds shutting down.\n", d->age); global_shutdown = 1; } } #ifdef CATCH_SIGNALS signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); #endif /* wait until all connections are closed */ int timeout = 31; if (d->num_clients > 0) dlog(DLOG_INFO, "SRV: server shutdown procedure: waiting up to %i sec for clients to disconnect..\n", timeout-1); #ifdef VERBOSE_SHUTDOWN printf("\n"); #endif while (d->num_clients> 0 && --timeout > 0) { #ifdef VERBOSE_SHUTDOWN if (timeout%3 == 0) printf("SRV: shutdown timeout (%i) \r", timeout); fflush(stdout); #endif mymsleep(1000); } #ifdef VERBOSE_SHUTDOWN printf("\n"); #endif if (d->num_clients > 0) { dlog(DLOG_WARNING, "SRV: Terminating with %d active connections.\n", d->num_clients); } else { dlog(DLOG_INFO, "SRV: Closed all connections.\n"); } daemon_end: close(d->fd); dlog(DLOG_CRIT, "SRV: server shut down.\n"); d->run = 0; if (d->local_addr) free(d->local_addr); pthread_mutex_destroy(&d->lock); free(d); return(rv); } // tcp server thread int start_tcp_server (const unsigned int hostnl, const unsigned short port, const char *docroot, const int uid, const int gid, unsigned int timeout, void *userdata) { ICI *d = calloc(1, sizeof(ICI)); pthread_mutex_init(&d->lock, NULL); d->run = 1; d->listenport = htons(port); d->listenaddr = hostnl; d->uid = uid; d->gid = gid; d->docroot = docroot; d->age = 0; d->timeout = timeout; d->userdata = userdata; return main_loop(d); } // vim:sw=2 sts=2 ts=8 et: harvid-0.7.3/src/socket_server.h000066400000000000000000000124341215541677300166330ustar00rootroot00000000000000/** @file socket_server.h @brief TCP socket interface and daemon This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2013 Robin Gareus 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 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef _SOCKETSERVER_H #define _SOCKETSERVER_H #include #include // limit number of connections per daemon #define MAXCONNECTIONS (120) #ifndef NDEBUG #define USAGE_FREQUENCY_STATISTICS 1 #endif #if (defined USAGE_FREQUENCY_STATISTICS && !defined FREQ_LEN) #define FREQ_LEN (3600) #endif /** * @brief daemon * * Daemon handle and configuration */ typedef struct ICI { int fd; ///< file descriptor of the socket int run; ///< server status: 1= keep running , 0 = error/end/terminate. unsigned short listenport; ///< in network order notation unsigned int listenaddr; ///< in network order notation int local_port; ///< same as listeport - in host order notation char *local_addr;///< same as listenaddr - in host order notation int num_clients; ///< current number of connected clients int max_clients; ///< configured max. number of connections for this server pthread_mutex_t lock; ///< lock to modify num_clients int uid; ///< drop privileges, assume this userid int gid; ///< drop privileges, adopt this group const char *docroot; ///< document root for all connections unsigned int age; ///< used for timeout -- in seconds unsigned int timeout; ///< if > 0 shut down serve if age reaches this value void *userdata; ///< generic placeholder for usage specific data #ifdef USAGE_FREQUENCY_STATISTICS time_t req_stats[FREQ_LEN]; time_t stat_start; unsigned int stat_count; #endif } ICI; /** * @brief client connection * * client connection handle */ typedef struct CONN { ICI *d; ///< pointer to parent daemon int fd; ///< file descriptor of the connection short run; ///< connection status: 1= keep running , 0 = error/end/terminate. char buf[BUFSIZ]; ///< Socket read buffer int buf_len; ///< Index of first unused byte in buf int timeout_cnt; ///< internal connectiontimeout counter char *client_address;///< IP address of the client unsigned short client_port; ///< port used by the client #ifdef SOCKET_WRITE void *cq; ///< outgoing command queue #endif void *userdata; ///< generic information for this connection } CONN; /** * @brief allocates and initializes an ICI structure and enters the server thread. * * While the tcp server itself launches threads for each incoming connection, * start_tcp_server() does not return until this server has been shut down. * launching a server will activate the connection callbacks \ref protocol_handler() * and \ref protocol_droid(). * * @param hostnl listen IP in network byte order. eg htonl(INADDR_ANY) * @param port TCP port to listen on * @param docroot configure the document-root for all connections to this server. * @param uid specify the user-id that the server will assume. If \a uid is zero no suid is performed. * @param gid the unix group of the server; \a gid may be zero in which case the effective group ID of the calling process will remain unchanged. * @param d user-data passed on to callbacks. */ int start_tcp_server (const unsigned int hostnl, const unsigned short port, const char *docroot, const int uid, const int gid, unsigned int timeout, void *d); // extern function virtual prototype(s) /** * virtual callback - implement this for the server's protocol. * * this callback will be invoked if data is available for reading on the socket. * * @param c socket connection to handle * @param d user/application specific server-data from \ref start_tcp_server() * @return return 0 is no error occured, returning non zero will close the connection. */ int protocol_handler(CONN *c, void *d); // called for each incoming data. /** * virtual callback - implement this for the server's protocol. * * @param fd socket connection to handle * @param status some status number * @param msg may be NULL * @param status integer - here: HTTP status code */ void protocol_error(int fd, int status, char *msg); /** * virtual callback - implement this for the server's protocol. */ void protocol_response(int fd, char *msg); /** * virtual callback - implement this for the server's protocol. * * this callback will be executed when data can be written to the socket. but only if SOCKET_WRITE is defined * * @param c socket connection to handle * @param d user/application specific server-data from \ref start_tcp_server() * @return return 0 is no error occured, returning non zero will close the connection. */ int protocol_droid(CONN *c, void *d); // called if socket is writable and c->cq is not NULL #endif