pax_global_header00006660000000000000000000000064126253466740014531gustar00rootroot0000000000000052 comment=4cde5835c173124c088bc1291ddf8fef28f9b378 harvid-0.8.1/000077500000000000000000000000001262534667400130145ustar00rootroot00000000000000harvid-0.8.1/COPYING000066400000000000000000000432541262534667400140570ustar00rootroot00000000000000 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.8.1/ChangeLog000066400000000000000000000110331262534667400145640ustar00rootroot000000000000002015-11-25 (0.8.1) Robin Gareus * explicit LARGEFILE64 support * spelling corrections * ffmpeg-2.9 compatibility * update static builds to ffmpeg-2.8 2015-03-03 (0.8.0) Robin Gareus * portability fixes * update ut-hash * speed up direct info-lookups & fix possible deadlock * bump various build-stack versions for binary releases 2015-01-03 (0.7.6) Robin Gareus * update build system, packaging and default flags * optimized windows build (no terminal) * support OSX 10.10 * remove 'beta' from header image/icon (no changes to application itself) 2014-09-05 (0.7.5) Robin Gareus * fix FPS calculation * hotfix release 2014-08-28 (0.7.4) Robin Gareus * case insensitive file extension for file-index * define OSX minimum version to 10.5 * various fixes for windows build (paths, snprintf, tmpfile) * tweak built-in HTML front-page (title, IE compat) * update ffmpeg compatibility layer * ffmpeg decoder: update seek-mechanism & framerate parsing * update win+osx build stack 2013-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.8.1/Makefile000066400000000000000000000007641262534667400144630ustar00rootroot00000000000000VERSION?=$(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.8.1/README.md000066400000000000000000000066431262534667400143040ustar00rootroot00000000000000harvid -- 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.8.1/common.mak000066400000000000000000000021371262534667400150010ustar00rootroot00000000000000VERSION?=$(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 WINPREFIX?=$(HOME)/.wine/drive_c/x-prefix WINLIB?=$(WINPREFIX)/lib PKG_CONFIG_PATH=$(WINLIB)/pkgconfig/ ARCHINCLUDES=-I$(WINPREFIX)/include -DHAVE_WINDOWS ARCHLIBES=-lwsock32 -lws2_32 -lpthread LDFLAGS+=-L$(WINLIB) -L$(WINPREFIX)/bin -mwindows UNAME=win32|mingw LIBEXT=dll else RANLIB=ranlib STRIP=strip NM=nm UNAME=$(shell uname) ifeq ($(UNAME),Darwin) ARCHFLAGS+=-headerpad_max_install_names LOGODEP=logo.c seek.c ECHO=echo LIBEXT=dylib NM=nm else ARCHLIBES=-lrt -lpthread LIBEXT=so NM=nm -B endif endif harvid-0.8.1/doc/000077500000000000000000000000001262534667400135615ustar00rootroot00000000000000harvid-0.8.1/doc/Makefile000066400000000000000000000013761262534667400152300ustar00rootroot00000000000000include ../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.8.1/doc/harvid.1000066400000000000000000000063311262534667400151230ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .TH HARVID "1" "November 2015" "harvid v0.8.1" "User Commands" .SH NAME harvid \- video server .SH SYNOPSIS .B harvid [\fI\,OPTION\/\fR] [\fI\,document-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 one to enable or disable \fI\,/seek\/\fP and \fI\,/index\/\fP http\-handlers and change their respone: 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 \-A '!flush_cache purge_cache shutdown' \-C 256 /tmp/ .PP sudo harvid \-c /tmp/ \-u nobody \-g 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 Lavf56.40.101 Lavc56.60.100 Lavu54.31.100 .SH COPYRIGHT Copyright \(co GPL 2002\-2013 Robin Gareus harvid-0.8.1/doc/harvid.jpg000066400000000000000000000514271262534667400155510ustar00rootroot00000000000000JFIFHHC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((d " ̝K$İ2ι4Kf(2 {=B0RL, K 'ĵPRA_$AYr+I hDह] J-eTRLm#0g$pCΓ*(Jz!'HKAbH^=F%B-μ=)̈́Q?DۢN<sK_?X[n]+w~ga.y>a_+_H;j~ܥs:82S\NOLkז-^%]|eOOcmy@ jKi+έL+N&tE9UV(˗aWM`5FuoȮoi&GtD(kdŷLѼ[|/$uI!$HI <*z♏kiu|:[+5WX{M`9ޓ `094Z=f4ٍ?n=鼲t(!#Ov-:#'SHa7 qϥu2,u鄴Syܫ`u5W%v|˨k]Su? mh-+ʽK?pB&q4˝ )ReĕTXsK?Nws|.y3'YsKs6i8$jKHE~mezO,{ =v5T z$֟ͬ;I#|`[$쯭"ռYIҤ题zs/T?mљ68}x2#gyGjU\YRBQ芸/:;(D]f&9YK–Z`wXkΎ,.|7箇oգ4yzk:z8)fJM͞=J#je]'2L5%ԮqH%hsI̤Qv5ז''YcM6MO]j`R wѥZ[mV6Ņs,X h9gDy3\@Ut]vp_m"ϑn.Mt3-w1""gB)Ǐf~#>D.!5\? n8S批L?%w0~!uf|q\|օc8Ϝq'8{ Ffy9͹ M.si}˛9J&{6r7yb/6t9K56W)Ù 9 ȑwd Gwv.ڑy4Ug}Ӳx].GdMY՛Xnv99R>X,4Ixz |a:xZK'ET8,6^GzHd,C3*Wu &rt>K&E(@ӵ6kR矑g͟ @`e{Ph`i$CgοQ8G_.L;DـKܡkf56>-QӋ`޹&Cۣ*;Z:YrNeade]IT'Y$]W*Zp)XXC_9ݡ)d #TT7Qۮh0Fon",fdœT$ lU֎cffEk VWL+bژW'qܘu[V²%KSGVjV #2ۣ?(E\o  ;pmjZ(ݘw1⏁1iS[yFHPl<;;rܘ3B qH+|d)\S.ll7+3nuXF d#/?ߥ{M4,FVf /a^9ߚ霌ƷKHP&j:"AfFk?xf8L{V'S{˾VIjK^;#7zf_Ww4bj6Db>:{&e8Dڭ:_N/9ݐbMK8KrXiܷ)E*HIC#Ms!#H٥4ηѼ~]rk 8ϑo?픯c}fyD^{4): fʚa2sS ؙfẉBa 9a :>; ժ*mUMSu8ӷj!s ^El'*0uO h[Y8EԅJT,HeK@nNOk~'3tdʹYFH|ML:L ,L38drCⓈGLKrGٽS/k?ӓU~>z}kӭSGB;uc߬_7`ۧjb(+Яdb-Eb23\-f:vcbwLi_Fq_Ѫ׮EYUӐ]~O]I4>sL%! '7eȷ:y qqm̵vnEȜГÕh2dvJuC?,:z8>>&pIن>f PASߵ}Z [SFKjx)-we% #;FIjhú)mMwE%I)V̱sGfXǹ,wE%歔Ijhú)-Kw5%@{;2vL["Lqfiv^e<ιʹ*[ٔ'2vZrweNNBvZ^ؒnbSS ;)7e[׬!jx 3ĦZ%>B-$jKs [ [s KSH K2 K3H KSH K3H*K3H(Jb̦*K3H KSH KSH KSH K2Jby QLdȤ"dNߦ鯯%!1 0AP"2Q@?Ve+hYeђ-hhEђ2Eh9$DGު,TbbJ,ك(iwfE$lVv6g#e6t׆<^%\rH-?e&EQF&(ģQHb^ B#EʥLU8woV:$9qL$.XDddu8/gDi!M =Zǒr__GeW%!1AQ0@q `a?͢fȻͯYOY5e3Vi/FjYL5WjX]X?e\fkai]ER>h,ME6TJ*gar'BYEI_<ל2Xf9Fm/bm9l-͙oRÕ xÏw,P5/򈶱xJĨhDV}+ZE ~H_oE !1"2Aa Qq#3brs$0B4CR@Sc?Qkxy"jFVr78/ dmqHxpxd7,׽Y oqxYE*Vr78/ dmqHxpxd7,׽Y oqxYE*Vr78/ dmqHxpxd7,׽Y oqxYE*Vr78/ dmqHxpxd7,׽Y oqxYE*T༤Ό[E_|U۩0ᒣz$努Ops\fzY|nh&-䢭I ',UR|,T34.{՗켢{v+A6>Qm=_%lL8dާn9bb\%1(1sެe_;۵Z n(eRa%F;pIT /j)@9e/(ݯ M[uWE[-*7ۀ2NXX{W Lg5i\/yE}pVl|ۯzJ*mԘpQNrU'KڸJc9P3NbY~,wk내c|QVonÆJv *>*^Srs=^Qe_\([{u&2ToSdUIRk ӘV_/E_|U۩0ᒣz$努Ops\fzY|nh&-䢭I ',UR|,T34.{՗켢{v+A6>Qm=_%lL8dާn9bb\%1(1sެe_;۵Z n(eRa%@s-#!l재Ukգ]jӨ;Vÿ$)S\*+<=)Z0VO}eJ|'&kW-fGї G=?>QʼnY֨eR7emBS893%0sS[}sS}L|S(jW&:S-ܯxCLck?=dV@Ł9Z@9mRk{ijZ!ivi qPNcqыѐ2ʮsoq ;rwFep\㟑tL醭;C ,OBRFL,oJl7\p͸e"Ub>/Hk}-o:[g6?ΛZeTa *FAT1ϸh&DB ZN [Zs(ч@<Ư^~w@0έ+T'cckR2'}Vc K31rB>MGIU8n6nyp'K/T4-J!%^;TS:rUgq74y  w :MԨNj^d]sn#|h8-|\=-ý/T6OK3EOD a@[OGqc S=`=3p %_˚e9+sZ&Уo(4,z6GN槳SG/%"RDsyfnrSw޲Swճ[8v~on1,Z[+%ANj)y=%[5WSղާWϭד=dVg6@^,pѿ #R7~jUg o#R7~jUt*olEEFp/#"aE~$^,4{#"١E'>y|Hx^GEϳCGċg<$G]y^u =5$\>^KEϳz_.}ĩ}%yx^KEϳגs@דP^OR8zKԚ_4cޱ[>[+g޲Y,] tHvtA:3xNtas_T NCYR :%1th4ԖFNIath4ԖFǾZx |XO ^iᠵxh-{vh-{姆׾Aᠵxha٠ΐxh-{姆׾Aᠵxh-{ ^ta D я(!1AQaq 0?! -sHY.b\gfʾU1>WTHY.b\gfʾU1>WTHY.b\gfʾU1>WTHY.b\gfʾU1>WT3GZT6UO-߿<o oQWUٕH0H-~KSzM ځV|i!NV=st=kn卲ƭ{Jh<ǴV)c)_/sv>+^ ,yg[A:Z>8wͭ.)ڃ8T/}M ^'<VnLyϼ%3,4f4/q-a=?!ne |ve~e^Fvݴ_.3{/F@aUώӂ]BNwNV]mq8&ܒ384~e^FvjCF4r`bh4i3\۟IJd_КW; Owyx=N=0.' φ[ktqwyT+{Kq>;2.}" 3㷴#Irl_m'kN|Ǥˢsn}IFƒ>;{J< C0-wo[K{ABq|jƷ60u R e{5~w*tvi6SK1*0‡>;{J LAߝJgb vd(_}"W3ǡϝ1i_d8jyS Z _ zj[4p//) G{jZPwMYb[nahxDU1Lj8 )^rAxMw;ΊcDՆx.xĹq )D -MʭJSQ.涹K'Hk5sX%&2KyY@ n$u4m8C%& 4 )8&+PUgCk_ IA4{Ɛ僝dhMOfrYiIY3xsqw6mw//4FeRNqq:lLAL/BݦUL9atnNwTTCI`had" GLB%Oz_L? ꧃H !!MkmWVd,3*ʄf, kßIYE8IgK~Xr[Fʀp06n>u_s QNQ٘?q!ZMSoG&S w piKP0&=ܓDpA;0 ڀ8V)RSH6/t2IUFeheJ( +ɅQ(Rؙ[a }%EC!4 hk4!ZiMFr QʽF%XOYO|qJd̀p~W{|2` 9[4~L_| quTbK7rq9K.*- %e @,JGaPoۋyT8N4*tb=4*NY9GͰ4~a{YB%5!1f_TSCh'c_)#Ǭr9kC(W#meez>Vr7*L05.8}w~gqo?\Kg*QZm?/J ɦe{־o0z'Buş.?nG~bz9?4$,])AQvb.x~&da*ފb fxnb^fa eh4у-_20 "1]-#DJ0(Rf8PXnXr?/};1fn"o5*[!4{ڥ*.1f;+/I{&y v\uHOc>0.Ogߺf1og.?%/QlTK|ˊtx0ҭF ʛl;`4@ Db6J 4{gh%/"{ ̷)*.Z?})JTSB ag 2'1F9pG+);s.`Q4{Y7('8FJ5?[a+jkd6'[n,L/`\ҙQ .J&%w(_w5biO⡔ $LLJNˉhzKHtd  V݇xe5.#n2=oNNiį=.\_gd_kB 5n2ݟ( 'Gc1gjaL`@n ;˧M!nfUm)q,X%[X2/ry%UA{DdzUH^S^;Uh\ׁbVU zjxb":iGkye_BˎOA. aR˾%[K̡2n3这}rŘtQ1ccOԢI?9֛`zb_1z./U]+0ll}gb*&L]Jywq6Qm^UR^;wM ?Ҫ!ڈ|b;T)\VBSK6{cSvrg30՟O9&4*j¿_ 9B%:*c:gm'Jni>rMgJR;k>d0[> 榬JkGA7m'DϿj 1u A%x:qa_}?fz#ѝe:*Rq]4OJb=RFEUt-x)ѵ3n^φNUܫn:bn7ӜMtfo:4UL?]9 NR`Ϙ:rE:ZNNNQCoe:swE:r:rE:rE:rE:rE:3D:7Z3zt:(RReoK )RaGdRW!(Nz&BBY!8FlO1oAlg uW3g, E3?ni<< hhI~:O誰},4+rw#l#?z$qa],GL8(`a!  < <<<<<"!1a AQ@Pq?ꖕ:DDY;tO-vN;3svN'wNG;t4;V J,e5.)3X06t0A(\F҈rӉFȰKB1^D84QkJB9OBo2a1@eTxϨH TRѷ Τ3#(hI)uN:cQS D#rkRĸ-cL]$w,T]CC9H5RF-l?f%x@jeOii2ŪH@ =PN Skx9ϝfNuF kP͠102}%ʮɵYd.]e 4T©r\GRIP_(!1AQaq0 ?H])aٶZ}J"wI}ZӦ4I]v(#΋'4.A؇a,;6B5DN / @6t<㠉+NЂ;aydZ%;6󜥇fhVU(߁%dhNӼt%tiG{,8#:,K[ҸAbs\v- z>;$삭Cipw$;B{ŇgEkcWH CZw٨řh"G`[$"y~4Kf̛UmVtS`Xn#T)6Pl:\ DO 2;EGtQ*m߯ *٠&hU4{ MEWQ ̴|l-Uuռۿ^TF%AfM*Ѷ:i7B(6\Y Ah"G`[$"y~4Kf̛UmVtS`Xn#T)6Pl:\ DO 2;E:l 6ː7zmu2 o0{\)hA5 u@ _XZ=Y($y̓Ob m:D@WT ^> +I2 &Y?U{DFi5CЀ#}46YJ#Z|*7}0ؾb/] ~t|!G1g,@"J}ѵaEW`Sׅ?^xd:Nsi>#S^ݛib8hnJ[w\"p*d_x3uq8Ga}89t> XX֕ $jCw܃ \5ai0%v=u pۅm [n bK(N KXo߼#Jd| G3@J}Z.iCp+Cw܃<cv̐9[`h=A TjSa"/p8V*7}0k0/l-e8WQ BMe[\47DŽGGdllT[}=ZP`u5Cp* ]r ƅrNS!r+7MT~s QT P\s;6C<.c ~2sY ҂KŔ *YH1**VaR ؾFׯ;Js73)DIX>|^@}1?Iڳ{T|υEg&dMVWEx\*9j{=Q'Ăʃ' + #]պ{Ǩʚ 9g>Ô"Ěg$OP7r^1d/~lݩgZ(z}㽘ˇ  Ţ DFf̢l?,;cT_s2/gk>sF#y]yerH(K4譿?Ti~9O'?g>É ;I ֺP]>`GS^y4V;LSޯ$8bՁPl?lR$7HwN 87ASg`ȃ}JU\u4tA :pe'-cDЃז.{ K@>KvMRfо`0 bc$s: c͵Uñy:}&2uj-Z>rƱy/5hٕx MbJy?RLFOX>uya &cm}me JPyJӚpG18 F#4h u{s*PF^Sfjp΁*P?}bKNχYA b=pr:~CdY|cj~L~Gt5 >_ы@_fY,͗㌆]@1FA3IKus)[6Yb[#JˊIFQ7pK'lnYhZkd4pgcu|b6+{pP.i{A7sarnx˱.ٺp<$e! ݭ5\u+ -jMvV\Hn/P<8(N8IC0QȖcWX meBc?-f9F $B'c}T`Ʊ!LpIQl'çQCI^a?9['k, Cᬽ{x^/IXs[L 4 V\~^J~t #P{{ǝt"zv.{~ѭpK%LH$>颸gIA (C^~#D&Fـbel<X|Xk$#לs.E~]h!DT >tJ l3Aq<pu72E##Q xNrtj)&U$^ً51~DђWpb?aˈUD1F& Bբ"XEʗ7`Nl$~sDAS4^ s+ S},I`w; Tr. c4f ,!C:Z@j` B\Z8*`0q[}ml8x8U. Bb`f=A] )KDG!rmk?wʕrTف%?)a|9*-8N9h|4qY8{ޭcebv伓i]fѲ2e-d_>VTIs$K߷2:´@ n)1"ԵvN9 [$ s+/R#O'Xk079x #Z)/+x@mQic9Ā54.K]3x栾)bV6~rnq09WBsgU g,o9\c$召TspᮉpdReZeZpR \ 5D`r&S9ц;r,08q:9ш0LFl-t8 U 8#m~gnZ./~Wpb WkF* h$$>q34mϔ|&quF' Hl/e) H4jeǙllⓒ(2S n|! ٷ=b0`=eJ_XXք79P >@'|qUKxx$%q:W85LFA.RPq-+to*/.GHe%rm2l\/S!q86'83Mn_Kd#c30^q.;chc4>13ą\;Wrʝ9i y_vqsGÛ{YxÀSrE>(zQ \TcH'n'An XQ~qz ؇X$I^Sfg/+iM ijk !d <{*Rd8uFXx^gX.]:1z,  \09WO!9kM&^qߝFFϻŬxr'Ãf@OuVIf^#9kO&@7&'rÍ/<[Kg iZ+`v\_>1G0>nW"Q"#DX\<TC 64NT 8_b.s^|ːpAsIUGS+\[c%E W .ooݓeCs`,~suEuGii6N9JpG7Z]YqXogsj.!P4 a h1P4˹+zh˦=@wT +kBrh/) \ h/)vRQe8yP4SB/ 6g5ܻ9Xh/.Ts:3l@^S랜{*RqEAyLS1;kf>|]L\*h/.e3ʁLz38B:$?}P>rC_ )]<ׂ rop>= /'LbM;7CN' TDM _sȃ`yl~x(;j `

X- }5\"l/!2liM|ֆM>P - }5 Z;6h@v4^O Gb&MxVM>D6OEcz=}4vUD%O$GH}P>e_harvid-0.8.1/doc/libharvid_example.c000066400000000000000000000071121262534667400174050ustar00rootroot00000000000000/* 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.8.1/doc/seek.js000066400000000000000000000043251262534667400150520ustar00rootroot00000000000000var 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.8.1/libharvid/000077500000000000000000000000001262534667400147605ustar00rootroot00000000000000harvid-0.8.1/libharvid/Makefile000066400000000000000000000077361262534667400164350ustar00rootroot00000000000000include ../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 ifeq ($(ARCH),mingw) LIBHARVID_OBJECTS += snprintf.o FLAGS += -DSNPRINTF_LONGLONG_SUPPORT -DHAVE_SNPRINTF -DPREFER_PORTABLE_SNPRINTF endif 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 export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH);\ $(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 export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH);\ $(CC) -o libharvid.$(LIBEXT) \ -dynamiclib \ $(LIBHARVID_OBJECTS) \ $(CFLAGS) $(FLAGS) \ -install_name $(libdir)/libharvid.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 export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH);\ $(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) export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH);\ $(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.8.1/libharvid/decoder_ctrl.c000066400000000000000000000652141262534667400175650ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2014 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 (AV_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 == AV_PIX_FMT_YUV420P || render_fmt == AV_PIX_FMT_YUV440P || render_fmt == AV_PIX_FMT_YUYV422 || render_fmt == AV_PIX_FMT_UYVY422 || render_fmt == AV_PIX_FMT_RGB24 || render_fmt == AV_PIX_FMT_BGR24 || render_fmt == AV_PIX_FMT_RGBA || render_fmt == AV_PIX_FMT_ARGB || render_fmt == AV_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 = AV_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 != AV_PIX_FMT_NONE && cptr->fmt != fmt && cptr->fmt != AV_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 = AV_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) { assert (prev != mem); 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 = AV_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 == AV_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 == AV_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"); pthread_mutex_lock(&jvo->lock); if ((jvo->flags&(VOF_OPEN|VOF_VALID|VOF_PENDING)) == (VOF_VALID|VOF_OPEN)) { jvo->infolock_refcnt++; jvo->flags |= VOF_INFO; pthread_mutex_unlock(&jvo->lock); BUSYDEC(jvd) return(jvo); } pthread_mutex_unlock(&jvo->lock); } } 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); jvo = NULL; continue; } if ((jvo->flags&(VOF_USED|VOF_OPEN|VOF_VALID|VOF_INFO|VOF_PENDING)) == (VOF_VALID)) { jvo->flags |= VOF_PENDING; jvo->lru = time(NULL); pthread_mutex_unlock(&jvo->lock); if (fmt == AV_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; } 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 } } 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, AV_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), (long long) 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%"PRIlld"%"PRIlld"
\n"); } else { rprintf("\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.8.1/libharvid/decoder_ctrl.h000066400000000000000000000054451262534667400175720ustar00rootroot00000000000000/** @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.8.1/libharvid/dlog.h000066400000000000000000000054261262534667400160650ustar00rootroot00000000000000/** @file dlog.h @brief output and logfile abstraction This file is part of harvid @author Robin Gareus @copyright Copyright (C) 2002,2003,2008-2014 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 */ #define PRIlld "lld" #ifndef WIN32 #define mymsleep(ms) usleep((ms) * 1000) #define SNPRINTF snprintf #else #include #define mymsleep(ms) Sleep(ms) int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); #define SNPRINTF portable_snprintf #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.8.1/libharvid/dlog_null.c000066400000000000000000000003741262534667400171070ustar00rootroot00000000000000/* 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.8.1/libharvid/ffcompat.h000066400000000000000000000100601262534667400167250ustar00rootroot00000000000000/* ffmpeg compatibility wrappers * * Copyright 2012-2014 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 #ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 #endif #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(50, 0, 0) #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #define AVMEDIA_TYPE_DATA CODEC_TYPE_DATA #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO #endif #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(55, 7, 0) # ifndef AVUTIL_OLD_PIX_FMTS_H # define AV_PIX_FMT_ARGB PIX_FMT_ARGB # define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 # define AV_PIX_FMT_BGRA PIX_FMT_BGRA # define AV_PIX_FMT_NONE PIX_FMT_NONE # define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 # define AV_PIX_FMT_RGBA PIX_FMT_RGBA # define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 # define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P # define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P # define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 # endif #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 /* avformat < 53.2.0 */ #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 /* avcodec < 53.5.0 */ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 7, 0) static inline int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, void **options __attribute__((unused))) { return avcodec_open(avctx, codec); } #endif /* avcodec <= 53.7.0 */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 2, 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 /* avformat < 53.5.0 */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 92, 100) // since 7ecc2d40 static inline AVFrame *av_frame_alloc() { return avcodec_alloc_frame(); } #endif #endif /* FFCOMPAT_H */ harvid-0.8.1/libharvid/ffdecoder.c000066400000000000000000000561721262534667400170600ustar00rootroot00000000000000/* 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 #ifndef MAX #define MAX(A,B) ( ( (A) > (B) ) ? (A) : (B) ) #endif /* ffmpeg source */ typedef struct { /* file specific decoder settings */ int want_ignstart; //< set before calling ff_open_movie() int want_genpts; /* 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; TimecodeRate tc; double file_frame_offset; long frames; char *current_file; /* helper variables */ int64_t 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 AV_PIX_FMT_UYVY422: { int i; for (i = 0; i < w*h*2; i += 2) { buf[i] = 0x00; buf[i+1] = 0x80; } } break; case AV_PIX_FMT_YUYV422: { int i; for (i = 0; i < w*h*2; i += 2) { buf[i] = 0x80; buf[i+1] = 0x00; } } break; case AV_PIX_FMT_YUV420P: { size_t Ylen = w * h; memset(buf, 0, Ylen); memset(buf+Ylen, 0x80, Ylen/2); } break; case AV_PIX_FMT_YUV440P: { size_t Ylen = w * h; memset(buf, 0, Ylen); memset(buf+Ylen, 0x80, Ylen); } break; case AV_PIX_FMT_BGR24: case AV_PIX_FMT_RGB24: case AV_PIX_FMT_RGBA: case AV_PIX_FMT_BGRA: case AV_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 AV_PIX_FMT_YUV420P: case AV_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 AV_PIX_FMT_YUYV422: case AV_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 AV_PIX_FMT_RGB24: case AV_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 AV_PIX_FMT_RGBA: case AV_PIX_FMT_BGRA: case AV_PIX_FMT_ARGB: { const int O = (ff->render_fmt == AV_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); assert(numBytes > 0); 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", (long unsigned) numBytes); #else fprintf(stderr, "out of memory (trying to allocate %zu bytes)\n", numBytes); #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_set_framerate(ffst *ff) { AVStream *av_stream; av_stream = ff->pFormatCtx->streams[ff->videoStream]; ff->framerate = 0; ff->tc.num = 0; ff->tc.den = 1; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55, 0, 100) // 9cf788eca8ba (merge a75f01d7e0) { AVRational fr = av_stream->r_frame_rate; if (fr.den > 0 && fr.num > 0) { ff->framerate = av_q2d (av_stream->r_frame_rate); ff->tc.num = fr.num; ff->tc.den = fr.den; } } #else { AVRational fr = av_stream_get_r_frame_rate (av_stream); if (fr.den > 0 && fr.num > 0) { ff->framerate = av_q2d (fr); ff->tc.num = fr.num; ff->tc.den = fr.den; } } #endif if (ff->framerate < 1 || ff->framerate > 1000) { AVRational fr = av_stream->avg_frame_rate; if (fr.den > 0 && fr.num > 0) { ff->framerate = av_q2d (fr); ff->tc.num = fr.num; ff->tc.den = fr.den; } } if (ff->framerate < 1 || ff->framerate > 1000) { AVRational fr = av_stream->time_base; if (fr.den > 0 && fr.num > 0) { ff->framerate = 1.0 / av_q2d (fr); ff->tc.num = fr.den; ff->tc.den = fr.num; } } if (ff->framerate < 1 || ff->framerate > 1000) { if (!want_quiet) fprintf(stderr, "WARNING: cannot determine video-frame rate, using 25fps.\n"); ff->framerate = 25; ff->tc.num = 25; ff->tc.den = 1; } ff->tc.drop = 0; if (floor(ff->framerate * 100.0) == 2997) ff->tc.drop = 1; } 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; ff->avprev = -1; 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); } 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 FormatContext: %.2f\n", ff->pFormatCtx->duration * ff->framerate / AV_TIME_BASE); #endif if (avs->nb_frames > 0) { ff->frames = avs->nb_frames; ff->duration = ff->frames / ff->framerate; } else { ff->duration = ff->pFormatCtx->duration / (double)AV_TIME_BASE; ff->frames = ff->pFormatCtx->duration * ff->framerate / AV_TIME_BASE; } const AVRational fr_Q = { ff->tc.den, ff->tc.num }; ff->tpf = av_rescale_q (1, fr_Q, avs->time_base); } 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 = av_frame_alloc())) { 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 = av_frame_alloc())) { 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 uint64_t parse_pts_from_frame (AVFrame *f) { uint64_t pts = AV_NOPTS_VALUE; static uint8_t pts_warn = 0; // should be per decoder pts = AV_NOPTS_VALUE; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 49, 100) if (pts == AV_NOPTS_VALUE) { pts = av_frame_get_best_effort_timestamp (f); if (pts != AV_NOPTS_VALUE) { if (!(pts_warn & 1) && want_verbose) fprintf(stderr, "PTS: Best effort.\n"); pts_warn |= 1; } } #else #warning building with libavutil < 51.49.100 is highly discouraged #endif if (pts == AV_NOPTS_VALUE) { pts = f->pkt_pts; if (pts != AV_NOPTS_VALUE) { if (!(pts_warn & 2) && want_verbose) fprintf(stderr, "Used PTS from packet instead frame's PTS.\n"); pts_warn |= 2; } } if (pts == AV_NOPTS_VALUE) { pts = f->pts; // sadly bogus with many codecs :( if (pts != AV_NOPTS_VALUE) { if (!(pts_warn & 8) && want_verbose) fprintf(stderr, "Used AVFrame assigned pts (instead frame PTS).\n"); pts_warn |= 8; } } if (pts == AV_NOPTS_VALUE) { pts = f->pkt_dts; if (pts != AV_NOPTS_VALUE) { if (!(pts_warn & 4) && want_verbose) fprintf(stderr, "Used decode-timestamp from packet (instead frame PTS).\n"); pts_warn |= 4; } } return pts; } static int my_seek_frame (ffst *ff, AVPacket *packet, int64_t framenumber) { AVStream *v_stream; int rv = 0; int64_t timestamp; if (ff->videoStream < 0) return (0); v_stream = ff->pFormatCtx->streams[ff->videoStream]; if (ff->want_ignstart) framenumber += (int64_t) rint(ff->framerate * ((double)ff->pFormatCtx->start_time / (double)AV_TIME_BASE)); if (framenumber < 0 || framenumber >= ff->frames) { return -1; } const AVRational fr_Q = { ff->tc.den, ff->tc.num }; timestamp = av_rescale_q(framenumber, fr_Q, v_stream->time_base); if (ff->avprev == timestamp) { return 0; } if (ff->avprev < 0 || ff->avprev >= timestamp || ((ff->avprev + 32 * ff->tpf) < timestamp)) { rv = av_seek_frame(ff->pFormatCtx, ff->videoStream, timestamp, AVSEEK_FLAG_BACKWARD) ; if (ff->pCodecCtx->codec->flush) { avcodec_flush_buffers(ff->pCodecCtx); } } ff->avprev = -1; if (rv < 0) { return -1; } int bailout = 600; int decoded = 0; while (bailout > 0) { int err; if ((err = av_read_frame (ff->pFormatCtx, packet)) < 0) { if (err != AVERROR_EOF) { av_free_packet (packet); return -1; } else { --bailout; } } if(packet->stream_index != ff->videoStream) { av_free_packet (packet); continue; } int frameFinished = 0; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 21, 0) err = avcodec_decode_video (ff->pCodecCtx, ff->pFrame, &frameFinished, packet->data, packet->size); #else err = avcodec_decode_video2 (ff->pCodecCtx, ff->pFrame, &frameFinished, packet); #endif av_free_packet (packet); if (err < 0) { return -10; } if (!frameFinished) { --bailout; continue; } int64_t pts = parse_pts_from_frame (ff->pFrame); if (pts == AV_NOPTS_VALUE) { return -7; } const int64_t prefuzz = ff->tpf > 10 ? 1 : 0; if (pts + prefuzz >= timestamp) { if (pts - timestamp < ff->tpf) { ff->avprev = pts; return 0; // OK } // Cannot reliably seek to target frame if (decoded == 0) { if (want_verbose) fprintf(stdout, " PTS mismatch want: %"PRId64" got: %"PRId64" -> re-seek\n", timestamp, pts); // re-seek - make a guess, since we don't know the keyframe interval rv = av_seek_frame(ff->pFormatCtx, ff->videoStream, MAX(0, timestamp - ff->tpf * 25), AVSEEK_FLAG_BACKWARD) ; if (ff->pCodecCtx->codec->flush) { avcodec_flush_buffers(ff->pCodecCtx); } if (rv < 0) { return -3; } --bailout; ++decoded; continue; } if (!want_quiet) fprintf(stderr, " PTS mismatch want: %"PRId64" got: %"PRId64" -> fail\n", timestamp, pts); // XXX return -2; } --bailout; ++decoded; } return -5; } /** * 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; 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, frame)) { 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); return 0; } if (ff->pFrameFMT && !want_quiet) { fprintf( stderr, "frame seek unsucessful (frame: %lu).\n", frame); } render_empty_frame(ff, buf, w, h, xoff, ys); return -1; } 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; memcpy(&i->framerate, &ff->tc, sizeof(TimecodeRate)); } 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 = AV_PIX_FMT_RGB24; (*((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 AV_PIX_FMT_NONE: return "-"; case AV_PIX_FMT_BGR24: return "BGR24"; case AV_PIX_FMT_RGB24: return "RGB24"; case AV_PIX_FMT_RGBA: return "RGBA"; case AV_PIX_FMT_BGRA: return "BGRA"; case AV_PIX_FMT_ARGB: return "ARGB"; case AV_PIX_FMT_YUV420P: return "YUV420P"; case AV_PIX_FMT_YUYV422: return "YUYV422"; case AV_PIX_FMT_UYVY422: return "UYVY422"; case AV_PIX_FMT_YUV440P: return "YUV440P"; default: return "?"; } } /* vi:set ts=8 sts=2 sw=2: */ harvid-0.8.1/libharvid/ffdecoder.h000066400000000000000000000026611262534667400170570ustar00rootroot00000000000000/* 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.8.1/libharvid/frame_cache.c000066400000000000000000000311011262534667400173350ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2014 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"), (long long) 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%"PRIlld"%"PRIlld"
cache size: %sB in memory
\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.8.1/libharvid/frame_cache.h000066400000000000000000000023631262534667400173520ustar00rootroot00000000000000/* 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.8.1/libharvid/harvid.h000066400000000000000000000023551262534667400164130ustar00rootroot00000000000000/** @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.8.1/libharvid/harvid.pc.in000066400000000000000000000003601262534667400171650ustar00rootroot00000000000000libdir=@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.8.1/libharvid/image_cache.c000066400000000000000000000205631262534667400173370ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2013,2014 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, (long unsigned) 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", (long long) 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%"PRIlld"%"PRIlld"
cache size: %sB in memory
\n"); } } // vim:sw=2 sts=2 ts=8 et: harvid-0.8.1/libharvid/image_cache.h000066400000000000000000000024751262534667400173460ustar00rootroot00000000000000/* 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.8.1/libharvid/snprintf.c000066400000000000000000001216661262534667400170030ustar00rootroot00000000000000/* * snprintf.c - a portable implementation of snprintf * * AUTHOR * Mark Martinec , April 1999. * * Copyright 1999, Mark Martinec. All rights reserved. * * TERMS AND CONDITIONS * This program is free software; you can redistribute it and/or modify * it under the terms of the "Frontier Artistic License" which comes * with this Kit. * * 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 Frontier Artistic License for more details. * * You should have received a copy of the Frontier Artistic License * with this Kit in the file named LICENSE.txt . * If not, I'll be glad to provide one. * * FEATURES * - careful adherence to specs regarding flags, field width and precision; * - good performance for large string handling (large format, large * argument or large paddings). Performance is similar to system's sprintf * and in several cases significantly better (make sure you compile with * optimizations turned on, tell the compiler the code is strict ANSI * if necessary to give it more freedom for optimizations); * - return value semantics per ISO/IEC 9899:1999 ("ISO C99"); * - written in standard ISO/ANSI C - requires an ANSI C compiler. * * SUPPORTED CONVERSION SPECIFIERS AND DATA TYPES * * This snprintf only supports the following conversion specifiers: * s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) * with flags: '-', '+', ' ', '0' and '#'. * An asterisk is supported for field width as well as precision. * * Length modifiers 'h' (short int), 'l' (long int), * and 'll' (long long int) are supported. * NOTE: * If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the * length modifier 'll' is recognized but treated the same as 'l', * which may cause argument value truncation! Defining * SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also * handles length modifier 'll'. long long int is a language extension * which may not be portable. * * Conversion of numeric data (conversion specifiers d, u, o, x, X, p) * with length modifiers (none or h, l, ll) is left to the system routine * sprintf, but all handling of flags, field width and precision as well as * c and s conversions is done very carefully by this portable routine. * If a string precision (truncation) is specified (e.g. %.8s) it is * guaranteed the string beyond the specified precision will not be referenced. * * Length modifiers h, l and ll are ignored for c and s conversions (data * types wint_t and wchar_t are not supported). * * The following common synonyms for conversion characters are supported: * - i is a synonym for d * - D is a synonym for ld, explicit length modifiers are ignored * - U is a synonym for lu, explicit length modifiers are ignored * - O is a synonym for lo, explicit length modifiers are ignored * The D, O and U conversion characters are nonstandard, they are supported * for backward compatibility only, and should not be used for new code. * * The following is specifically NOT supported: * - flag ' (thousands' grouping character) is recognized but ignored * - numeric conversion specifiers: f, e, E, g, G and synonym F, * as well as the new a and A conversion specifiers * - length modifier 'L' (long double) and 'q' (quad - use 'll' instead) * - wide character/string conversions: lc, ls, and nonstandard * synonyms C and S * - writeback of converted string length: conversion character n * - the n$ specification for direct reference to n-th argument * - locales * * It is permitted for str_m to be zero, and it is permitted to specify NULL * pointer for resulting string argument if str_m is zero (as per ISO C99). * * The return value is the number of characters which would be generated * for the given input, excluding the trailing null. If this value * is greater or equal to str_m, not all characters from the result * have been stored in str, output bytes beyond the (str_m-1) -th character * are discarded. If str_m is greater than zero it is guaranteed * the resulting string will be null-terminated. * * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1, * but is different from some older and vendor implementations, * and is also different from XPG, XSH5, SUSv2 specifications. * For historical discussion on changes in the semantics and standards * of snprintf see printf(3) man page in the Linux programmers manual. * * Routines asprintf and vasprintf return a pointer (in the ptr argument) * to a buffer sufficiently large to hold the resulting string. This pointer * should be passed to free(3) to release the allocated storage when it is * no longer needed. If sufficient space cannot be allocated, these functions * will return -1 and set ptr to be a NULL pointer. These two routines are a * GNU C library extensions (glibc). * * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf, * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1 * characters into the allocated output string, the last character in the * allocated buffer then gets the terminating null. If the formatted string * length (the return value) is greater than or equal to the str_m argument, * the resulting string was truncated and some of the formatted characters * were discarded. These routines present a handy way to limit the amount * of allocated memory to some sane value. * * AVAILABILITY * http://www.ijs.si/software/snprintf/ * * REVISION HISTORY * 1999-04 V0.9 Mark Martinec * - initial version, some modifications after comparing printf * man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10, * and checking how Perl handles sprintf (differently!); * 1999-04-09 V1.0 Mark Martinec * - added main test program, fixed remaining inconsistencies, * added optional (long long int) support; * 1999-04-12 V1.1 Mark Martinec * - support the 'p' conversion (pointer to void); * - if a string precision is specified * make sure the string beyond the specified precision * will not be referenced (e.g. by strlen); * 1999-04-13 V1.2 Mark Martinec * - support synonyms %D=%ld, %U=%lu, %O=%lo; * - speed up the case of long format string with few conversions; * 1999-06-30 V1.3 Mark Martinec * - fixed runaway loop (eventually crashing when str_l wraps * beyond 2^31) while copying format string without * conversion specifiers to a buffer that is too short * (thanks to Edwin Young for * spotting the problem); * - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR) * to snprintf.h * 2000-02-14 V2.0 (never released) Mark Martinec * - relaxed license terms: The Artistic License now applies. * You may still apply the GNU GENERAL PUBLIC LICENSE * as was distributed with previous versions, if you prefer; * - changed REVISION HISTORY dates to use ISO 8601 date format; * - added vsnprintf (patch also independently proposed by * Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01) * 2000-06-27 V2.1 Mark Martinec * - removed POSIX check for str_m<1; value 0 for str_m is * allowed by ISO C99 (and GNU C library 2.1) - (pointed out * on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie). * Besides relaxed license this change in standards adherence * is the main reason to bump up the major version number; * - added nonstandard routines asnprintf, vasnprintf, asprintf, * vasprintf that dynamically allocate storage for the * resulting string; these routines are not compiled by default, * see comments where NEED_V?ASN?PRINTF macros are defined; * - autoconf contributed by Caolan McNamara * 2000-10-06 V2.2 Mark Martinec * - BUG FIX: the %c conversion used a temporary variable * that was no longer in scope when referenced, * possibly causing incorrect resulting character; * - BUG FIX: make precision and minimal field width unsigned * to handle huge values (2^31 <= n < 2^32) correctly; * also be more careful in the use of signed/unsigned/size_t * internal variables - probably more careful than many * vendor implementations, but there may still be a case * where huge values of str_m, precision or minimal field * could cause incorrect behaviour; * - use separate variables for signed/unsigned arguments, * and for short/int, long, and long long argument lengths * to avoid possible incompatibilities on certain * computer architectures. Also use separate variable * arg_sign to hold sign of a numeric argument, * to make code more transparent; * - some fiddling with zero padding and "0x" to make it * Linux compatible; * - systematically use macros fast_memcpy and fast_memset * instead of case-by-case hand optimization; determine some * breakeven string lengths for different architectures; * - terminology change: 'format' -> 'conversion specifier', * 'C9x' -> 'ISO/IEC 9899:1999 ("ISO C99")', * 'alternative form' -> 'alternate form', * 'data type modifier' -> 'length modifier'; * - several comments rephrased and new ones added; * - make compiler not complain about 'credits' defined but * not used; */ /* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf. * * If HAVE_SNPRINTF is defined this module will not produce code for * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well, * causing this portable version of snprintf to be called portable_snprintf * (and portable_vsnprintf). */ /* #define HAVE_SNPRINTF */ /* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and * vsnprintf but you would prefer to use the portable routine(s) instead. * In this case the portable routine is declared as portable_snprintf * (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf') * is defined to expand to 'portable_v?snprintf' - see file snprintf.h . * Defining this macro is only useful if HAVE_SNPRINTF is also defined, * but does does no harm if defined nevertheless. */ /* #define PREFER_PORTABLE_SNPRINTF */ /* Define SNPRINTF_LONGLONG_SUPPORT if you want to support * data type (long long int) and length modifier 'll' (e.g. %lld). * If undefined, 'll' is recognized but treated as a single 'l'. * * If the system's sprintf does not handle 'll' * the SNPRINTF_LONGLONG_SUPPORT must not be defined! * * This is off by default as (long long int) is a language extension. */ /* #define SNPRINTF_LONGLONG_SUPPORT */ /* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf. * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly, * otherwise both snprintf and vsnprintf routines will be defined * and snprintf will be a simple wrapper around vsnprintf, at the expense * of an extra procedure call. */ /* #define NEED_SNPRINTF_ONLY */ /* Define NEED_V?ASN?PRINTF macros if you need library extension * routines asprintf, vasprintf, asnprintf, vasnprintf respectively, * and your system library does not provide them. They are all small * wrapper routines around portable_vsnprintf. Defining any of the four * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY * and turns on PREFER_PORTABLE_SNPRINTF. * * Watch for name conflicts with the system library if these routines * are already present there. * * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as * specified by C99, to be able to traverse the same list of arguments twice. * I don't know of any other standard and portable way of achieving the same. * With some versions of gcc you may use __va_copy(). You might even get away * with "ap2 = ap", in this case you must not call va_end(ap2) ! * #define va_copy(ap2,ap) ap2 = ap */ /* #define NEED_ASPRINTF */ /* #define NEED_ASNPRINTF */ /* #define NEED_VASPRINTF */ /* #define NEED_VASNPRINTF */ /* Define the following macros if desired: * SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE, * HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE, LINUX_COMPATIBLE, * DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE, * PERL_COMPATIBLE, PERL_BUG_COMPATIBLE, * * - For portable applications it is best not to rely on peculiarities * of a given implementation so it may be best not to define any * of the macros that select compatibility and to avoid features * that vary among the systems. * * - Selecting compatibility with more than one operating system * is not strictly forbidden but is not recommended. * * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE . * * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is * documented in a sprintf man page on a given operating system * and actually adhered to by the system's sprintf (but not on * most other operating systems). It may also refer to and enable * a behaviour that is declared 'undefined' or 'implementation specific' * in the man page but a given implementation behaves predictably * in a certain way. * * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf * that contradicts the sprintf man page on the same operating system. * * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE * conditionals take into account all idiosyncrasies of a particular * implementation, there may be other incompatibilities. */ /* ============================================= */ /* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */ /* ============================================= */ #define PORTABLE_SNPRINTF_VERSION_MAJOR 2 #define PORTABLE_SNPRINTF_VERSION_MINOR 2 #if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF) # if defined(NEED_SNPRINTF_ONLY) # undef NEED_SNPRINTF_ONLY # endif # if !defined(PREFER_PORTABLE_SNPRINTF) # define PREFER_PORTABLE_SNPRINTF # endif #endif #if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE) #define SOLARIS_COMPATIBLE #endif #if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE) #define HPUX_COMPATIBLE #endif #if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE) #define DIGITAL_UNIX_COMPATIBLE #endif #if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE) #define PERL_COMPATIBLE #endif #if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE) #define LINUX_COMPATIBLE #endif #include #include #include #include #include #include #include #ifdef isdigit #undef isdigit #endif #define isdigit(c) ((c) >= '0' && (c) <= '9') /* For copying strings longer or equal to 'breakeven_point' * it is more efficient to call memcpy() than to do it inline. * The value depends mostly on the processor architecture, * but also on the compiler and its optimization capabilities. * The value is not critical, some small value greater than zero * will be just fine if you don't care to squeeze every drop * of performance out of the code. * * Small values favor memcpy, large values favor inline code. */ #if defined(__alpha__) || defined(__alpha) # define breakeven_point 2 /* AXP (DEC Alpha) - gcc or cc or egcs */ #endif #if defined(__i386__) || defined(__i386) # define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */ #endif #if defined(__hppa) # define breakeven_point 10 /* HP-PA - gcc */ #endif #if defined(__sparc__) || defined(__sparc) # define breakeven_point 33 /* Sun Sparc 5 - gcc 2.8.1 */ #endif /* some other values of possible interest: */ /* #define breakeven_point 8 */ /* VAX 4000 - vaxc */ /* #define breakeven_point 19 */ /* VAX 4000 - gcc 2.7.0 */ #ifndef breakeven_point # define breakeven_point 6 /* some reasonable one-size-fits-all value */ #endif #define fast_memcpy(d,s,n) \ { register size_t nn = (size_t)(n); \ if (nn >= breakeven_point) memcpy((d), (s), nn); \ else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ register char *dd; register const char *ss; \ for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } } #define fast_memset(d,c,n) \ { register size_t nn = (size_t)(n); \ if (nn >= breakeven_point) memset((d), (int)(c), nn); \ else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ register char *dd; register const int cc=(int)(c); \ for (dd=(d); nn>0; nn--) *dd++ = cc; } } /* prototypes */ #if defined(NEED_ASPRINTF) int asprintf (char **ptr, const char *fmt, /*args*/ ...); #endif #if defined(NEED_VASPRINTF) int vasprintf (char **ptr, const char *fmt, va_list ap); #endif #if defined(NEED_ASNPRINTF) int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...); #endif #if defined(NEED_VASNPRINTF) int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap); #endif #if defined(HAVE_SNPRINTF) /* declare our portable snprintf routine under name portable_snprintf */ /* declare our portable vsnprintf routine under name portable_vsnprintf */ #else /* declare our portable routines under names snprintf and vsnprintf */ #define portable_snprintf snprintf #if !defined(NEED_SNPRINTF_ONLY) #define portable_vsnprintf vsnprintf #endif #endif #if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); #if !defined(NEED_SNPRINTF_ONLY) int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap); #endif #endif /* declarations */ static char credits[] = "\n\ @(#)snprintf.c, v2.2: Mark Martinec, \n\ @(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\ @(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n"; #if defined(NEED_ASPRINTF) int asprintf(char **ptr, const char *fmt, /*args*/ ...) { va_list ap; size_t str_m; int str_l; *ptr = NULL; va_start(ap, fmt); /* measure the required size */ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); va_end(ap); assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ *ptr = (char *) malloc(str_m = (size_t)str_l + 1); if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } else { int str_l2; va_start(ap, fmt); str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); va_end(ap); assert(str_l2 == str_l); } return str_l; } #endif #if defined(NEED_VASPRINTF) int vasprintf(char **ptr, const char *fmt, va_list ap) { size_t str_m; int str_l; *ptr = NULL; { va_list ap2; va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ va_end(ap2); } assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ *ptr = (char *) malloc(str_m = (size_t)str_l + 1); if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } else { int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); assert(str_l2 == str_l); } return str_l; } #endif #if defined(NEED_ASNPRINTF) int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) { va_list ap; int str_l; *ptr = NULL; va_start(ap, fmt); /* measure the required size */ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); va_end(ap); assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ if (str_m == 0) { /* not interested in resulting string, just return size */ } else { *ptr = (char *) malloc(str_m); if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } else { int str_l2; va_start(ap, fmt); str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); va_end(ap); assert(str_l2 == str_l); } } return str_l; } #endif #if defined(NEED_VASNPRINTF) int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) { int str_l; *ptr = NULL; { va_list ap2; va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ va_end(ap2); } assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ if (str_m == 0) { /* not interested in resulting string, just return size */ } else { *ptr = (char *) malloc(str_m); if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } else { int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); assert(str_l2 == str_l); } } return str_l; } #endif /* * If the system does have snprintf and the portable routine is not * specifically required, this module produces no code for snprintf/vsnprintf. */ #if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) #if !defined(NEED_SNPRINTF_ONLY) int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { va_list ap; int str_l; va_start(ap, fmt); str_l = portable_vsnprintf(str, str_m, fmt, ap); va_end(ap); return str_l; } #endif #if defined(NEED_SNPRINTF_ONLY) int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { #else int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) { #endif #if defined(NEED_SNPRINTF_ONLY) va_list ap; #endif size_t str_l = 0; const char *p = fmt; /* In contrast with POSIX, the ISO C99 now says * that str can be NULL and str_m can be 0. * This is more useful than the old: if (str_m < 1) return -1; */ #if defined(NEED_SNPRINTF_ONLY) va_start(ap, fmt); #endif if (!p) p = ""; while (*p) { if (*p != '%') { /* if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient */ /* but the following code achieves better performance for cases * where format string is long and contains few conversions */ const char *q = strchr(p+1,'%'); size_t n = !q ? strlen(p) : (q-p); if (str_l < str_m) { size_t avail = str_m-str_l; fast_memcpy(str+str_l, p, (n>avail?avail:n)); } p += n; str_l += n; } else { const char *starting_p; size_t min_field_width = 0, precision = 0; int zero_padding = 0, precision_specified = 0, justify_left = 0; int alternate_form = 0, force_sign = 0; int space_for_positive = 1; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored. */ char length_modifier = '\0'; /* allowed values: \0, h, l, L */ char tmp[32];/* temporary buffer for simple numeric->string conversion */ const char *str_arg; /* string address in case of string argument */ size_t str_arg_l; /* natural field width of arg without padding and sign */ unsigned char uchar_arg; /* unsigned char argument value - only defined for c conversion. N.B. standard explicitly states the char argument for the c conversion is unsigned */ size_t number_of_zeros_to_pad = 0; /* number of zeros to be inserted for numeric conversions as required by the precision or minimal field width */ size_t zero_padding_insertion_ind = 0; /* index into tmp where zero padding is to be inserted */ char fmt_spec = '\0'; /* current conversion specifier character */ str_arg = credits;/* just to make compiler happy (defined but not used)*/ str_arg = NULL; starting_p = p; p++; /* skip '%' */ /* parse flags */ while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '\'') { switch (*p) { case '0': zero_padding = 1; break; case '-': justify_left = 1; break; case '+': force_sign = 1; space_for_positive = 0; break; case ' ': force_sign = 1; /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */ #ifdef PERL_COMPATIBLE /* ... but in Perl the last of ' ' and '+' applies */ space_for_positive = 1; #endif break; case '#': alternate_form = 1; break; case '\'': break; } p++; } /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */ /* parse field width */ if (*p == '*') { int j; p++; j = va_arg(ap, int); if (j >= 0) min_field_width = j; else { min_field_width = -j; justify_left = 1; } } else if (isdigit((int)(*p))) { /* size_t could be wider than unsigned int; make sure we treat argument like common implementations do */ unsigned int uj = *p++ - '0'; while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); min_field_width = uj; } /* parse precision */ if (*p == '.') { p++; precision_specified = 1; if (*p == '*') { int j = va_arg(ap, int); p++; if (j >= 0) precision = j; else { precision_specified = 0; precision = 0; /* NOTE: * Solaris 2.6 man page claims that in this case the precision * should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page * claim that this case should be treated as unspecified precision, * which is what we do here. */ } } else if (isdigit((int)(*p))) { /* size_t could be wider than unsigned int; make sure we treat argument like common implementations do */ unsigned int uj = *p++ - '0'; while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); precision = uj; } } /* parse 'h', 'l' and 'll' length modifiers */ if (*p == 'h' || *p == 'l') { length_modifier = *p; p++; if (length_modifier == 'l' && *p == 'l') { /* double l = long long */ #ifdef SNPRINTF_LONGLONG_SUPPORT length_modifier = '2'; /* double l encoded as '2' */ #else length_modifier = 'l'; /* treat it as a single 'l' */ #endif p++; } } fmt_spec = *p; /* common synonyms: */ switch (fmt_spec) { case 'i': fmt_spec = 'd'; break; case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; default: break; } /* get parameter value, do initial processing */ switch (fmt_spec) { case '%': /* % behaves similar to 's' regarding flags and field widths */ case 'c': /* c behaves similar to 's' regarding flags and field widths */ case 's': length_modifier = '\0'; /* wint_t and wchar_t not supported */ /* the result of zero padding flag with non-numeric conversion specifier*/ /* is undefined. Solaris and HPUX 10 does zero padding in this case, */ /* Digital Unix and Linux does not. */ #if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE) zero_padding = 0; /* turn zero padding off for string conversions */ #endif str_arg_l = 1; switch (fmt_spec) { case '%': str_arg = p; break; case 'c': { int j = va_arg(ap, int); uchar_arg = (unsigned char) j; /* standard demands unsigned char */ str_arg = (const char *) &uchar_arg; break; } case 's': str_arg = va_arg(ap, const char *); if (!str_arg) str_arg_l = 0; /* make sure not to address string beyond the specified precision !!! */ else if (!precision_specified) str_arg_l = strlen(str_arg); /* truncate string if necessary as requested by precision */ else if (precision == 0) str_arg_l = 0; else { /* memchr on HP does not like n > 2^31 !!! */ const char *q = memchr(str_arg, '\0', precision <= 0x7fffffff ? precision : 0x7fffffff); str_arg_l = !q ? precision : (q-str_arg); } break; default: break; } break; case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': { /* NOTE: the u, o, x, X and p conversion specifiers imply the value is unsigned; d implies a signed value */ int arg_sign = 0; /* 0 if numeric argument is zero (or if pointer is NULL for 'p'), +1 if greater than zero (or nonzero for unsigned arguments), -1 if negative (unsigned argument is never negative) */ int int_arg = 0; unsigned int uint_arg = 0; /* only defined for length modifier h, or for no length modifiers */ long int long_arg = 0; unsigned long int ulong_arg = 0; /* only defined for length modifier l */ void *ptr_arg = NULL; /* pointer argument value -only defined for p conversion */ #ifdef SNPRINTF_LONGLONG_SUPPORT long long int long_long_arg = 0; unsigned long long int ulong_long_arg = 0; /* only defined for length modifier ll */ #endif if (fmt_spec == 'p') { /* HPUX 10: An l, h, ll or L before any other conversion character * (other than d, i, u, o, x, or X) is ignored. * Digital Unix: * not specified, but seems to behave as HPUX does. * Solaris: If an h, l, or L appears before any other conversion * specifier (other than d, i, u, o, x, or X), the behavior * is undefined. (Actually %hp converts only 16-bits of address * and %llp treats address as 64-bit data which is incompatible * with (void *) argument on a 32-bit system). */ #ifdef SOLARIS_COMPATIBLE # ifdef SOLARIS_BUG_COMPATIBLE /* keep length modifiers even if it represents 'll' */ # else if (length_modifier == '2') length_modifier = '\0'; # endif #else length_modifier = '\0'; #endif ptr_arg = va_arg(ap, void *); if (ptr_arg != NULL) arg_sign = 1; } else if (fmt_spec == 'd') { /* signed */ switch (length_modifier) { case '\0': case 'h': /* It is non-portable to specify a second argument of char or short * to va_arg, because arguments seen by the called function * are not char or short. C converts char and short arguments * to int before passing them to a function. */ int_arg = va_arg(ap, int); if (int_arg > 0) arg_sign = 1; else if (int_arg < 0) arg_sign = -1; break; case 'l': long_arg = va_arg(ap, long int); if (long_arg > 0) arg_sign = 1; else if (long_arg < 0) arg_sign = -1; break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': long_long_arg = va_arg(ap, long long int); if (long_long_arg > 0) arg_sign = 1; else if (long_long_arg < 0) arg_sign = -1; break; #endif } } else { /* unsigned */ switch (length_modifier) { case '\0': case 'h': uint_arg = va_arg(ap, unsigned int); if (uint_arg) arg_sign = 1; break; case 'l': ulong_arg = va_arg(ap, unsigned long int); if (ulong_arg) arg_sign = 1; break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': ulong_long_arg = va_arg(ap, unsigned long long int); if (ulong_long_arg) arg_sign = 1; break; #endif } } str_arg = tmp; str_arg_l = 0; /* NOTE: * For d, i, u, o, x, and X conversions, if precision is specified, * the '0' flag should be ignored. This is so with Solaris 2.6, * Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. */ #ifndef PERL_COMPATIBLE if (precision_specified) zero_padding = 0; #endif if (fmt_spec == 'd') { if (force_sign && arg_sign >= 0) tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; /* leave negative numbers for sprintf to handle, to avoid handling tricky cases like (short int)(-32768) */ #ifdef LINUX_COMPATIBLE } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) { tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; #endif } else if (alternate_form) { if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; } /* alternate form should have no effect for p conversion, but ... */ #ifdef HPUX_COMPATIBLE else if (fmt_spec == 'p' /* HPUX 10: for an alternate form of p conversion, * a nonzero result is prefixed by 0x. */ #ifndef HPUX_BUG_COMPATIBLE /* Actually it uses 0x prefix even for a zero value. */ && arg_sign != 0 #endif ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; } #endif } zero_padding_insertion_ind = str_arg_l; if (!precision_specified) precision = 1; /* default precision is 1 */ if (precision == 0 && arg_sign == 0 #if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE) && fmt_spec != 'p' /* HPUX 10 man page claims: With conversion character p the result of * converting a zero value with a precision of zero is a null string. * Actually HP returns all zeroes, and Linux returns "(nil)". */ #endif ) { /* converted to null string */ /* When zero value is formatted with an explicit precision 0, the resulting formatted string is empty (d, i, u, o, x, X, p). */ } else { char f[5]; int f_l = 0; f[f_l++] = '%'; /* construct a simple format string for sprintf */ if (!length_modifier) { } else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; } else f[f_l++] = length_modifier; f[f_l++] = fmt_spec; f[f_l++] = '\0'; if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg); else if (fmt_spec == 'd') { /* signed */ switch (length_modifier) { case '\0': case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg); break; case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break; #endif } } else { /* unsigned */ switch (length_modifier) { case '\0': case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg); break; case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break; #ifdef SNPRINTF_LONGLONG_SUPPORT case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break; #endif } } /* include the optional minus sign and possible "0x" in the region before the zero padding insertion point */ if (zero_padding_insertion_ind < str_arg_l && tmp[zero_padding_insertion_ind] == '-') { zero_padding_insertion_ind++; } if (zero_padding_insertion_ind+1 < str_arg_l && tmp[zero_padding_insertion_ind] == '0' && (tmp[zero_padding_insertion_ind+1] == 'x' || tmp[zero_padding_insertion_ind+1] == 'X') ) { zero_padding_insertion_ind += 2; } } { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; if (alternate_form && fmt_spec == 'o' #ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */ && (str_arg_l > 0) #endif #ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */ #else /* unless zero is already the first character */ && !(zero_padding_insertion_ind < str_arg_l && tmp[zero_padding_insertion_ind] == '0') #endif ) { /* assure leading zero for alternate-form octal numbers */ if (!precision_specified || precision < num_of_digits+1) { /* precision is increased to force the first character to be zero, except if a zero value is formatted with an explicit precision of zero */ precision = num_of_digits+1; precision_specified = 1; } } /* zero padding to specified precision? */ if (num_of_digits < precision) number_of_zeros_to_pad = precision - num_of_digits; } /* zero padding to specified minimal field width? */ if (!justify_left && zero_padding) { int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); if (n > 0) number_of_zeros_to_pad += n; } break; } default: /* unrecognized conversion specifier, keep format string as-is*/ zero_padding = 0; /* turn zero padding off for non-numeric convers. */ #ifndef DIGITAL_UNIX_COMPATIBLE justify_left = 1; min_field_width = 0; /* reset flags */ #endif #if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE) /* keep the entire format string unchanged */ str_arg = starting_p; str_arg_l = p - starting_p; /* well, not exactly so for Linux, which does something inbetween, * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */ #else /* discard the unrecognized conversion, just keep * * the unrecognized conversion character */ str_arg = p; str_arg_l = 0; #endif if (*p) str_arg_l++; /* include invalid conversion specifier unchanged if not at end-of-string */ break; } if (*p) p++; /* step over the just processed conversion specifier */ /* insert padding to the left as requested by min_field_width; this does not include the zero padding in case of numerical conversions*/ if (!justify_left) { /* left padding with blank or zero */ int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { size_t avail = str_m-str_l; fast_memset(str+str_l, (zero_padding?'0':' '), (n>avail?avail:n)); } str_l += n; } } /* zero padding as requested by the precision or by the minimal field width * for numeric conversions required? */ if (number_of_zeros_to_pad <= 0) { /* will not copy first part of numeric right now, * * force it to be copied later in its entirety */ zero_padding_insertion_ind = 0; } else { /* insert first part of numerics (sign or '0x') before zero padding */ int n = zero_padding_insertion_ind; if (n > 0) { if (str_l < str_m) { size_t avail = str_m-str_l; fast_memcpy(str+str_l, str_arg, (n>avail?avail:n)); } str_l += n; } /* insert zero padding as requested by the precision or min field width */ n = number_of_zeros_to_pad; if (n > 0) { if (str_l < str_m) { size_t avail = str_m-str_l; fast_memset(str+str_l, '0', (n>avail?avail:n)); } str_l += n; } } /* insert formatted string * (or as-is conversion specifier for unknown conversions) */ { int n = str_arg_l - zero_padding_insertion_ind; if (n > 0) { if (str_l < str_m) { size_t avail = str_m-str_l; fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind, (n>avail?avail:n)); } str_l += n; } } /* insert right padding */ if (justify_left) { /* right blank padding to the field width */ int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); if (n > 0) { if (str_l < str_m) { size_t avail = str_m-str_l; fast_memset(str+str_l, ' ', (n>avail?avail:n)); } str_l += n; } } } } #if defined(NEED_SNPRINTF_ONLY) va_end(ap); #endif if (str_m > 0) { /* make sure the string is null-terminated even at the expense of overwriting the last character (shouldn't happen, but just in case) */ str[str_l <= str_m-1 ? str_l : str_m-1] = '\0'; } /* Return the number of characters formatted (excluding trailing null * character), that is, the number of characters that would have been * written to the buffer if it were large enough. * * The value of str_l should be returned, but str_l is of unsigned type * size_t, and snprintf is int, possibly leading to an undetected * integer overflow, resulting in a negative return value, which is illegal. * Both XSH5 and ISO C99 (at least the draft) are silent on this issue. * Should errno be set to EOVERFLOW and EOF returned in this case??? */ return (int) str_l; } #endif harvid-0.8.1/libharvid/timecode.c000066400000000000000000000066021262534667400167210ustar00rootroot00000000000000/* 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.8.1/libharvid/timecode.h000066400000000000000000000040111262534667400167160ustar00rootroot00000000000000/* 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.8.1/libharvid/uthash.h000066400000000000000000001703621262534667400164360ustar00rootroot00000000000000/* Copyright (c) 2003-2014, Troy D. Hanson http://troydhanson.github.com/uthash/ 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. */ #if defined(_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 #elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #define DECLTYPE(x) #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 Pre VS2010 */ #if defined (_WIN32) #if defined(_MSC_VER) && _MSC_VER >= 1600 #include #elif defined(__WATCOMC__) #include #else typedef unsigned int uint32_t; typedef unsigned char uint8_t; #endif #else #include #endif #define UTHASH_VERSION 1.9.9 #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 { \ out=NULL; \ if (head) { \ unsigned _hf_bkt,_hf_hashv; \ 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) #define HASH_BLOOM_BYTELEN 0 #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_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ replaced=NULL; \ HASH_FIND(hh,head,&((add)->fieldname),keylen_in,replaced); \ if (replaced!=NULL) { \ HASH_DELETE(hh,head,replaced); \ }; \ HASH_ADD(hh,head,fieldname,keylen_in,add); \ } while(0) #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 { \ 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 { \ unsigned _hd_bkt; \ _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,(unsigned)strlen(findstr),out) #define HASH_ADD_STR(head,strfield,add) \ HASH_ADD(hh,head,strfield[0],strlen(add->strfield),add) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ HASH_REPLACE(hh,head,strfield[0],(unsigned)strlen(add->strfield),add,replaced) #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_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #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_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #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 { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count; \ char *_prev; \ _count = 0; \ for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ unsigned _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 %u, actual %u\n", \ (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("invalid hh item count %u, actual %u\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 %u, actual %u\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. Note (x<<5+x)=x*33. */ #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) << 5) + (hashv)) + *_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) /* FNV-1a variation */ #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 ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619; \ } \ 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; \ unsigned char *_hj_key=(unsigned 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 { \ unsigned char *_sfh_key=(unsigned 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 = (uint32_t)(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 ^= (uint32_t)(_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; \ if (_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; \ if (_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; \ } \ if (_hs_e) { \ _hs_e->prev = ((_hs_tail) ? \ ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail){ \ _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) #define HASH_OVERHEAD(hh,head) \ ((head) ? ( \ (size_t)((((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ (sizeof(UT_hash_table)) + \ (HASH_BLOOM_BYTELEN)))) : 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.8.1/libharvid/vinfo.c000066400000000000000000000020501262534667400162420ustar00rootroot00000000000000/* 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.8.1/libharvid/vinfo.h000066400000000000000000000033041262534667400162520ustar00rootroot00000000000000/** @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.8.1/src/000077500000000000000000000000001262534667400136035ustar00rootroot00000000000000harvid-0.8.1/src/Makefile000066400000000000000000000056001262534667400152440ustar00rootroot00000000000000ifneq ($(shell which xxd),) LOGODEP=logo.c seek.c else LOGODEP=logo.o seek.o endif include ../common.mak CONFIGTEMP=conf.out ifeq ($(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --exists libavcodec libavformat libavutil libswscale || echo no), no) $(error "http://ffmpeg.org is required - install libavcodec-dev, libswscale-dev, etc") endif ifeq ($(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) 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 ifneq ($(shell which xxd),) FLAGS+=-DXXDI logo.c: ../doc/harvid.jpg xxd -i ../doc/harvid.jpg > logo.c seek.c: ../doc/seek.js xxd -i ../doc/seek.js > seek.c logo.o: ../doc/harvid.jpg xxd -i ../doc/harvid.jpg | $(CC) -x c - -c -o logo.o seek.o: ../doc/seek.js xxd -i ../doc/seek.js | $(CC) -x c - -c -o seek.o else 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 endif 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.c logo.o seek.c 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.8.1/src/daemon_log.c000066400000000000000000000062631262534667400160620ustar00rootroot00000000000000/* 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.8.1/src/daemon_log.h000066400000000000000000000031221262534667400160560ustar00rootroot00000000000000/** @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.8.1/src/daemon_util.c000066400000000000000000000067361262534667400162630ustar00rootroot00000000000000/* 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 #include "daemon_util.h" #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; } uid_t resolve_uid(const char *setuid_user) { #ifndef HAVE_WINDOWS int 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 = 0; } if (uid == 0) { dlog(DLOG_CRIT, "SYS: invalid username '%s'\n", setuid_user); } return uid; #else return 0; #endif } gid_t 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 = 0; } if (gid == 0) { dlog(DLOG_CRIT, "SYS: invalid group '%s'\n", setgid_group); } return gid; #else return 0; #endif } /* set process user and group(s) id */ int drop_privileges(const uid_t uid, const gid_t 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.8.1/src/daemon_util.h000066400000000000000000000037511262534667400162620ustar00rootroot00000000000000/** @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 #ifdef HAVE_WINDOWS typedef int gid_t; typedef int uid_t; #else #include #endif /** * 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 0 on error, uid otherwise (root cannot be looked up) */ uid_t resolve_uid(const char *setuid_user); /** * resolve unix-group-name or ID to integer * @param setgid_group unix group name or ID * @return 0 on error, gid otherwise (root cannot be looked up) */ gid_t 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 gid_t uid, const gid_t 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.8.1/src/enums.h000066400000000000000000000021601262534667400151020ustar00rootroot00000000000000/* 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.8.1/src/favicon.h000066400000000000000000000107761262534667400154140ustar00rootroot00000000000000/* 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.8.1/src/fileindex.c000066400000000000000000000261331262534667400157230ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2014 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 && ( !strcasecmp(&name[l4], ".avi") || !strcasecmp(&name[l4], ".mov") || !strcasecmp(&name[l4], ".ogg") || !strcasecmp(&name[l4], ".ogv") || !strcasecmp(&name[l4], ".mpg") || !strcasecmp(&name[l4], ".mov") || !strcasecmp(&name[l4], ".mp4") || !strcasecmp(&name[l4], ".mkv") || !strcasecmp(&name[l4], ".vob") || !strcasecmp(&name[l4], ".asf") || !strcasecmp(&name[l4], ".avs") || !strcasecmp(&name[l4], ".dts") || !strcasecmp(&name[l4], ".flv") || !strcasecmp(&name[l4], ".m4v") )) || (l5 > 0 && ( !strcasecmp(&name[l5], ".h264") || !strcasecmp(&name[l5], ".webm") )) || (l6 > 0 && ( !strcasecmp(&name[l6], ".dirac") )) || (l9 > 0 && ( !strcasecmp(&name[l9], ".matroska") )) || (l3 > 0 && ( !strcasecmp(&name[l3], ".dv") || !strcasecmp(&name[l3], ".ts") )) ) { 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.8.1/src/harvid.c000066400000000000000000001041311262534667400152240ustar00rootroot00000000000000/* harvid -- http ardour video daemon Copyright (C) 2008-2014 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 one to enable or disable /seek and /index\n" "http-handlers and change their respone: 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; uid_t cfg_uid; gid_t cfg_gid; #ifdef HAVE_WINDOWS char *docroot = "C:\\"; #else char *docroot = "/"; #endif 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_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, "

    Harvid - HTTP Ardour Video Daemon

    \n"); 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.8.1/src/htmlconst.h000066400000000000000000000016261262534667400157740ustar00rootroot00000000000000 #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.8.1/src/htmlseek.c000066400000000000000000000102121262534667400155570ustar00rootroot00000000000000#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.8.1/src/httprotocol.c000066400000000000000000000352531262534667400163400ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2002,2003,2008-2014 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.8.1/src/httprotocol.h000066400000000000000000000071771262534667400163510ustar00rootroot00000000000000/** @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 #ifdef HAVE_WINDOWS #define SL_SEP(string) (strlen(string)>0?(string[strlen(string)-1]=='/' || string[strlen(string)-1]=='\\')?"":"\\":"") #else #define SL_SEP(string) (strlen(string)>0?(string[strlen(string)-1]=='/')?"":"/":"") #endif #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.8.1/src/ics_handler.c000066400000000000000000000352031262534667400162250ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2014 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 = AV_PIX_FMT_YUV420P;} else if (!strcmp(val, "yuv420")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_YUV420P;} else if (!strcmp(val, "yuv440")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_YUV440P;} else if (!strcmp(val, "yuv422")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_YUYV422;} else if (!strcmp(val, "uyv422")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_UYVY422;} else if (!strcmp(val, "rgb")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_RGB24;} else if (!strcmp(val, "bgr")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_BGR24;} else if (!strcmp(val, "rgba")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_RGBA;} else if (!strcmp(val, "argb")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_PIX_FMT_ARGB;} else if (!strcmp(val, "bgra")) {qps->a->render_fmt = FMT_RAW; qps->a->decode_fmt = AV_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 = AV_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); #ifdef HAVE_WINDOWS char *tmp; while (tmp = strchr(a->file_name, '/')) *tmp = '\\'; #endif 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 XXDI #define EXTLD(NAME) \ extern const unsigned char ___ ## NAME []; \ extern const unsigned int ___ ## NAME ## _len; #define LDVAR(NAME) ___ ## NAME #define LDLEN(NAME) ___ ## NAME ## _len #elif defined __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); #ifdef HAVE_WINDOWS char *tmp; while (tmp = strchr(abspath, '/')) *tmp = '\\'; #endif 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 { debugmsg(DEBUG_ICS, "indexing dir: '%s'\n", abspath); 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(qps.fn); } free(abspath); 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", "Nonexistent 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.8.1/src/ics_handler.h000066400000000000000000000024501262534667400162300ustar00rootroot00000000000000/* 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.8.1/src/image_format.c000066400000000000000000000146631262534667400164130ustar00rootroot00000000000000/* This file is part of harvid Copyright (C) 2008-2014 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" #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 HAVE_WINDOWS char tfn[64] = ""; #endif #ifdef __USE_XOPEN2K8 size_t rs = 0; FILE *x = open_memstream((char**) out, &rs); #else FILE *x = tmpfile(); #endif #ifdef HAVE_WINDOWS if (!x) { // wine and ancient version of windows don't support tmpfile() // srand is per thread :( struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_sec * tv.tv_usec * 100000); snprintf(tfn, sizeof(tfn), "harvid.tmp.%d", rand()); x = fopen(tfn, "w+b"); // we should really use open(tfn, O_RDWR | O_CREAT | O_EXCL, 0600); and fdopen } #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 if (strlen(tfn) > 0) { fclose(x); x = fopen(tfn, "rb"); } else { fflush(x); } #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 if (strlen(tfn) > 0) { 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.8.1/src/image_format.h000066400000000000000000000024261262534667400164120ustar00rootroot00000000000000/* 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.8.1/src/socket_server.c000066400000000000000000000307121262534667400166300ustar00rootroot00000000000000/* Copyright (C) 2002,2003,2008-2014 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(struct sockaddr_in)); 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; #ifdef HAVE_WINDOWS WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); if((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) #else if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) #endif { 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); #ifdef HAVE_WINDOWS WSACleanup(); #endif return(rv); } // tcp server thread int start_tcp_server (const unsigned int hostnl, const unsigned short port, const char *docroot, const uid_t uid, const gid_t 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.8.1/src/socket_server.h000066400000000000000000000126011262534667400166320ustar00rootroot00000000000000/** @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 #ifdef HAVE_WINDOWS typedef int gid_t; typedef int uid_t; #else #include #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 uid_t uid; ///< drop privileges, assume this userid gid_t 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 uid_t uid, const gid_t 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