pax_global_header00006660000000000000000000000064130364337460014523gustar00rootroot0000000000000052 comment=41e62a56febd0b59acf1c29f022d1d130e7af855 vdr-plugin-vnsiserver-1.5.2/000077500000000000000000000000001303643374600160235ustar00rootroot00000000000000vdr-plugin-vnsiserver-1.5.2/.gitignore000066400000000000000000000001421303643374600200100ustar00rootroot00000000000000.dependencies *.o *.so *~ po/*.pot po/*.mo .kdev4/ # Don't add patch files *.diff *.patch *.orig vdr-plugin-vnsiserver-1.5.2/COPYING000066400000000000000000000432541303643374600170660ustar00rootroot00000000000000 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. vdr-plugin-vnsiserver-1.5.2/HISTORY000066400000000000000000000047261303643374600171200ustar00rootroot00000000000000VDR Plugin 'vnsiserver' Revision History ---------------------------------------- 2017-01-14: Version 1.5.2 - group recordings - fixes for epg updates - fixes for searchtimers - see github history 2016-09-25: Version 1.5.1 - see github history for fixes - added new setup parameter DisableCamBlacklist: avoids a VDR hard-coded 15 seconds timeout if tuning failed due to hard-coded scramble timeout 2016-08-01: Version 1.5.0 - support for vdr 2.3.1 - add epg search timers - support for h265 (hevc) - fix PID change - fixes and improvements for handling of CAMs - see github history for other fixes 2015-09-18: Version 1.3.0 - add support for undelete recordings - add support for rds data if present on dvb radio and send to addon if supported there - Update support for wirbelscan plugin - Rework Licence files to use KODI - Improved README - Fix possible seq fault in void cRecPlayer::reScan() - fix picons for western - make avoid EPG scan while streaming default - see github history for other fixes 2015-01-25: Version 1.2.1 - add cmd line switch for setting tcp port - fix potential segfault on stop streaming - stop triggering epg updates if client does not fetch epg 2014-09-03: Version 1.2.0 - add cmd line switch "-d". if set, vnsi creates a dummy dvb device which makes femon work on all channels - add setup parameter AvoidEPGScan. if set to 1, vnsi disables EPG scan while streaming. EPG scan during streaming can lead to artifacs on some dual tuner cards. - fix div by zero on AAC audio, thanks to Jakob Sloth Jepsen 2014-03-20: Version 1.1.0 - add support for picons - add setup paramter to select if current recording should start when tuning to a channel - only activate AAC mux workaround if environment variable VNSI_AAC_MUXMODE is set - bump max timshift buffer RAM to 8 gig 2014-03-20: Version 1.0.0 - release 1.0 - various fixes - remove suffix from plugin name vnsiserver5 -> vnsiserver 2014-01-08: Version 0.9.4 - update length of recorings in progress while playing 2013-12-04: Version 0.9.3 - add support for EDL (marks) - add channel filter - send buffer times for timeshift - bump protocol to XBMC to 5 - suffix plugin with version of protocol: vnsiserver5 - this version is compatible with XBMC 13 2013-02-03: Version 0.9.1 - most parts of the plugin rewritten - revised parsers - proper handling of PMT changes - suffix plugin with version of protocol: vnsiserver3 - this version is compatible with XBMC 12.0 2010-03-23: Version 0.0.1 - Initial revision. vdr-plugin-vnsiserver-1.5.2/Makefile000066400000000000000000000076011303643374600174670ustar00rootroot00000000000000# # Makefile for a Video Disk Recorder plugin # # $Id$ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. # By default the main source file also carries this name. PLUGIN = vnsiserver ### The version number of this plugin (taken from the main source file): VERSION = $(shell grep 'static const char \*VERSION *=' vnsi.h | awk '{ print $$6 }' | sed -e 's/[";]//g') ### The directory environment: # Use package data if installed...otherwise assume we're under the VDR source directory: PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc)) LIBDIR ?= $(call PKGCFG,libdir) LOCDIR = $(call PKGCFG,locdir) PLGCFG = $(call PKGCFG,plgcfg) # TMPDIR ?= /tmp ### The compiler options: export CFLAGS = $(call PKGCFG,cflags) export CXXFLAGS = $(call PKGCFG,cxxflags) ### The version number of VDR's plugin API: APIVERSION = $(call PKGCFG,apiversion) ifeq ($(strip $(APIVERSION)),) APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g') NOCONFIG := 1 endif ### Allow user defined options to overwrite defaults: -include $(PLGCFG) ### The name of the distribution archive: ARCHIVE = $(PLUGIN)-$(VERSION) PACKAGE = vdr-$(ARCHIVE) ### The name of the shared object file: SOFILE = libvdr-$(PLUGIN).so ### Includes and Defines (add further entries here): ifdef API1733 INCLUDES += -I$(VDRSRC)/include endif DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DVNSI_SERVER_VERSION='"$(VERSION)"' CXXFLAGS += -std=c++11 export CXXFLAGS ifeq ($(DEBUG),1) DEFINES += -DDEBUG endif ### The object files (add further files here): OBJS = vnsi.o bitstream.o vnsiclient.o channelscancontrol.o config.o cxsocket.o parser.o parser_AAC.o \ parser_AC3.o parser_DTS.o parser_h264.o parser_hevc.o parser_MPEGAudio.o parser_MPEGVideo.o \ parser_Subtitle.o parser_Teletext.o streamer.o recplayer.o requestpacket.o responsepacket.o \ vnsiserver.o hash.o recordingscache.o setup.o vnsiosd.o demuxer.o videobuffer.o \ videoinput.o channelfilter.o status.o vnsitimer.o ### The main target: ifdef API1733 all: install-lib else all: $(SOFILE) endif # ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Internationalization (I18N): PODIR = po I18Npo = $(wildcard $(PODIR)/*.po) I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file)))) I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) I18Npot = $(PODIR)/$(PLUGIN).pot %.mo: %.po msgfmt -c -o $@ $< $(I18Npot): $(wildcard *.c) xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='' -o $@ `ls $^` %.po: $(I18Npot) msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $< @touch $@ $(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo install -D -m644 $< $@ .PHONY: i18n i18n: $(I18Nmo) $(I18Npot) install-i18n: $(I18Nmsgs) ### Targets: $(SOFILE): $(OBJS) $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ install-lib: $(SOFILE) install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION) install: install-lib install-i18n dist: $(I18Npo) clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE) @cp -a * $(TMPDIR)/$(ARCHIVE) @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE) @echo Distribution package created as $(PACKAGE).tgz clean: @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ compile: $(SOFILE) vdr-plugin-vnsiserver-1.5.2/README000066400000000000000000000031701303643374600167040ustar00rootroot00000000000000 ----------------------------------------------------------------------------- 1. Introduction ----------------------------------------------------------------------------- This is a "plugin" for the Video Disk Recorder (VDR). VDR plugin to handle KODI clients. The vdr-plugin-vnsiserver is able to handle serveral KODI clients connecting via the VNSI addon. 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. See the file COPYING for more information. Latest version available at: https://github.com/FernetMenta/vdr-plugin-vnsiserver ----------------------------------------------------------------------------- 2. Compile in VDR's source tree ----------------------------------------------------------------------------- Get VDR sources. $ cd /PLUGINS/src $ git clone https://github.com/FernetMenta/vdr-plugin-vnsiserver $ ln -s vdr-plugin-vnsiserver vnsiserver On newer VDR Versions (2.2.0) plugin can be build and installed alone, use: $ git clone https://github.com/FernetMenta/vdr-plugin-vnsiserver $ cd vdr-plugin-vnsiserver $ make $ sudo make install If you want to fix install dir you can use this, where the end define the VDR library folder: $ sudo make install LIBDIR=/usr/lib/vdr ----------------------------------------------------------------------------- 3. Help and Suppport ----------------------------------------------------------------------------- http://forum.kodi.tv/forumdisplay.php?fid=169 vdr-plugin-vnsiserver-1.5.2/bitstream.c000066400000000000000000000054221303643374600201640ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "bitstream.h" void cBitstream::skipBits(unsigned int num) { if (m_doEP3) { register unsigned int tmp; while (num) { tmp = m_offset >> 3; if (!(m_offset & 7) && (m_data[tmp--] == 3) && (m_data[tmp--] == 0) && (m_data[tmp] == 0)) m_offset += 8; // skip EP3 byte if (!(m_offset & 7) && (num >= 8)) // byte boundary, speed up things a little bit { m_offset += 8; num -= 8; } else if ((tmp = 8-(m_offset & 7)) <= num) // jump to byte boundary { m_offset += tmp; num -= tmp; } else { m_offset += num; num = 0; } if (m_offset >= m_len) { m_error = true; break; } } return; } m_offset += num; } unsigned int cBitstream::readBits(int num) { unsigned int r = 0; while(num > 0) { if (m_doEP3) { size_t tmp = m_offset >> 3; if (!(m_offset & 7) && (m_data[tmp--] == 3) && (m_data[tmp--] == 0) && (m_data[tmp] == 0)) m_offset += 8; // skip EP3 byte } if(m_offset >= m_len) { m_error = true; return 0; } num--; if(m_data[m_offset / 8] & (1 << (7 - (m_offset & 7)))) r |= 1 << num; m_offset++; } return r; } unsigned int cBitstream::showBits(int num) { unsigned int r = 0; size_t offs = m_offset; while(num > 0) { if(offs >= m_len) { m_error = true; return 0; } num--; if(m_data[offs / 8] & (1 << (7 - (offs & 7)))) r |= 1 << num; offs++; } return r; } unsigned int cBitstream::readGolombUE(int maxbits) { int lzb = -1; int bits = 0; for(int b = 0; !b; lzb++, bits++) { if (bits > maxbits) return 0; b = readBits1(); } return (1 << lzb) - 1 + readBits(lzb); } signed int cBitstream::readGolombSE() { int v, pos; v = readGolombUE(); if(v == 0) return 0; pos = (v & 1); v = (v + 1) >> 1; return pos ? v : -v; } vdr-plugin-vnsiserver-1.5.2/bitstream.h000066400000000000000000000035771303643374600202020ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_BITSTREAM_H #define VNSI_BITSTREAM_H #include #include class cBitstream { private: uint8_t *const m_data; size_t m_offset = 0; const size_t m_len; bool m_error = false; const bool m_doEP3 = false; public: constexpr cBitstream(uint8_t *data, size_t bits) :m_data(data), m_len(bits) { } // this is a bitstream that has embedded emulation_prevention_three_byte // sequences that need to be removed as used in HECV. // Data must start at byte 2 constexpr cBitstream(uint8_t *data, size_t bits, bool doEP3) :m_data(data), m_offset(16), // skip header and use as sentinel for EP3 detection m_len(bits), m_doEP3(true) { } void skipBits(unsigned int num); unsigned int readBits(int num); unsigned int showBits(int num); unsigned int readBits1() { return readBits(1); } unsigned int readGolombUE(int maxbits = 32); signed int readGolombSE(); constexpr size_t length() const { return m_len; } constexpr bool isError() const { return m_error; } }; #endif // VNSI_BITSTREAM_H vdr-plugin-vnsiserver-1.5.2/channelfilter.c000066400000000000000000000172661303643374600210210ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2013 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "channelfilter.h" #include "config.h" #include "hash.h" #include #include #include #include #include cVNSIProvider::cVNSIProvider() :m_caid(0) { } cVNSIProvider::cVNSIProvider(const cVNSIProvider &provider) :m_name(provider.m_name), m_caid(provider.m_caid) { } cVNSIProvider::cVNSIProvider(std::string name, int caid) :m_name(name), m_caid(caid) { }; bool cVNSIProvider::operator==(const cVNSIProvider &rhs) const { if (rhs.m_caid != m_caid) return false; if (m_name.empty()) return false; if (rhs.m_name.compare(m_name) != 0) return false; return true; } bool cVNSIChannelFilter::IsRadio(const cChannel* channel) { bool isRadio = false; // assume channels without VPID & APID are video channels if (channel->Vpid() == 0 && channel->Apid(0) == 0) isRadio = false; // channels without VPID are radio channels (channels with VPID 1 are encrypted radio channels) else if (channel->Vpid() == 0 || channel->Vpid() == 1 || channel->Rid() == 1) isRadio = true; return isRadio; } void cVNSIChannelFilter::Load() { cMutexLock lock(&m_Mutex); cString filename; std::string line; std::ifstream rfile; filename = cString::sprintf("%s/videowhitelist.vnsi", *VNSIServerConfig.ConfigDirectory); m_providersVideo.clear(); rfile.open(filename); if (rfile.is_open()) { while(std::getline(rfile,line)) { cVNSIProvider provider; size_t pos = line.find("|"); if(pos == line.npos) { provider.m_name = line; } else { provider.m_name.assign(line, 0, pos); provider.m_caid = strtol(line.c_str() + pos + 1, nullptr, 10); } auto p_it = std::find(m_providersVideo.begin(), m_providersVideo.end(), provider); if(p_it == m_providersVideo.end()) { m_providersVideo.emplace_back(std::move(provider)); } } rfile.close(); } filename = cString::sprintf("%s/radiowhitelist.vnsi", *VNSIServerConfig.ConfigDirectory); rfile.open(filename); m_providersRadio.clear(); if (rfile.is_open()) { while(std::getline(rfile,line)) { cVNSIProvider provider; auto pos = line.find("|"); if(pos == line.npos) { provider.m_name = line; } else { provider.m_name.assign(line, 0, pos); provider.m_caid = strtol(line.c_str() + pos + 1, nullptr, 10); } auto p_it = std::find(m_providersRadio.begin(), m_providersRadio.end(), provider); if(p_it == m_providersRadio.end()) { m_providersRadio.emplace_back(std::move(provider)); } } rfile.close(); } filename = cString::sprintf("%s/videoblacklist.vnsi", *VNSIServerConfig.ConfigDirectory); rfile.open(filename); m_channelsVideo.clear(); if (rfile.is_open()) { while(getline(rfile,line)) { int id = strtol(line.c_str(), nullptr, 10); m_channelsVideo.insert(id); } rfile.close(); } filename = cString::sprintf("%s/radioblacklist.vnsi", *VNSIServerConfig.ConfigDirectory); rfile.open(filename); m_channelsRadio.clear(); if (rfile.is_open()) { while(getline(rfile,line)) { int id = strtol(line.c_str(), nullptr, 10); m_channelsRadio.insert(id); } rfile.close(); } } void cVNSIChannelFilter::StoreWhitelist(bool radio) { cMutexLock lock(&m_Mutex); cString filename; std::ofstream wfile; std::vector *whitelist; if (radio) { filename = cString::sprintf("%s/radiowhitelist.vnsi", *VNSIServerConfig.ConfigDirectory); whitelist = &m_providersRadio; } else { filename = cString::sprintf("%s/videowhitelist.vnsi", *VNSIServerConfig.ConfigDirectory); whitelist = &m_providersVideo; } wfile.open(filename); if(wfile.is_open()) { for (const auto i : *whitelist) { wfile << i.m_name << '|' << i.m_caid << '\n'; } wfile.close(); } SortChannels(); } void cVNSIChannelFilter::StoreBlacklist(bool radio) { cMutexLock lock(&m_Mutex); cString filename; std::ofstream wfile; std::set *blacklist; if (radio) { filename = cString::sprintf("%s/radioblacklist.vnsi", *VNSIServerConfig.ConfigDirectory); blacklist = &m_channelsRadio; } else { filename = cString::sprintf("%s/videoblacklist.vnsi", *VNSIServerConfig.ConfigDirectory); blacklist = &m_channelsVideo; } wfile.open(filename); if(wfile.is_open()) { for (const auto i : *blacklist) { wfile << i << '\n'; } wfile.close(); } SortChannels(); } bool cVNSIChannelFilter::IsWhitelist(const cChannel &channel) { cVNSIProvider provider; std::vector *providers; provider.m_name = channel.Provider(); if (IsRadio(&channel)) providers = &m_providersRadio; else providers = &m_providersVideo; if(providers->empty()) return true; if (channel.Ca(0) == 0) { provider.m_caid = 0; auto p_it = std::find(providers->begin(), providers->end(), provider); if(p_it!=providers->end()) return true; else return false; } int caid; int idx = 0; while((caid = channel.Ca(idx)) != 0) { provider.m_caid = caid; auto p_it = std::find(providers->begin(), providers->end(), provider); if(p_it!=providers->end()) return true; idx++; } return false; } bool cVNSIChannelFilter::PassFilter(const cChannel &channel) { cMutexLock lock(&m_Mutex); if(channel.GroupSep()) return true; if (!IsWhitelist(channel)) return false; if (IsRadio(&channel)) { auto it = std::find(m_channelsRadio.begin(), m_channelsRadio.end(), CreateChannelUID(&channel)); if(it!=m_channelsRadio.end()) return false; } else { auto it = std::find(m_channelsVideo.begin(), m_channelsVideo.end(), CreateChannelUID(&channel)); if(it!=m_channelsVideo.end()) return false; } return true; } void cVNSIChannelFilter::SortChannels() { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_WRITE; for (cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else Channels.IncBeingEdited(); Channels.Lock(true); for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { if(!PassFilter(*channel)) { #if VDRVERSNUM >= 20301 for (cChannel *whitechan = Channels->Next(channel); whitechan; whitechan = Channels->Next(whitechan)) #else for (cChannel *whitechan = Channels.Next(channel); whitechan; whitechan = Channels.Next(whitechan)) #endif { if(PassFilter(*whitechan)) { #if VDRVERSNUM >= 20301 Channels->Move(whitechan, channel); #else Channels.Move(whitechan, channel); #endif channel = whitechan; break; } } } } #if VDRVERSNUM >= 20301 Channels->SetModifiedByUser(); #else Channels.SetModified(true); Channels.Unlock(); Channels.DecBeingEdited(); #endif } cVNSIChannelFilter VNSIChannelFilter; vdr-plugin-vnsiserver-1.5.2/channelfilter.h000066400000000000000000000032311303643374600210110ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2013 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include #include #include #include class cVNSIProvider { public: cVNSIProvider(); cVNSIProvider(std::string name, int caid); cVNSIProvider(const cVNSIProvider &provider); bool operator==(const cVNSIProvider &rhs) const; std::string m_name; int m_caid; }; class cVNSIChannelFilter { public: void Load(); void StoreWhitelist(bool radio); void StoreBlacklist(bool radio); bool IsWhitelist(const cChannel &channel); bool PassFilter(const cChannel &channel); void SortChannels(); static bool IsRadio(const cChannel* channel); std::vector m_providersVideo; std::vector m_providersRadio; std::set m_channelsVideo; std::set m_channelsRadio; cMutex m_Mutex; }; extern cVNSIChannelFilter VNSIChannelFilter; vdr-plugin-vnsiserver-1.5.2/channelscancontrol.c000066400000000000000000000234341303643374600220530ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010,2015 Alwin Esch (Team KODI) * Copyright (C) 2010, 2011 Alexander Pipelka * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "channelscancontrol.h" #include "vnsiclient.h" #include #include using namespace WIRBELSCAN_SERVICE; /*! * versions definitions */ #define MIN_CMDVERSION 1 // command api 0001 #define MIN_STATUSVERSION 1 // query status #define MIN_SETUPVERSION 1 // get/put setup, GetSetup#XXXX/SetSetup#XXXX #define MIN_COUNTRYVERSION 1 // get list of country IDs and Names #define MIN_SATVERSION 1 // get list of sat IDs and Names #define MIN_USERVERSION 1 // scan user defined transponder #define SCAN_TV ( 1 << 0 ) #define SCAN_RADIO ( 1 << 1 ) #define SCAN_FTA ( 1 << 2 ) #define SCAN_SCRAMBLED ( 1 << 3 ) #define SCAN_HD ( 1 << 4 ) /*! * macro definitions */ #define SETSCAN 0 #define SCANNING 1 #define SCANDONE 2 #define CHECKVERSION(a,b,c) p=strchr((char *) m_scanInformation->a,'#') + 1; sscanf(p,"%d ",&version); if (version < b) c = true; #define CHECKLIMITS(a,v,_min,_max,_def) a=v; if ((a<_min) || (a>_max)) a=_def; CScanControl::CScanControl(cVNSIClient *client) : m_client(client), m_isChecked(false), m_isNotSupported(true), m_channelScanPlugin(NULL), m_singleScan(0), m_cbuf(NULL), m_sbuf(NULL), m_scanInformation(NULL) { } CScanControl::~CScanControl() { delete m_scanInformation; free(m_cbuf); free(m_sbuf); } bool CScanControl::IsSupported() { if (!m_isChecked) LoadScanner(); return !m_isNotSupported; } bool CScanControl::LoadScanner() { /*! * Plugin present? */ m_channelScanPlugin = cPluginManager::GetPlugin("wirbelscan"); if (m_channelScanPlugin == NULL) return false; /*! * This plugin version compatibel to wirbelscan? */ char *p; m_scanInformation = new cWirbelscanInfo; if (asprintf(&p, "%s%s", SPlugin, SInfo) < 0 || !m_channelScanPlugin->Service("wirbelscan_GetVersion", m_scanInformation)) { m_client->OsdStatusMessage(*cString::sprintf("%s", tr("Your scanner version doesnt support services - Please upgrade."))); free(p); return false; } free(p); int version = 0; m_isNotSupported = false; CHECKVERSION(CommandVersion,MIN_CMDVERSION, m_isNotSupported); CHECKVERSION(StatusVersion, MIN_STATUSVERSION, m_isNotSupported); CHECKVERSION(SetupVersion, MIN_SETUPVERSION, m_isNotSupported); CHECKVERSION(CountryVersion,MIN_COUNTRYVERSION, m_isNotSupported); CHECKVERSION(SatVersion, MIN_SATVERSION, m_isNotSupported); CHECKVERSION(UserVersion, MIN_USERVERSION, m_isNotSupported); if (m_isNotSupported) { m_client->OsdStatusMessage(*cString::sprintf("%s", tr("Your scanner version is to old - Please upgrade." ))); return false; } /*! * Check which receivers are present */ int cardnr = 0; cDevice *device; memset(&m_receiverSystems, 0, sizeof(m_receiverSystems)); while ((device = cDevice::GetDevice(cardnr++))) { if (device->ProvidesSource(cSource::stTerr)) m_receiverSystems[RECEIVER_SYSTEM_DVB_T] = true; if (device->ProvidesSource(cSource::stCable)) m_receiverSystems[RECEIVER_SYSTEM_DVB_C] = true; if (device->ProvidesSource(cSource::stSat)) m_receiverSystems[RECEIVER_SYSTEM_DVB_S] = true; if (device->ProvidesSource(cSource::stAtsc)) m_receiverSystems[RECEIVER_SYSTEM_ATSC] = true; } if (cPluginManager::GetPlugin("pvrinput")) { m_receiverSystems[RECEIVER_SYSTEM_ANALOG_TV] = true; m_receiverSystems[RECEIVER_SYSTEM_ANALOG_RADIO] = true; } m_countryBuffer.size = 0; m_countryBuffer.count = 0; m_countryBuffer.buffer = NULL; if (asprintf(&p, "%sGet%s", SPlugin, SCountry) < 0) return false; m_channelScanPlugin->Service(p, &m_countryBuffer); // query buffer size. m_cbuf = (SListItem*) malloc(m_countryBuffer.size * sizeof(SListItem)); // now, allocate memory. m_countryBuffer.buffer = m_cbuf; // assign buffer m_channelScanPlugin->Service(p, &m_countryBuffer); // fill buffer with values. free(p); m_satBuffer.size = 0; m_satBuffer.count = 0; m_satBuffer.buffer = NULL; if (asprintf(&p, "%sGet%s", SPlugin, SSat) < 0) return false; m_channelScanPlugin->Service(p, &m_satBuffer); // query buffer size. m_sbuf = (SListItem*) malloc(m_satBuffer.size * sizeof(SListItem)); // now, allocate memory. m_satBuffer.buffer = m_sbuf; // assign buffer m_channelScanPlugin->Service(p, &m_satBuffer); // fill buffer with values. free(p); if (asprintf(&p, "%sGet%s", SPlugin, SSetup) < 0) return false; m_channelScanPlugin->Service(p, &m_setup); free(p); return true; } bool CScanControl::StartScan(sScanServiceData &data) { if (m_isNotSupported) return false; m_finishReported = false; CHECKLIMITS(m_setup.SatId , data.SatIndex , 0 , 0xFFFF, 0); CHECKLIMITS(m_setup.CountryId , data.CountryIndex , 0 , 0xFFFF, 0); CHECKLIMITS(m_setup.DVB_Type , data.type , 0 , 5 , 0); CHECKLIMITS(m_setup.DVBT_Inversion , data.DVBT_Inversion , 0 , 1 , 0); CHECKLIMITS(m_setup.DVBC_Inversion , data.DVBC_Inversion , 0 , 1 , 0); CHECKLIMITS(m_setup.DVBC_Symbolrate , data.DVBC_Symbolrate , 0 , 16 , 0); CHECKLIMITS(m_setup.DVBC_QAM , data.DVBC_QAM , 0 , 4 , 0); CHECKLIMITS(m_setup.ATSC_type , data.ATSC_Type , 0 , 1 , 0); m_setup.scanflags = data.scan_tv ? SCAN_TV : 0; m_setup.scanflags |= data.scan_radio ? SCAN_RADIO : 0; m_setup.scanflags |= data.scan_scrambled ? SCAN_SCRAMBLED : 0; m_setup.scanflags |= data.scan_fta ? SCAN_FTA : 0; m_setup.scanflags |= data.scan_hd ? SCAN_HD : 0; #if VDRVERSNUM >= 20301 { LOCK_CHANNELS_READ; m_lastChannelCount = Channels->Count(); } #else m_lastChannelCount = Channels.Count(); #endif char *s; if (asprintf(&s, "%sSet%s", SPlugin, SSetup) < 0) return false; m_channelScanPlugin->Service(s, &m_setup); free(s); PutCommand(CmdStartScan); Start();// start polling thread return true; } bool CScanControl::StopScan() { if (m_isNotSupported) return false; PutCommand(CmdStopScan); cCondWait::SleepMs(500); Cancel(2); if (!m_finishReported) { if (m_scanStatus.status != StatusStopped) { m_scanStatus.status = StatusStopped; m_client->processSCAN_SetStatus(m_scanStatus.status); INFOLOG("Stopping scan plugin not reported back and stop maybe not complete confirmed"); } m_client->processSCAN_IsFinished(); } return true; } bool CScanControl::GetCountries(scannerEntryList &list) { for (uint32_t i = 0; i < m_countryBuffer.count; i++) { scannerEntry entry; entry.index = m_countryBuffer.buffer[i].id; entry.name = m_countryBuffer.buffer[i].short_name; entry.longName = m_countryBuffer.buffer[i].full_name; list.push_back(entry); } return !list.empty(); } bool CScanControl::GetSatellites(scannerEntryList &list) { for (uint32_t i = 0; i < m_satBuffer.count; i++) { scannerEntry entry; entry.index = m_satBuffer.buffer[i].id; entry.name = m_satBuffer.buffer[i].short_name; entry.longName = m_satBuffer.buffer[i].full_name; list.push_back(entry); } return !list.empty(); } void CScanControl::PutCommand(WIRBELSCAN_SERVICE::s_cmd command) { cWirbelscanCmd cmd; char * request; if (asprintf(&request, "%s%s", SPlugin, SCommand) < 0) return; cmd.cmd = command; m_channelScanPlugin->Service(request, &cmd); free(request); } void CScanControl::Action(void) { while (Running()) { cCondWait::SleepMs(1000); char * s; if (asprintf(&s, "%sGet%s", SPlugin, SStatus) < 0) return; m_channelScanPlugin->Service(s, &m_scanStatus); free(s); m_client->processSCAN_SetStatus(m_scanStatus.status); m_client->processSCAN_SetPercentage(m_scanStatus.progress); m_client->processSCAN_SetSignalStrength(m_scanStatus.strength, false); m_client->processSCAN_SetDeviceInfo(m_scanStatus.curr_device); m_client->processSCAN_SetTransponder(m_scanStatus.transponder); int noOfChannels; #if VDRVERSNUM >= 20301 { LOCK_CHANNELS_READ; noOfChannels = Channels->Count(); } #else noOfChannels = Channels.Count(); #endif for (int i = 0; i < noOfChannels-m_lastChannelCount; i++) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByNumber(Channels->Count()-i); #else cChannel *channel = Channels.GetByNumber(Channels.Count()-i); #endif m_client->processSCAN_NewChannel(channel->Name(), channel->Vpid() == 0, channel->Ca() > 0, channel->Vtype() > 2); } #if VDRVERSNUM >= 20301 { LOCK_CHANNELS_READ; m_lastChannelCount = Channels->Count(); } #else m_lastChannelCount = Channels.Count(); #endif if (m_scanStatus.status == StatusStopped) { m_client->processSCAN_IsFinished(); m_finishReported = true; break; } } Cancel(0); } vdr-plugin-vnsiserver-1.5.2/channelscancontrol.h000066400000000000000000000067311303643374600220610ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010,2015 Alwin Esch (Team KODI) * Copyright (C) 2010, 2011 Alexander Pipelka * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef __CHANNELSCAN_CONTROL__ #define __CHANNELSCAN_CONTROL__ #include #include #include "wirbelscan_services.h" #define RECEIVER_SYSTEM_DVB_T 0 #define RECEIVER_SYSTEM_DVB_C 1 #define RECEIVER_SYSTEM_DVB_S 2 #define RECEIVER_SYSTEM_ATSC 3 #define RECEIVER_SYSTEM_ANALOG_TV 4 #define RECEIVER_SYSTEM_ANALOG_RADIO 5 typedef enum scantype { DVB_TERR = 0, DVB_CABLE = 1, DVB_SAT = 2, PVRINPUT = 3, PVRINPUT_FM = 4, DVB_ATSC = 5, } scantype_t; struct sScanServiceData { int type; bool scan_tv; bool scan_radio; bool scan_fta; bool scan_scrambled; bool scan_hd; int CountryIndex; int DVBC_Inversion; int DVBC_Symbolrate; int DVBC_QAM; int DVBT_Inversion; int SatIndex; int ATSC_Type; }; struct scannerEntry { int index; const char *name; const char *longName; }; typedef std::vector scannerEntryList; class cVNSIClient; class CScanControl : public cThread { public: CScanControl(cVNSIClient *client); ~CScanControl(); bool IsSupported(); bool StartScan(sScanServiceData &data); bool StopScan(); bool GetCountries(scannerEntryList &list); bool GetSatellites(scannerEntryList &list); bool SupportsDVB_T() { return m_receiverSystems[RECEIVER_SYSTEM_DVB_T]; } bool SupportsDVB_C() { return m_receiverSystems[RECEIVER_SYSTEM_DVB_C]; } bool SupportsDVB_S() { return m_receiverSystems[RECEIVER_SYSTEM_DVB_S]; } bool SupportsAnalogTV() { return m_receiverSystems[RECEIVER_SYSTEM_ANALOG_TV]; } bool SupportsAnalogRadio() { return m_receiverSystems[RECEIVER_SYSTEM_ANALOG_RADIO]; } bool SupportsATSC() { return m_receiverSystems[RECEIVER_SYSTEM_ATSC]; } virtual void Action(); private: bool LoadScanner(); void PutCommand(WIRBELSCAN_SERVICE::s_cmd command); cVNSIClient *m_client; bool m_isChecked; bool m_isNotSupported; bool m_isNotTpSupported; bool m_receiverSystems[6]; cPlugin *m_channelScanPlugin; bool m_singleScan; bool m_finishReported; WIRBELSCAN_SERVICE::SListItem *m_cbuf; WIRBELSCAN_SERVICE::SListItem *m_sbuf; WIRBELSCAN_SERVICE::cPreAllocBuffer m_countryBuffer; WIRBELSCAN_SERVICE::cPreAllocBuffer m_satBuffer; WIRBELSCAN_SERVICE::cWirbelscanInfo *m_scanInformation; WIRBELSCAN_SERVICE::cWirbelscanStatus m_scanStatus; WIRBELSCAN_SERVICE::cWirbelscanScanSetup m_setup; int m_lastChannelCount; }; #endif // __CHANNELSCAN_CONTROL__ vdr-plugin-vnsiserver-1.5.2/config.c000066400000000000000000000024071303643374600174370ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include #include #include #include #include "config.h" cVNSIServerConfig::cVNSIServerConfig() { listen_port = LISTEN_PORT; ConfigDirectory = NULL; stream_timeout = 10; device = false; pDevice = NULL; testStreamActive = false; } /* Global instance */ cVNSIServerConfig VNSIServerConfig; vdr-plugin-vnsiserver-1.5.2/config.h000066400000000000000000000046741303643374600174540ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_CONFIG_H #define VNSI_CONFIG_H #include #include #include // log output configuration #ifdef CONSOLEDEBUG #define INFOLOG(x...) printf("VNSI: " x) #define ERRORLOG(x...) printf("VNSI-Error: " x) #define DEBUGLOG(x...) printf("VNSI-Debug: " x) #else #define INFOLOG(x...) isyslog("VNSI: " x) #define ERRORLOG(x...) esyslog("VNSI-Error: " x) #define DEBUGLOG(x...) (SysLogLevel > 3) ? dsyslog("VNSI-Debug: " x) : void() #endif // default settings #define ALLOWED_HOSTS_FILE "allowed_hosts.conf" #define FRONTEND_DEVICE "/dev/dvb/adapter%d/frontend%d" #define LISTEN_PORT 34890 #define LISTEN_PORT_S "34890" #define DISCOVERY_PORT 34890 // backward compatibility #if APIVERSNUM < 10701 #define FOLDERDELIMCHAR '~' #endif // Error flags #define ERROR_PES_GENERAL 0x01 #define ERROR_PES_SCRAMBLE 0x02 #define ERROR_PES_STARTCODE 0x04 #define ERROR_DEMUX_NODATA 0x10 #define ERROR_CAM_ERROR 0x20 class cVNSIServerConfig { public: cVNSIServerConfig(); // Remote server settings cString ConfigDirectory; // config directory path uint16_t listen_port; // Port of remote server uint16_t stream_timeout; // timeout in seconds for stream data bool device; // true if vnsi should act as dummy device void *pDevice; // pointer to cDvbVnsiDevice cString testStreamFile; // TS file to simulate channel bool testStreamActive; // true if test mode is enabled }; // Global instance extern cVNSIServerConfig VNSIServerConfig; #endif // VNSI_CONFIG_H vdr-plugin-vnsiserver-1.5.2/cxsocket.c000066400000000000000000000076471303643374600200300ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2003-2006 Petri Hintukainen * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ /* * Socket wrapper classes * * Code is taken from xineliboutput plugin. * */ #include "cxsocket.h" #include "config.h" #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include #ifndef MSG_MORE #define MSG_MORE 0 #endif cxSocket::cxSocket(int h) :m_fd(h), m_pollerRead(m_fd), m_pollerWrite(m_fd, true) { } cxSocket::~cxSocket() { close(m_fd); } void cxSocket::Shutdown() { ::shutdown(m_fd, SHUT_RD); } void cxSocket::LockWrite() { m_MutexWrite.Lock(); } void cxSocket::UnlockWrite() { m_MutexWrite.Unlock(); } ssize_t cxSocket::write(const void *buffer, size_t size, int timeout_ms, bool more_data) { cMutexLock CmdLock(&m_MutexWrite); ssize_t written = (ssize_t)size; const unsigned char *ptr = (const unsigned char *)buffer; while (size > 0) { if(!m_pollerWrite.Poll(timeout_ms)) { ERRORLOG("cxSocket::write(fd=%d): poll() failed", m_fd); return written-size; } ssize_t p = ::send(m_fd, ptr, size, (more_data ? MSG_MORE : 0)); if (p <= 0) { if (errno == EINTR || errno == EAGAIN) { DEBUGLOG("cxSocket::write(fd=%d): EINTR during write(), retrying", m_fd); continue; } else if (errno != EPIPE) ERRORLOG("cxSocket::write(fd=%d): write() error", m_fd); return p; } ptr += p; size -= p; } return written; } ssize_t cxSocket::read(void *buffer, size_t size, int timeout_ms) { int retryCounter = 0; ssize_t missing = (ssize_t)size; unsigned char *ptr = (unsigned char *)buffer; while (missing > 0) { if(!m_pollerRead.Poll(timeout_ms)) { ERRORLOG("cxSocket::read(fd=%d): poll() failed at %d/%d", m_fd, (int)(size-missing), (int)size); return size-missing; } ssize_t p = ::read(m_fd, ptr, missing); if (p < 0) { if (retryCounter < 10 && (errno == EINTR || errno == EAGAIN)) { DEBUGLOG("cxSocket::read(fd=%d): EINTR/EAGAIN during read(), retrying", m_fd); retryCounter++; continue; } ERRORLOG("cxSocket::read(fd=%d): read() error at %d/%d", m_fd, (int)(size-missing), (int)size); return 0; } else if (p == 0) { INFOLOG("cxSocket::read(fd=%d): eof, connection closed", m_fd); return 0; } retryCounter = 0; ptr += p; missing -= p; } return size; } char *cxSocket::ip2txt(uint32_t ip, unsigned int port, char *str) { // inet_ntoa is not thread-safe (?) if(str) { unsigned int iph =(unsigned int)ntohl(ip); unsigned int porth =(unsigned int)ntohs(port); if(!porth) sprintf(str, "%d.%d.%d.%d", ((iph>>24)&0xff), ((iph>>16)&0xff), ((iph>>8)&0xff), ((iph)&0xff)); else sprintf(str, "%u.%u.%u.%u:%u", ((iph>>24)&0xff), ((iph>>16)&0xff), ((iph>>8)&0xff), ((iph)&0xff), porth); } return str; } vdr-plugin-vnsiserver-1.5.2/cxsocket.h000066400000000000000000000032631303643374600200230ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2003-2006 Petri Hintukainen * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_CXSOCKET_H #define VNSI_CXSOCKET_H #ifdef __FreeBSD__ #include #endif #include #include #include #include #include class cxSocket { const int m_fd; cMutex m_MutexWrite; cPoller m_pollerRead; cPoller m_pollerWrite; public: cxSocket(int h); ~cxSocket(); cxSocket(const cxSocket &) = delete; cxSocket &operator=(const cxSocket &) = delete; void Shutdown(void); void LockWrite(); void UnlockWrite(); ssize_t read(void *buffer, size_t size, int timeout_ms = -1); ssize_t write(const void *buffer, size_t size, int timeout_ms = -1, bool more_data = false); static char *ip2txt(uint32_t ip, unsigned int port, char *str); }; #endif // VNSI_CXSOCKET_H vdr-plugin-vnsiserver-1.5.2/demuxer.c000066400000000000000000000356341303643374600176530ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "config.h" #include "demuxer.h" #include "parser.h" #include "videobuffer.h" #include #include cStreamInfo::cStreamInfo() { } cStreamInfo::cStreamInfo(const cStreamInfo& info) { pID = info.pID; type = info.type; content = info.content; subtitlingType = info.subtitlingType; compositionPageId = info.compositionPageId; ancillaryPageId = info.ancillaryPageId; handleRDS = info.handleRDS; SetLanguage(info.language); } void cStreamInfo::SetLanguage(const char* lang) { language[0] = lang[0]; language[1] = lang[1]; language[2] = lang[2]; language[3] = 0; } cVNSIDemuxer::cVNSIDemuxer(bool bAllowRDS) : m_bAllowRDS(bAllowRDS) { } cVNSIDemuxer::~cVNSIDemuxer() { } void cVNSIDemuxer::Open(const cChannel &channel, cVideoBuffer *videoBuffer) { cMutexLock lock(&m_Mutex); m_CurrentChannel = channel; m_VideoBuffer = videoBuffer; if (m_CurrentChannel.Vpid()) m_WaitIFrame = true; else m_WaitIFrame = false; m_PtsWrap.m_Wrap = false; m_PtsWrap.m_NoOfWraps = 0; m_PtsWrap.m_ConfirmCount = 0; m_MuxPacketSerial = 0; m_Error = ERROR_DEMUX_NODATA; m_SetRefTime = true; m_seenFirstPacket = false; } void cVNSIDemuxer::Close() { cMutexLock lock(&m_Mutex); for (auto *i : m_Streams) { DEBUGLOG("Deleting stream parser for pid=%i and type=%i", i->GetPID(), i->Type()); delete i; } m_Streams.clear(); m_StreamInfos.clear(); } int cVNSIDemuxer::Read(sStreamPacket *packet, sStreamPacket *packet_side_data) { uint8_t *buf; int len; cTSStream *stream; cMutexLock lock(&m_Mutex); if (!m_CurrentChannel.Vpid()) m_WaitIFrame = false; // clear packet if (!packet) return -1; packet->data = NULL; packet->streamChange = false; packet->pmtChange = false; // read TS Packet from buffer len = m_VideoBuffer->Read(&buf, TS_SIZE, m_endTime, m_wrapTime); // eof if (len == -2) return -2; else if (len != TS_SIZE) return -1; m_Error &= ~ERROR_DEMUX_NODATA; int ts_pid = TsPid(buf); // parse PAT/PMT if (ts_pid == PATPID) { m_PatPmtParser.ParsePat(buf, TS_SIZE); } #if APIVERSNUM >= 10733 else if (m_PatPmtParser.IsPmtPid(ts_pid)) #else else if (ts_pid == m_PatPmtParser.PmtPid()) #endif { int patVersion, pmtVersion; m_PatPmtParser.ParsePmt(buf, TS_SIZE); if (m_PatPmtParser.GetVersions(patVersion, pmtVersion)) { cChannel pmtChannel(m_CurrentChannel); SetChannelPids(&pmtChannel, &m_PatPmtParser); SetChannelStreamInfos(&pmtChannel); m_PatPmtParser.Reset(); if (EnsureParsers()) { packet->pmtChange = true; return 1; } } } else if (stream = FindStream(ts_pid)) { int error = stream->ProcessTSPacket(buf, packet, packet_side_data, m_WaitIFrame); if (error == 0) { m_WaitIFrame = false; m_seenFirstPacket = true; packet->serial = m_MuxPacketSerial; if (m_SetRefTime) { m_refTime = m_VideoBuffer->GetRefTime(); packet->reftime = m_refTime; m_SetRefTime = false; } return 1; } else if (error < 0) { m_Error |= abs(error); if (m_Error & ERROR_PES_SCRAMBLE) { if (m_seenFirstPacket) { ResetParsers(); m_Error |= ERROR_CAM_ERROR; m_WaitIFrame = true; } } } } return 0; } bool cVNSIDemuxer::SeekTime(int64_t time) { off_t pos, pos_min, pos_max, pos_limit, start_pos; int64_t ts, ts_min, ts_max, last_ts; int no_change; if (!m_VideoBuffer->HasBuffer()) return false; cMutexLock lock(&m_Mutex); // INFOLOG("----- seek to time: %ld", time); // rescale to 90khz time = cTSStream::Rescale(time, 90000, DVD_TIME_BASE); m_VideoBuffer->GetPositions(&pos, &pos_min, &pos_max); // INFOLOG("----- seek to time: %ld", time); // INFOLOG("------pos: %ld, pos min: %ld, pos max: %ld", pos, pos_min, pos_max); if (!GetTimeAtPos(&pos_min, &ts_min)) { ResetParsers(); m_WaitIFrame = true; return false; } // INFOLOG("----time at min: %ld", ts_min); if (ts_min >= time) { m_VideoBuffer->SetPos(pos_min); ResetParsers(); m_WaitIFrame = true; m_MuxPacketSerial++; return true; } int64_t timecur; GetTimeAtPos(&pos, &timecur); // get time at end of buffer unsigned int step= 1024; bool gotTime; do { pos_max -= step; gotTime = GetTimeAtPos(&pos_max, &ts_max); step += step; } while (!gotTime && pos_max >= step); if (!gotTime) { ResetParsers(); m_WaitIFrame = true; return false; } if (ts_max <= time) { ResetParsers(); m_WaitIFrame = true; m_MuxPacketSerial++; return true; } // INFOLOG(" - time in buffer: %ld", cTSStream::Rescale(ts_max-ts_min, DVD_TIME_BASE, 90000)/1000000); // bisect seek if(ts_min > ts_max) { ResetParsers(); m_WaitIFrame = true; return false; } else if (ts_min == ts_max) { pos_limit = pos_min; } else pos_limit = pos_max; no_change = 0; ts = time; last_ts = 0; while (pos_min < pos_limit) { if (no_change==0) { // interpolate position pos = cTSStream::Rescale(time - ts_min, pos_max - pos_min, ts_max - ts_min) + pos_min - (pos_max - pos_limit); } else if (no_change==1) { // bisection, if interpolation failed to change min or max pos last time pos = (pos_min + pos_limit) >> 1; } else { // linear search if bisection failed pos = pos_min; } // clamp calculated pos into boundaries if( pos <= pos_min) pos = pos_min + 1; else if (pos > pos_limit) pos = pos_limit; start_pos = pos; // get time stamp at pos if (!GetTimeAtPos(&pos, &ts)) { ResetParsers(); m_WaitIFrame = true; return false; } pos = m_VideoBuffer->GetPosCur(); // determine method for next calculation of pos if ((last_ts == ts) || (pos >= pos_max)) no_change++; else no_change=0; // INFOLOG("--- pos: %ld, \t time: %ld, diff time: %ld", pos, ts, time-ts); // 0.4 sec is close enough if (abs(time - ts) <= 36000) { break; } // target is to the left else if (time <= ts) { pos_limit = start_pos - 1; pos_max = pos; ts_max = ts; } // target is to the right if (time >= ts) { pos_min = pos; ts_min = ts; } last_ts = ts; } // INFOLOG("----pos found: %ld", pos); // INFOLOG("----time at pos: %ld, diff time: %ld", ts, cTSStream::Rescale(timecur-ts, DVD_TIME_BASE, 90000)); m_VideoBuffer->SetPos(pos); ResetParsers(); m_WaitIFrame = true; m_MuxPacketSerial++; return true; } void cVNSIDemuxer::BufferStatus(bool ×hift, uint32_t &start, uint32_t &end) { timeshift = m_VideoBuffer->HasBuffer(); if (timeshift) { if (!m_wrapTime) { start = m_refTime; } else { start = m_endTime - (m_wrapTime - m_refTime); } end = m_endTime; } else { start = 0; end = 0; } } cTSStream *cVNSIDemuxer::GetFirstStream() { m_StreamsIterator = m_Streams.begin(); if (m_StreamsIterator != m_Streams.end()) return *m_StreamsIterator; else return NULL; } cTSStream *cVNSIDemuxer::GetNextStream() { ++m_StreamsIterator; if (m_StreamsIterator != m_Streams.end()) return *m_StreamsIterator; else return NULL; } cTSStream *cVNSIDemuxer::FindStream(int Pid) { for (auto *i : m_Streams) { if (Pid == i->GetPID()) return i; } return NULL; } void cVNSIDemuxer::ResetParsers() { for (auto *i : m_Streams) { i->ResetParser(); } m_seenFirstPacket = false; } static bool Contains(const std::list &list, int pID, eStreamType type) { for (const auto &i : list) if (i.pID == pID && i.type == type) return true; return false; } bool cVNSIDemuxer::EnsureParsers() { bool streamChange = false; auto it = m_Streams.begin(); while (it != m_Streams.end()) { if (!Contains(m_StreamInfos, (*it)->GetPID(), (*it)->Type())) { INFOLOG("Deleting stream for pid=%i and type=%i", (*it)->GetPID(), (*it)->Type()); it = m_Streams.erase(it); streamChange = true; } else ++it; } for (const auto &i : m_StreamInfos) { cTSStream *stream = FindStream(i.pID); if (stream) { // TODO: check for change in lang stream->SetLanguage(i.language); continue; } if (i.type == stH264) { stream = new cTSStream(stH264, i.pID, &m_PtsWrap); } else if (i.type == stHEVC) { stream = new cTSStream(stHEVC, i.pID, &m_PtsWrap); } else if (i.type == stMPEG2VIDEO) { stream = new cTSStream(stMPEG2VIDEO, i.pID, &m_PtsWrap); } else if (i.type == stMPEG2AUDIO) { stream = new cTSStream(stMPEG2AUDIO, i.pID, &m_PtsWrap, i.handleRDS); stream->SetLanguage(i.language); } else if (i.type == stAACADTS) { stream = new cTSStream(stAACADTS, i.pID, &m_PtsWrap); stream->SetLanguage(i.language); } else if (i.type == stAACLATM) { stream = new cTSStream(stAACLATM, i.pID, &m_PtsWrap); stream->SetLanguage(i.language); } else if (i.type == stAC3) { stream = new cTSStream(stAC3, i.pID, &m_PtsWrap); stream->SetLanguage(i.language); } else if (i.type == stEAC3) { stream = new cTSStream(stEAC3, i.pID, &m_PtsWrap); stream->SetLanguage(i.language); } else if (i.type == stDVBSUB) { stream = new cTSStream(stDVBSUB, i.pID, &m_PtsWrap); stream->SetLanguage(i.language); #if APIVERSNUM >= 10709 stream->SetSubtitlingDescriptor(i.subtitlingType, i.compositionPageId, i.ancillaryPageId); #endif } else if (i.type == stTELETEXT) { stream = new cTSStream(stTELETEXT, i.pID, &m_PtsWrap); } else continue; m_Streams.push_back(stream); INFOLOG("Created stream for pid=%i and type=%i", stream->GetPID(), stream->Type()); streamChange = true; } m_StreamInfos.clear(); return streamChange; } void cVNSIDemuxer::SetChannelStreamInfos(const cChannel *channel) { m_StreamInfos.clear(); cStreamInfo newStream; bool containsVideo = false; int index = 0; if (channel->Vpid()) { newStream.pID = channel->Vpid(); #if APIVERSNUM >= 10701 if (channel->Vtype() == 0x1B) newStream.type = stH264; else if (channel->Vtype() == 0x24) newStream.type = stHEVC; else #endif newStream.type = stMPEG2VIDEO; m_StreamInfos.push_back(newStream); containsVideo = true; } const int *DPids = channel->Dpids(); index = 0; for ( ; *DPids; DPids++) { newStream.pID = *DPids; newStream.type = stAC3; if (channel->Dtype(index) == SI::EnhancedAC3DescriptorTag) newStream.type = stEAC3; newStream.SetLanguage(channel->Dlang(index)); m_StreamInfos.push_back(newStream); index++; } const int *APids = channel->Apids(); index = 0; for ( ; *APids; APids++) { newStream.pID = *APids; newStream.type = stMPEG2AUDIO; if (channel->Atype(index) == 0x0F) newStream.type = stAACADTS; else if (channel->Atype(index) == 0x11) newStream.type = stAACLATM; newStream.handleRDS = m_bAllowRDS && newStream.type == stMPEG2AUDIO && !containsVideo ? true : false; // Relevant for RDS, if present only on mpeg 2 audio, use only if RDS is allowed newStream.SetLanguage(channel->Alang(index)); m_StreamInfos.push_back(newStream); index++; } const int *SPids = channel->Spids(); if (SPids) { index = 0; for ( ; *SPids; SPids++) { newStream.pID = *SPids; newStream.type = stDVBSUB; newStream.SetLanguage(channel->Slang(index)); newStream.subtitlingType = channel->SubtitlingType(index); newStream.compositionPageId = channel->CompositionPageId(index); newStream.ancillaryPageId = channel->AncillaryPageId(index); m_StreamInfos.push_back(newStream); } index++; } if (channel->Tpid()) { newStream.pID = channel->Tpid(); newStream.type = stTELETEXT; m_StreamInfos.push_back(newStream); } } void cVNSIDemuxer::SetChannelPids(cChannel *channel, cPatPmtParser *patPmtParser) { int Apids[MAXAPIDS + 1] = { 0 }; int Atypes[MAXAPIDS + 1] = { 0 }; int Dpids[MAXDPIDS + 1] = { 0 }; int Dtypes[MAXDPIDS + 1] = { 0 }; int Spids[MAXSPIDS + 1] = { 0 }; char ALangs[MAXAPIDS][MAXLANGCODE2] = { 0 }; char DLangs[MAXDPIDS][MAXLANGCODE2] = { 0 }; char SLangs[MAXSPIDS][MAXLANGCODE2] = { 0 }; int index = 0; const int *aPids = patPmtParser->Apids(); index = 0; for ( ; *aPids; aPids++) { Apids[index] = patPmtParser->Apid(index); Atypes[index] = patPmtParser->Atype(index); strn0cpy(ALangs[index], patPmtParser->Alang(index), MAXLANGCODE2); index++; } const int *dPids = patPmtParser->Dpids(); index = 0; for ( ; *dPids; dPids++) { Dpids[index] = patPmtParser->Dpid(index); Dtypes[index] = patPmtParser->Dtype(index); strn0cpy(DLangs[index], patPmtParser->Dlang(index), MAXLANGCODE2); index++; } const int *sPids = patPmtParser->Spids(); index = 0; for ( ; *sPids; sPids++) { Spids[index] = patPmtParser->Spid(index); strn0cpy(SLangs[index], patPmtParser->Slang(index), MAXLANGCODE2); index++; } int Vpid = patPmtParser->Vpid(); int Ppid = patPmtParser->Ppid(); int VType = patPmtParser->Vtype(); int Tpid = m_CurrentChannel.Tpid(); channel->SetPids(Vpid, Ppid, VType, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); } bool cVNSIDemuxer::GetTimeAtPos(off_t *pos, int64_t *time) { uint8_t *buf; int len; cTSStream *stream; int ts_pid; m_VideoBuffer->SetPos(*pos); ResetParsers(); while ((len = m_VideoBuffer->Read(&buf, TS_SIZE, m_endTime, m_wrapTime)) == TS_SIZE) { ts_pid = TsPid(buf); if (stream = FindStream(ts_pid)) { // only consider video or audio streams if ((stream->Content() == scVIDEO || stream->Content() == scAUDIO) && stream->ReadTime(buf, time)) { return true; } } } return false; } uint16_t cVNSIDemuxer::GetError() { uint16_t ret = m_Error; m_Error = ERROR_DEMUX_NODATA; return ret; } vdr-plugin-vnsiserver-1.5.2/demuxer.h000066400000000000000000000052721303643374600176530ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include "parser.h" struct sStreamPacket; class cTSStream; class cChannel; class cPatPmtParser; class cVideoBuffer; class cStreamInfo { public: cStreamInfo(); cStreamInfo(const cStreamInfo& info); cStreamInfo& operator=(const cStreamInfo& info) = delete; void SetLanguage(const char* lang); int pID = 0; eStreamType type = eStreamType::stNone; eStreamContent content; char language[MAXLANGCODE2] = { 0 }; int subtitlingType; int compositionPageId; int ancillaryPageId; bool handleRDS = false; }; class cVNSIDemuxer { public: cVNSIDemuxer(bool bAllowRDS); virtual ~cVNSIDemuxer(); cVNSIDemuxer(const cVNSIDemuxer &) = delete; cVNSIDemuxer &operator=(const cVNSIDemuxer &) = delete; int Read(sStreamPacket *packet, sStreamPacket *packet_side_data); cTSStream *GetFirstStream(); cTSStream *GetNextStream(); void Open(const cChannel &channel, cVideoBuffer *videoBuffer); void Close(); bool SeekTime(int64_t time); uint32_t GetSerial() { return m_MuxPacketSerial; } void SetSerial(uint32_t serial) { m_MuxPacketSerial = serial; } void BufferStatus(bool ×hift, uint32_t &start, uint32_t &end); uint16_t GetError(); protected: bool EnsureParsers(); void ResetParsers(); void SetChannelStreamInfos(const cChannel *channel); void SetChannelPids(cChannel *channel, cPatPmtParser *patPmtParser); cTSStream *FindStream(int Pid); bool GetTimeAtPos(off_t *pos, int64_t *time); std::list m_Streams; std::list::iterator m_StreamsIterator; std::list m_StreamInfos; cChannel m_CurrentChannel; cPatPmtParser m_PatPmtParser; bool m_WaitIFrame; cVideoBuffer *m_VideoBuffer; cMutex m_Mutex; uint32_t m_MuxPacketSerial; sPtsWrap m_PtsWrap; uint16_t m_Error; bool m_SetRefTime; time_t m_refTime, m_endTime, m_wrapTime; bool m_bAllowRDS; bool m_seenFirstPacket; }; vdr-plugin-vnsiserver-1.5.2/hash.c000066400000000000000000000125301303643374600171130ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 1986 Gary S. Brown (CRC32 code) * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "hash.h" #include #include static uint32_t crc32_tab[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; uint32_t crc32(const unsigned char *buf, size_t size) { uint32_t crc = 0xFFFFFFFF; const uint8_t *p = (uint8_t*)buf; while (size--) crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); return (crc ^ ~0U) & 0x7FFFFFFF; // channeluid is signed } uint32_t CreateStringHash(const cString& string) { const char* p = string; int len = strlen(p); return crc32((const unsigned char*)p, len); } uint32_t CreateChannelUID(const cChannel* channel) { cString channelid = channel->GetChannelID().ToString(); return CreateStringHash(channelid); } const cChannel* FindChannelByUID(uint32_t channelUID) { const cChannel* result = NULL; #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; // maybe we need to use a lookup table for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) { cString channelid = channel->GetChannelID().ToString(); if(channelUID == CreateStringHash(channelid)) { result = channel; break; } } #else // maybe we need to use a lookup table Channels.Lock(false); for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { cString channelid = channel->GetChannelID().ToString(); if(channelUID == CreateStringHash(channelid)) { result = channel; break; } } Channels.Unlock(); #endif return result; } vdr-plugin-vnsiserver-1.5.2/hash.h000066400000000000000000000022301303643374600171140ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 1986 Gary S. Brown (CRC32 code) * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_HASH_H #define VNSI_HASH_H #include #include class cChannel; uint32_t CreateChannelUID(const cChannel* channel); const cChannel* FindChannelByUID(uint32_t channelUID); uint32_t CreateStringHash(const cString& string); #endif // VNSI_HASH_H vdr-plugin-vnsiserver-1.5.2/parser.c000066400000000000000000000442411303643374600174700ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include #include #include #include #include "config.h" #include "parser.h" #include "parser_AAC.h" #include "parser_AC3.h" #include "parser_DTS.h" #include "parser_h264.h" #include "parser_hevc.h" #include "parser_MPEGAudio.h" #include "parser_MPEGVideo.h" #include "parser_Subtitle.h" #include "parser_Teletext.h" #define PTS_MASK 0x1ffffffffLL //#define PTS_MASK 0x7ffffLL #ifndef INT64_MIN #define INT64_MIN (-0x7fffffffffffffffLL-1) #endif // --- cParser ------------------------------------------------- cParser::cParser(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : m_pID(pID), m_PtsWrap(ptsWrap), m_ObservePtsWraps(observePtsWraps) { m_PesBuffer = NULL; m_Stream = stream; m_IsVideo = false; m_PesBufferInitialSize = 1024; Reset(); } cParser::~cParser() { free(m_PesBuffer); } void cParser::Reset() { m_curPTS = DVD_NOPTS_VALUE; m_curDTS = DVD_NOPTS_VALUE; m_prevDTS = DVD_NOPTS_VALUE; m_PesBufferPtr = 0; m_PesParserPtr = 0; m_PesNextFramePtr = 0; m_FoundFrame = false; m_FrameValid = false; m_PesPacketLength = 0; m_PesHeaderPtr = 0; m_Error = ERROR_PES_GENERAL; } /* * Extract DTS and PTS and update current values in stream */ int cParser::ParsePESHeader(uint8_t *buf, size_t len) { m_PesPacketLength = buf[4] << 8 | buf[5]; if (!PesIsVideoPacket(buf) && !PesIsAudioPacket(buf)) return 6; unsigned int hdr_len = PesHeaderLength(buf); if (m_PesPacketLength > 0) m_PesPacketLength -= (hdr_len-6); // scrambled if ((buf[6] & 0x30) != 0) return hdr_len; // parse PTS if ((hdr_len >= 13) && ((((buf[7] & 0xC0) == 0x80) && ((buf[9] & 0xF0) == 0x20)) || ((buf[7] & 0xC0) == 0xC0) && ((buf[9] & 0xF0) == 0x30))) { int64_t pts; pts = ((int64_t)(buf[ 9] & 0x0E)) << 29 ; pts |= ((int64_t) buf[10]) << 22 ; pts |= ((int64_t)(buf[11] & 0xFE)) << 14 ; pts |= ((int64_t) buf[12]) << 7 ; pts |= ((int64_t)(buf[13] & 0xFE)) >> 1 ; int64_t bit32and31 = pts >> 31; if (m_ObservePtsWraps) { if ((bit32and31 == 3) && !m_PtsWrap->m_Wrap) { m_PtsWrap->m_ConfirmCount++; if (m_PtsWrap->m_ConfirmCount >= 2) { m_PtsWrap->m_Wrap = true; } } else if ((bit32and31 == 1) && m_PtsWrap->m_Wrap) { m_PtsWrap->m_ConfirmCount++; if (m_PtsWrap->m_ConfirmCount >= 2) { m_PtsWrap->m_Wrap = false; m_PtsWrap->m_NoOfWraps++; } } else m_PtsWrap->m_ConfirmCount = 0; } m_prevPTS = m_curPTS; m_curPTS = pts; m_PesTimePos = m_PesBufferPtr; if (m_PtsWrap->m_Wrap && !(bit32and31)) { m_curPTS += 1LL<<33; } if (m_PtsWrap->m_NoOfWraps) { m_curPTS += ((int64_t)m_PtsWrap->m_NoOfWraps<<33); } } else return hdr_len; m_prevDTS = m_curDTS; // parse DTS if ((hdr_len >= 18) && ((buf[7] & 0xC0) == 0xC0) && ((buf[14] & 0xF0) == 0x10)) { int64_t dts; dts = ((int64_t)( buf[14] & 0x0E)) << 29 ; dts |= (int64_t)( buf[15] << 22 ); dts |= (int64_t)((buf[16] & 0xFE) << 14 ); dts |= (int64_t)( buf[17] << 7 ); dts |= (int64_t)((buf[18] & 0xFE) >> 1 ); m_curDTS = dts; if (m_PtsWrap->m_Wrap && !(m_curDTS >> 31)) { m_curDTS += 1LL<<33; } if (m_PtsWrap->m_NoOfWraps) { m_curDTS += ((int64_t)m_PtsWrap->m_NoOfWraps<<33); } } else m_curDTS = m_curPTS; return hdr_len; } int cParser::ParsePacketHeader(uint8_t *data) { if (TsIsScrambled(data)) { m_Error = ERROR_PES_SCRAMBLE; return -1; } if (TsPayloadStart(data)) { m_IsPusi = true; m_Error = 0; } int bytes = TS_SIZE - TsPayloadOffset(data); if(bytes < 0 || bytes > TS_SIZE) { m_Error = ERROR_PES_GENERAL; return -1; } if (TsError(data)) { m_Error = ERROR_PES_GENERAL; return -1; } if (!TsHasPayload(data)) { DEBUGLOG("no payload, size %d", bytes); return 0; } /* drop broken PES packets */ if (m_Error) { return -1; } return bytes; } bool cParser::AddPESPacket(uint8_t *data, int size) { // check for beginning of a PES packet if (m_IsPusi && m_IsVideo && !IsValidStartCode(data, 4)) { m_IsPusi = false; } if (m_IsPusi) { int hdr_len = 6; if (m_PesHeaderPtr + size < hdr_len) { memcpy(m_PesHeader+m_PesHeaderPtr, data, size); m_PesHeaderPtr += size; return false; } else if (m_PesHeaderPtr) { int bytesNeeded = hdr_len-m_PesHeaderPtr; if (bytesNeeded > 0) { memcpy(m_PesHeader+m_PesHeaderPtr, data, bytesNeeded); m_PesHeaderPtr += bytesNeeded; data += bytesNeeded; size -= bytesNeeded; } if (!IsValidStartCode(m_PesHeader, hdr_len)) { Reset(); m_Error |= ERROR_PES_STARTCODE; return false; } if (PesIsVideoPacket(m_PesHeader) || PesIsAudioPacket(m_PesHeader)) { hdr_len = 9; bytesNeeded = hdr_len-m_PesHeaderPtr; if (size < bytesNeeded) { memcpy(m_PesHeader+m_PesHeaderPtr, data, size); m_PesHeaderPtr += size; return false; } else if (bytesNeeded > 0) { memcpy(m_PesHeader+m_PesHeaderPtr, data, bytesNeeded); m_PesHeaderPtr += bytesNeeded; data += bytesNeeded; size -= bytesNeeded; } if ((m_PesHeader[6] & 0x30)) { Reset(); m_Error |= ERROR_PES_SCRAMBLE; return false; } hdr_len = PesHeaderLength(m_PesHeader); if (hdr_len > PES_HEADER_LENGTH) { Reset(); return false; } } bytesNeeded = hdr_len-m_PesHeaderPtr; if (size < bytesNeeded) { memcpy(m_PesHeader+m_PesHeaderPtr, data, size); m_PesHeaderPtr += size; return false; } else if (bytesNeeded > 0) { memcpy(m_PesHeader+m_PesHeaderPtr, data, bytesNeeded); m_PesHeaderPtr += bytesNeeded; data += bytesNeeded; size -= bytesNeeded; } if (ParsePESHeader(m_PesHeader, hdr_len) < 0) { INFOLOG("error parsing pes packet error "); Reset(); return false; } m_PesHeaderPtr = 0; m_IsPusi = false; } else if (!IsValidStartCode(data, size)) { Reset(); m_Error |= ERROR_PES_STARTCODE; return false; } else { if (PesIsVideoPacket(data) || PesIsAudioPacket(data)) { if (size < 9) { memcpy(m_PesHeader+m_PesHeaderPtr, data, size); m_PesHeaderPtr += size; return false; } if ((data[6] & 0x30)) { Reset(); m_Error |= ERROR_PES_STARTCODE; return false; } hdr_len = PesHeaderLength(data); if (hdr_len > PES_HEADER_LENGTH) { Reset(); return false; } } if (size < hdr_len) { memcpy(m_PesHeader+m_PesHeaderPtr, data, size); m_PesHeaderPtr += size; return false; } if (ParsePESHeader(data, hdr_len) < 0) { INFOLOG("error parsing pes packet error 2"); Reset(); return false; } data += hdr_len; size -= hdr_len; m_IsPusi = false; } } if (m_PesBuffer == NULL) { m_PesBufferSize = m_PesBufferInitialSize; m_PesBuffer = (uint8_t*)malloc(m_PesBufferSize); if (m_PesBuffer == NULL) { ERRORLOG("cParser::AddPESPacket - malloc failed"); Reset(); return false; } } if (m_PesBufferPtr + size >= m_PesBufferSize) { if (m_PesBufferPtr + size >= 1000000) { ERRORLOG("cParser::AddPESPacket - max buffer size reached, pid: %d", m_pID); Reset(); return false; } m_PesBufferSize += m_PesBufferInitialSize / 10; uint8_t *new_buffer = (uint8_t*)realloc(m_PesBuffer, m_PesBufferSize); if (new_buffer == NULL) { ERRORLOG("cParser::AddPESPacket - realloc failed"); Reset(); return false; } m_PesBuffer = new_buffer; } // copy first packet of new frame to front if (m_PesNextFramePtr) { memmove(m_PesBuffer, m_PesBuffer+m_PesNextFramePtr, m_PesBufferPtr-m_PesNextFramePtr); m_PesBufferPtr = m_PesBufferPtr-m_PesNextFramePtr; m_PesTimePos -= m_PesNextFramePtr; m_PesNextFramePtr = 0; } // copy payload memcpy(m_PesBuffer+m_PesBufferPtr, data, size); m_PesBufferPtr += size; return true; } inline bool cParser::IsValidStartCode(uint8_t *buf, int size) { if (size < 4) return false; uint32_t startcode = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; if (m_Stream->Type() == stH264 || m_Stream->Type() == stHEVC ||m_Stream->Type() == stMPEG2VIDEO) { if (startcode >= 0x000001e0 && startcode <= 0x000001ef) return true; } else if (m_Stream->Type() == stAC3 || m_Stream->Type() == stEAC3) { if (PesIsPS1Packet(buf)) return true; } else if (m_Stream->Type() == stMPEG2AUDIO || m_Stream->Type() == stAACADTS || m_Stream->Type() == stAACLATM || m_Stream->Type() == stDTS) { if (startcode >= 0x000001c0 && startcode <= 0x000001df) return true; } else if (m_Stream->Type() == stTELETEXT) { if (PesIsPS1Packet(buf)) return true; } else if (m_Stream->Type() == stDVBSUB || m_Stream->Type() == stTEXTSUB) { if (startcode == 0x000001bd || startcode == 0x000001bf || (startcode >= 0x000001f0 && startcode <= 0x000001f9)) return true; } return false; } // --- cTSStream ---------------------------------------------------- uint32_t cTSStream::m_UniqueSideDataIDs = 0; cTSStream::cTSStream(eStreamType type, int pid, sPtsWrap *ptsWrap, bool handleSideData) : m_streamType(type) , m_pID(pid) { m_pesError = false; m_pesParser = NULL; m_language[0] = 0; m_FpsScale = 0; m_FpsRate = 0; m_Height = 0; m_Width = 0; m_Aspect = 0.0f; m_Channels = 0; m_SampleRate = 0; m_BitRate = 0; m_BitsPerSample = 0; m_BlockAlign = 0; m_IsStreamChange = false; if (m_streamType == stMPEG2VIDEO) { m_pesParser = new cParserMPEG2Video(m_pID, this, ptsWrap, true); m_streamContent = scVIDEO; } else if (m_streamType == stH264) { m_pesParser = new cParserH264(m_pID, this, ptsWrap, true); m_streamContent = scVIDEO; } else if (m_streamType == stHEVC) { m_pesParser = new cParserHEVC(m_pID, this, ptsWrap, true); m_streamContent = scVIDEO; } else if (m_streamType == stMPEG2AUDIO) { m_pesParser = new cParserMPEG2Audio(m_pID, this, ptsWrap, true, handleSideData); m_streamContent = scAUDIO; } else if (m_streamType == stAACADTS) { m_pesParser = new cParserAAC(m_pID, this, ptsWrap, true); m_streamContent = scAUDIO; } else if (m_streamType == stAACLATM) { m_pesParser = new cParserAAC(m_pID, this, ptsWrap, true); m_streamContent = scAUDIO; } else if (m_streamType == stAC3) { m_pesParser = new cParserAC3(m_pID, this, ptsWrap, true); m_streamContent = scAUDIO; } else if (m_streamType == stEAC3) { m_pesParser = new cParserAC3(m_pID, this, ptsWrap, true); m_streamContent = scAUDIO; } else if (m_streamType == stDTS) { m_pesParser = new cParserDTS(m_pID, this, ptsWrap, true); m_streamContent = scAUDIO; } else if (m_streamType == stTELETEXT) { m_pesParser = new cParserTeletext(m_pID, this, ptsWrap, false); m_streamContent = scTELETEXT; } else if (m_streamType == stDVBSUB) { m_pesParser = new cParserSubtitle(m_pID, this, ptsWrap, false); m_streamContent = scSUBTITLE; } else { ERRORLOG("Unrecognised type %i inside stream %i", m_streamType, m_pID); return; } } cTSStream::~cTSStream() { if (m_pesParser) { delete m_pesParser; m_pesParser = NULL; } } int cTSStream::ProcessTSPacket(uint8_t *data, sStreamPacket *pkt, sStreamPacket *pkt_side_data, bool iframe) { int ret = 1; if (!data) return ret; if (!m_pesParser) return ret; int payloadSize = m_pesParser->ParsePacketHeader(data); if (payloadSize == 0) return ret; else if (payloadSize < 0) { return -m_pesParser->GetError(); } if (!m_pesParser->AddPESPacket(data+TS_SIZE-payloadSize, payloadSize)) { return -m_pesParser->GetError(); } m_pesParser->Parse(pkt, pkt_side_data); if (iframe && !m_pesParser->IsVideo()) return ret; if (pkt->data) { int64_t dts = pkt->dts; int64_t pts = pkt->pts; // Rescale for KODI if (pkt->dts != DVD_NOPTS_VALUE) pkt->dts = Rescale(dts, DVD_TIME_BASE, 90000); if (pkt->pts != DVD_NOPTS_VALUE) pkt->pts = Rescale(pts, DVD_TIME_BASE, 90000); pkt->duration = Rescale(pkt->duration, DVD_TIME_BASE, 90000); ret = 0; } if (pkt_side_data && pkt_side_data->data) { int64_t dts = pkt_side_data->dts; int64_t pts = pkt_side_data->pts; // Rescale for KODI if (pkt_side_data->dts != DVD_NOPTS_VALUE) pkt_side_data->dts = Rescale(dts, DVD_TIME_BASE, 90000); if (pkt_side_data->pts != DVD_NOPTS_VALUE) pkt_side_data->pts = Rescale(pts, DVD_TIME_BASE, 90000); pkt_side_data->duration = Rescale(pkt_side_data->duration, DVD_TIME_BASE, 90000); ret = 0; } return ret; } bool cTSStream::ReadTime(uint8_t *data, int64_t *dts) { if (!data) return false; if (!m_pesParser) return false; int payloadSize = m_pesParser->ParsePacketHeader(data); if (payloadSize < 0) return false; if (m_pesParser->m_IsPusi) { data += TS_SIZE-payloadSize; if (payloadSize >= 6 && m_pesParser->IsValidStartCode(data, payloadSize)) { m_pesParser->m_curPTS = DVD_NOPTS_VALUE; m_pesParser->m_curDTS = DVD_NOPTS_VALUE; m_pesParser->ParsePESHeader(data, payloadSize); if (m_pesParser->m_curDTS != DVD_NOPTS_VALUE) { *dts = m_pesParser->m_curDTS; return true; } else if (m_pesParser->m_curPTS != DVD_NOPTS_VALUE) { *dts = m_pesParser->m_curPTS; return true; } } m_pesParser->m_IsPusi = false; } return false; } void cTSStream::ResetParser() { if (m_pesParser) m_pesParser->Reset(); } int64_t cTSStream::Rescale(int64_t a, int64_t b, int64_t c) { uint64_t r = c/2; if (b<=INT_MAX && c<=INT_MAX) { if (a<=INT_MAX) return (a * b + r)/c; else return a/c*b + (a%c*b + r)/c; } else { uint64_t a0= a&0xFFFFFFFF; uint64_t a1= a>>32; uint64_t b0= b&0xFFFFFFFF; uint64_t b1= b>>32; uint64_t t1= a0*b1 + a1*b0; uint64_t t1a= t1<<32; a0 = a0*b0 + t1a; a1 = a1*b1 + (t1>>32) + (a0=0; i--) { a1+= a1 + ((a0>>i)&1); t1+=t1; if (c <= (int64_t)a1) { a1 -= c; t1++; } } return t1; } } uint32_t cTSStream::AddSideDataType(eStreamContent content) { m_UniqueSideDataIDs++; if (m_UniqueSideDataIDs == 0) m_UniqueSideDataIDs++; uint32_t sidePid = m_UniqueSideDataIDs << 16; m_SideDataTypes.push_back(std::make_pair(sidePid, content)); return sidePid; } void cTSStream::SetLanguage(const char *language) { m_language[0] = language[0]; m_language[1] = language[1]; m_language[2] = language[2]; m_language[3] = 0; } bool cTSStream::SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect) { if ((m_FpsScale != FpsScale) || (m_FpsRate != FpsRate) || (m_Height != Height) || (m_Width != Width) || (m_Aspect != Aspect)) { INFOLOG("Video stream change, pid: %d, width: %d, height: %d, aspect: %f", m_pID, Width, Height, Aspect); m_IsStreamChange = true; } m_FpsScale = FpsScale; m_FpsRate = FpsRate; m_Height = Height; m_Width = Width; m_Aspect = Aspect; return m_IsStreamChange; } void cTSStream::GetVideoInformation(uint32_t &FpsScale, uint32_t &FpsRate, uint32_t &Height, uint32_t &Width, double &Aspect) { FpsScale = m_FpsScale; FpsRate = m_FpsRate; Height = m_Height; Width = m_Width; Aspect = m_Aspect; m_IsStreamChange = false; } bool cTSStream::SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign) { if ((m_Channels != Channels) || (m_SampleRate != SampleRate) || (m_BlockAlign != BlockAlign) || (m_BitRate != BitRate) || (m_BitsPerSample != BitsPerSample)) { INFOLOG("Audio stream change, pid: %d, channels: %d, samplerate: %d", m_pID, Channels, SampleRate); m_IsStreamChange = true; } m_Channels = Channels; m_SampleRate = SampleRate; m_BlockAlign = BlockAlign; m_BitRate = BitRate; m_BitsPerSample = BitsPerSample; return m_IsStreamChange; } void cTSStream::GetAudioInformation(uint32_t &Channels, uint32_t &SampleRate, uint32_t &BitRate, uint32_t &BitsPerSample, uint32_t &BlockAlign) { Channels = m_Channels; SampleRate = m_SampleRate; BlockAlign = m_BlockAlign; BitRate = m_BitRate; BitsPerSample = m_BitsPerSample; m_IsStreamChange = false; } void cTSStream::SetSubtitlingDescriptor(unsigned char SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId) { m_subtitlingType = SubtitlingType; m_compositionPageId = CompositionPageId; m_ancillaryPageId = AncillaryPageId; } vdr-plugin-vnsiserver-1.5.2/parser.h000066400000000000000000000164611303643374600175000ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_H #define VNSI_DEMUXER_H #include #include #include #define DVD_TIME_BASE 1000000 #define DVD_NOPTS_VALUE (-1LL<<52) // should be possible to represent in both double and __int64 /* PES PIDs */ #define PRIVATE_STREAM1 0xBD #define PADDING_STREAM 0xBE #define PRIVATE_STREAM2 0xBF #define PRIVATE_STREAM3 0xFD #define AUDIO_STREAM_S 0xC0 /* 1100 0000 */ #define AUDIO_STREAM_E 0xDF /* 1101 1111 */ #define VIDEO_STREAM_S 0xE0 /* 1110 0000 */ #define VIDEO_STREAM_E 0xEF /* 1110 1111 */ #define AUDIO_STREAM_MASK 0x1F /* 0001 1111 */ #define VIDEO_STREAM_MASK 0x0F /* 0000 1111 */ #define AUDIO_STREAM 0xC0 /* 1100 0000 */ #define VIDEO_STREAM 0xE0 /* 1110 0000 */ #define ECM_STREAM 0xF0 #define EMM_STREAM 0xF1 #define DSM_CC_STREAM 0xF2 #define ISO13522_STREAM 0xF3 #define PROG_STREAM_DIR 0xFF inline bool PesIsHeader(const uchar *p) { return !(p)[0] && !(p)[1] && (p)[2] == 1; } inline int PesHeaderLength(const uchar *p) { return 8 + (p)[8] + 1; } inline bool PesIsVideoPacket(const uchar *p) { return (((p)[3] & ~VIDEO_STREAM_MASK) == VIDEO_STREAM); } inline bool PesIsMPEGAudioPacket(const uchar *p) { return (((p)[3] & ~AUDIO_STREAM_MASK) == AUDIO_STREAM); } inline bool PesIsPS1Packet(const uchar *p) { return ((p)[3] == PRIVATE_STREAM1 || (p)[3] == PRIVATE_STREAM3 ); } inline bool PesIsAudioPacket(const uchar *p) { return (PesIsMPEGAudioPacket(p) || PesIsPS1Packet(p)); } enum eStreamContent { scVIDEO, scAUDIO, scSUBTITLE, scTELETEXT, scPROGRAMM, scRDS }; enum eStreamType { stNone, stAC3, stMPEG2AUDIO, stEAC3, stAACADTS, stAACLATM, stDTS, stMPEG2VIDEO, stH264, stHEVC, stDVBSUB, stTEXTSUB, stTELETEXT, }; #define PKT_I_FRAME 1 #define PKT_P_FRAME 2 #define PKT_B_FRAME 3 #define PKT_NTYPES 4 struct sStreamPacket { int64_t id; int64_t dts; int64_t pts; int duration; uint8_t commercial; uint8_t componentindex; uint8_t *data; int size; bool streamChange; bool pmtChange; uint32_t serial; uint32_t reftime; }; struct sPtsWrap { bool m_Wrap; int m_NoOfWraps; int m_ConfirmCount; }; class cTSStream; #define PES_HEADER_LENGTH 128 class cParser { friend class cTSStream; public: cParser(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParser(); cParser(const cParser &) = delete; cParser &operator=(const cParser &) = delete; bool AddPESPacket(uint8_t *data, int size); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) = 0; // void ClearFrame() {m_PesBufferPtr = 0;} int ParsePacketHeader(uint8_t *data); int ParsePESHeader(uint8_t *buf, size_t len); virtual void Reset(); bool IsVideo() {return m_IsVideo; } uint16_t GetError() { return m_Error; } protected: virtual bool IsValidStartCode(uint8_t *buf, int size); uint8_t m_PesHeader[PES_HEADER_LENGTH]; int m_PesHeaderPtr; int m_PesPacketLength; uint8_t *m_PesBuffer; int m_PesBufferSize; int m_PesBufferPtr; size_t m_PesBufferInitialSize; size_t m_PesParserPtr; size_t m_PesNextFramePtr; int m_PesTimePos; bool m_FoundFrame; bool m_FrameValid; int m_pID; int64_t m_curPTS; int64_t m_curDTS; int64_t m_prevPTS; int64_t m_prevDTS; bool m_IsPusi; uint16_t m_Error; cTSStream *m_Stream; bool m_IsVideo; sPtsWrap *m_PtsWrap; bool m_ObservePtsWraps; }; class cTSStream { private: eStreamType m_streamType; const int m_pID; eStreamContent m_streamContent; bool m_IsStreamChange; bool m_pesError; cParser *m_pesParser; char m_language[4]; // ISO 639 3-letter language code (empty string if undefined) int m_FpsScale; // scale of 1000 and a rate of 29970 will result in 29.97 fps int m_FpsRate; int m_Height; // height of the stream reported by the demuxer int m_Width; // width of the stream reported by the demuxer float m_Aspect; // display aspect of stream int m_Channels; int m_SampleRate; int m_BitRate; int m_BitsPerSample; int m_BlockAlign; std::vector< std::pair > m_SideDataTypes; static uint32_t m_UniqueSideDataIDs; unsigned char m_subtitlingType; uint16_t m_compositionPageId; uint16_t m_ancillaryPageId; public: cTSStream(eStreamType type, int pid, sPtsWrap *ptsWrap, bool handleSideData = false); virtual ~cTSStream(); cTSStream(const cTSStream &) = delete; cTSStream &operator=(const cTSStream &) = delete; int ProcessTSPacket(uint8_t *data, sStreamPacket *pkt, sStreamPacket *pkt_side_data, bool iframe); bool ReadTime(uint8_t *data, int64_t *dts); void ResetParser(); void SetLanguage(const char *language); const char *GetLanguage() { return m_language; } eStreamContent Content() const { return m_streamContent; } eStreamType Type() const { return m_streamType; } void SetType(eStreamType type) { m_streamType = type; } int GetPID() const { return m_pID; } uint32_t AddSideDataType(eStreamContent content); const std::vector< std::pair > &GetSideDataTypes() const { return m_SideDataTypes; } /* Video Stream Information */ bool SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect); void GetVideoInformation(uint32_t &FpsScale, uint32_t &FpsRate, uint32_t &Height, uint32_t &Width, double &Aspect); /* Audio Stream Information */ bool SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign); void GetAudioInformation(uint32_t &Channels, uint32_t &SampleRate, uint32_t &BitRate, uint32_t &BitsPerSample, uint32_t &BlockAlign); /* Subtitle related stream information */ void SetSubtitlingDescriptor(unsigned char SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId); unsigned char SubtitlingType() const { return m_subtitlingType; } uint16_t CompositionPageId() const { return m_compositionPageId; } uint16_t AncillaryPageId() const { return m_ancillaryPageId; } static int64_t Rescale(int64_t a, int64_t b, int64_t c); }; #endif // VNSI_DEMUXER_H vdr-plugin-vnsiserver-1.5.2/parser_AAC.c000066400000000000000000000161471303643374600201400ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_AAC.h" #include "config.h" #include #include static int aac_sample_rates[16] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; cParserAAC::cParserAAC(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_Configured = false; m_FrameLengthType = 0; m_PTS = 0; m_DTS = 0; m_FrameSize = 0; m_SampleRate = 0; m_Channels = 0; m_BitRate = 0; m_PesBufferInitialSize = 1920*2; m_DetectMuxMode = false; char *detectEnv = getenv("VNSI_AAC_MUXMODE"); if (detectEnv) { m_DetectMuxMode = true; INFOLOG("cParserAAC - detect muxing mode while parsing"); } } cParserAAC::~cParserAAC() { } void cParserAAC::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { int p = m_PesParserPtr; int l; while ((l = m_PesBufferPtr - p) > 8) { if (FindHeaders(m_PesBuffer + p, l) < 0) break; p++; } m_PesParserPtr = p; if (m_FoundFrame && l >= m_FrameSize) { bool streamChange = m_Stream->SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); pkt->id = m_pID; pkt->data = &m_PesBuffer[p]; pkt->size = m_FrameSize; if(!m_SampleRate) m_SampleRate = aac_sample_rates[4]; pkt->duration = 1024 * 90000 / m_SampleRate; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->streamChange = streamChange; m_PesNextFramePtr = p + m_FrameSize; m_PesParserPtr = 0; m_FoundFrame = false; } } int cParserAAC::FindHeaders(uint8_t *buf, int buf_size) { if (m_FoundFrame) return -1; uint8_t *buf_ptr = buf; if (m_Stream->Type() == stAACLATM) { if ((buf_ptr[0] == 0x56 && (buf_ptr[1] & 0xE0) == 0xE0)) { // TODO if (buf_size < 16) return -1; cBitstream bs(buf_ptr, 16 * 8); bs.skipBits(11); m_FrameSize = bs.readBits(13) + 3; if (!ParseLATMAudioMuxElement(&bs)) return 0; m_FoundFrame = true; m_DTS = m_curPTS; m_PTS = m_curPTS; if(!m_SampleRate) m_SampleRate = aac_sample_rates[4]; m_curPTS += 90000 * 1024 / m_SampleRate; return -1; } else if (m_DetectMuxMode && buf_ptr[0] == 0xFF && (buf_ptr[1] & 0xF0) == 0xF0) { m_Stream->SetType(stAACADTS); INFOLOG("cParserAAC::FindHeaders - detected ADTS muxing mode"); return -1; } } else if (m_Stream->Type() == stAACADTS) { if(buf_ptr[0] == 0xFF && (buf_ptr[1] & 0xF0) == 0xF0) { // need at least 7 bytes for header if (buf_size < 7) return -1; cBitstream bs(buf_ptr, 9 * 8); bs.skipBits(15); // check if CRC is present, means header is 9 byte long int noCrc = bs.readBits(1); if (!noCrc && (buf_size < 9)) return -1; bs.skipBits(2); // profile int SampleRateIndex = bs.readBits(4); bs.skipBits(1); // private m_Channels = bs.readBits(3); bs.skipBits(4); m_FrameSize = bs.readBits(13); m_SampleRate = aac_sample_rates[SampleRateIndex & 0x0E]; if (!m_SampleRate) m_SampleRate = aac_sample_rates[4]; m_FoundFrame = true; m_DTS = m_curPTS; m_PTS = m_curPTS; m_curPTS += 90000 * 1024 / m_SampleRate; return -1; } else if (m_DetectMuxMode && buf_ptr[0] == 0x56 && (buf_ptr[1] & 0xE0) == 0xE0) { m_Stream->SetType(stAACLATM); INFOLOG("cParserAAC::FindHeaders - detected LATM muxing mode"); return -1; } } return 0; } bool cParserAAC::ParseLATMAudioMuxElement(cBitstream *bs) { if (!bs->readBits1()) ReadStreamMuxConfig(bs); if (!m_Configured) return false; return true; } void cParserAAC::ReadStreamMuxConfig(cBitstream *bs) { int AudioMuxVersion = bs->readBits(1); m_AudioMuxVersion_A = 0; if (AudioMuxVersion) // audioMuxVersion m_AudioMuxVersion_A = bs->readBits(1); if(m_AudioMuxVersion_A) return; if (AudioMuxVersion) LATMGetValue(bs); // taraFullness bs->skipBits(1); // allStreamSameTimeFraming = 1 bs->skipBits(6); // numSubFrames = 0 bs->skipBits(4); // numPrograms = 0 // for each program (which there is only on in DVB) bs->skipBits(3); // numLayer = 0 // for each layer (which there is only on in DVB) if (!AudioMuxVersion) ReadAudioSpecificConfig(bs); else return; // these are not needed... perhaps m_FrameLengthType = bs->readBits(3); switch (m_FrameLengthType) { case 0: bs->readBits(8); break; case 1: bs->readBits(9); break; case 3: case 4: case 5: bs->readBits(6); // celp_table_index break; case 6: case 7: bs->readBits(1); // hvxc_table_index break; } if (bs->readBits(1)) { // other data? if (AudioMuxVersion == 1) { LATMGetValue(bs); // other_data_bits } else { int esc; do { esc = bs->readBits(1); bs->skipBits(8); } while (esc); } } if (bs->readBits(1)) // crc present? bs->skipBits(8); // config_crc m_Configured = true; } void cParserAAC::ReadAudioSpecificConfig(cBitstream *bs) { int aot = bs->readBits(5); if (aot == 31) aot = 32 + bs->readBits(6); int SampleRateIndex = bs->readBits(4); if (SampleRateIndex == 0xf) m_SampleRate = bs->readBits(24); else m_SampleRate = aac_sample_rates[SampleRateIndex & 0xf]; m_Channels = bs->readBits(4); if (aot == 5) { // AOT_SBR if (bs->readBits(4) == 0xf) { // extensionSamplingFrequencyIndex bs->skipBits(24); } aot = bs->readBits(5); // this is the main object type (i.e. non-extended) if (aot == 31) aot = 32 + bs->readBits(6); } if(aot != 2) return; bs->skipBits(1); //framelen_flag if (bs->readBits1()) // depends_on_coder bs->skipBits(14); if (bs->readBits(1)) // ext_flag bs->skipBits(1); // ext3_flag } void cParserAAC::Reset() { cParser::Reset(); m_Configured = false; } vdr-plugin-vnsiserver-1.5.2/parser_AAC.h000066400000000000000000000036201303643374600201350ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_AAC_H #define VNSI_DEMUXER_AAC_H #include "parser.h" #include "bitstream.h" // --- cParserAAC ------------------------------------------------- class cParserAAC : public cParser { private: int m_SampleRate; int m_Channels; int m_BitRate; int m_FrameSize; int64_t m_PTS; /* pts of the current frame */ int64_t m_DTS; /* dts of the current frame */ bool m_Configured; int m_AudioMuxVersion_A; int m_FrameLengthType; bool m_DetectMuxMode; int FindHeaders(uint8_t *buf, int buf_size); bool ParseLATMAudioMuxElement(cBitstream *bs); void ReadStreamMuxConfig(cBitstream *bs); void ReadAudioSpecificConfig(cBitstream *bs); uint32_t LATMGetValue(cBitstream *bs) { return bs->readBits(bs->readBits(2) * 8); } public: cParserAAC(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserAAC(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); virtual void Reset(); }; #endif // VNSI_DEMUXER_AAC_H vdr-plugin-vnsiserver-1.5.2/parser_AC3.c000066400000000000000000000143101303643374600201100ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_AC3.h" #include "bitstream.h" #include "config.h" #include #include #define AC3_HEADER_SIZE 7 /** Channel mode (audio coding mode) */ typedef enum { AC3_CHMODE_DUALMONO = 0, AC3_CHMODE_MONO, AC3_CHMODE_STEREO, AC3_CHMODE_3F, AC3_CHMODE_2F1R, AC3_CHMODE_3F1R, AC3_CHMODE_2F2R, AC3_CHMODE_3F2R } AC3ChannelMode; /* possible frequencies */ const uint16_t AC3SampleRateTable[3] = { 48000, 44100, 32000 }; /* possible bitrates */ const uint16_t AC3BitrateTable[19] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 }; const uint8_t AC3ChannelsTable[8] = { 2, 1, 2, 3, 3, 4, 4, 5 }; const uint16_t AC3FrameSizeTable[38][3] = { { 64, 69, 96 }, { 64, 70, 96 }, { 80, 87, 120 }, { 80, 88, 120 }, { 96, 104, 144 }, { 96, 105, 144 }, { 112, 121, 168 }, { 112, 122, 168 }, { 128, 139, 192 }, { 128, 140, 192 }, { 160, 174, 240 }, { 160, 175, 240 }, { 192, 208, 288 }, { 192, 209, 288 }, { 224, 243, 336 }, { 224, 244, 336 }, { 256, 278, 384 }, { 256, 279, 384 }, { 320, 348, 480 }, { 320, 349, 480 }, { 384, 417, 576 }, { 384, 418, 576 }, { 448, 487, 672 }, { 448, 488, 672 }, { 512, 557, 768 }, { 512, 558, 768 }, { 640, 696, 960 }, { 640, 697, 960 }, { 768, 835, 1152 }, { 768, 836, 1152 }, { 896, 975, 1344 }, { 896, 976, 1344 }, { 1024, 1114, 1536 }, { 1024, 1115, 1536 }, { 1152, 1253, 1728 }, { 1152, 1254, 1728 }, { 1280, 1393, 1920 }, { 1280, 1394, 1920 }, }; const uint8_t EAC3Blocks[4] = { 1, 2, 3, 6 }; typedef enum { EAC3_FRAME_TYPE_INDEPENDENT = 0, EAC3_FRAME_TYPE_DEPENDENT, EAC3_FRAME_TYPE_AC3_CONVERT, EAC3_FRAME_TYPE_RESERVED } EAC3FrameType; cParserAC3::cParserAC3(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_PTS = 0; m_DTS = 0; m_FrameSize = 0; m_SampleRate = 0; m_Channels = 0; m_BitRate = 0; m_PesBufferInitialSize = 1920*2; } cParserAC3::~cParserAC3() { } void cParserAC3::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { int p = m_PesParserPtr; int l; while ((l = m_PesBufferPtr - p) > 8) { if (FindHeaders(m_PesBuffer + p, l) < 0) break; p++; } m_PesParserPtr = p; if (m_FoundFrame && l >= m_FrameSize) { bool streamChange = m_Stream->SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); pkt->id = m_pID; pkt->data = &m_PesBuffer[p]; pkt->size = m_FrameSize; pkt->duration = 90000 * 1536 / m_SampleRate; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->streamChange = streamChange; m_PesNextFramePtr = p + m_FrameSize; m_PesParserPtr = 0; m_FoundFrame = false; } } int cParserAC3::FindHeaders(uint8_t *buf, int buf_size) { if (m_FoundFrame) return -1; if (buf_size < 9) return -1; uint8_t *buf_ptr = buf; if ((buf_ptr[0] == 0x0b && buf_ptr[1] == 0x77)) { cBitstream bs(buf_ptr + 2, AC3_HEADER_SIZE * 8); // read ahead to bsid to distinguish between AC-3 and E-AC-3 int bsid = bs.showBits(29) & 0x1F; if (bsid > 16) return 0; if (bsid <= 10) { // Normal AC-3 bs.skipBits(16); int fscod = bs.readBits(2); int frmsizecod = bs.readBits(6); bs.skipBits(5); // skip bsid, already got it bs.skipBits(3); // skip bitstream mode int acmod = bs.readBits(3); if (fscod == 3 || frmsizecod > 37) return 0; if (acmod == AC3_CHMODE_STEREO) { bs.skipBits(2); // skip dsurmod } else { if ((acmod & 1) && acmod != AC3_CHMODE_MONO) bs.skipBits(2); if (acmod & 4) bs.skipBits(2); } int lfeon = bs.readBits(1); int srShift = max(bsid, 8) - 8; m_SampleRate = AC3SampleRateTable[fscod] >> srShift; m_BitRate = (AC3BitrateTable[frmsizecod>>1] * 1000) >> srShift; m_Channels = AC3ChannelsTable[acmod] + lfeon; m_FrameSize = AC3FrameSizeTable[frmsizecod][fscod] * 2; } else { // Enhanced AC-3 int frametype = bs.readBits(2); if (frametype == EAC3_FRAME_TYPE_RESERVED) return 0; bs.readBits(3); // int substreamid m_FrameSize = (bs.readBits(11) + 1) << 1; if (m_FrameSize < AC3_HEADER_SIZE) return 0; int numBlocks = 6; int sr_code = bs.readBits(2); if (sr_code == 3) { int sr_code2 = bs.readBits(2); if (sr_code2 == 3) return 0; m_SampleRate = AC3SampleRateTable[sr_code2] / 2; } else { numBlocks = EAC3Blocks[bs.readBits(2)]; m_SampleRate = AC3SampleRateTable[sr_code]; } int channelMode = bs.readBits(3); int lfeon = bs.readBits(1); m_BitRate = (uint32_t)(8.0 * m_FrameSize * m_SampleRate / (numBlocks * 256.0)); m_Channels = AC3ChannelsTable[channelMode] + lfeon; } m_FoundFrame = true; m_DTS = m_curPTS; m_PTS = m_curPTS; m_curPTS += 90000 * 1536 / m_SampleRate; return -1; } return 0; } void cParserAC3::Reset() { cParser::Reset(); } vdr-plugin-vnsiserver-1.5.2/parser_AC3.h000066400000000000000000000030301303643374600201120ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_AC3_H #define VNSI_DEMUXER_AC3_H #include "parser.h" // --- cParserAC3 ------------------------------------------------- class cParserAC3 : public cParser { private: int m_SampleRate; int m_Channels; int m_BitRate; int m_FrameSize; int64_t m_PTS; /* pts of the current frame */ int64_t m_DTS; /* dts of the current frame */ int FindHeaders(uint8_t *buf, int buf_size); public: cParserAC3(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserAC3(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); virtual void Reset(); }; #endif // VNSI_DEMUXER_AC3_H vdr-plugin-vnsiserver-1.5.2/parser_DTS.c000066400000000000000000000022561303643374600202020ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_DTS.h" #include "bitstream.h" #include "config.h" #include #include cParserDTS::cParserDTS(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { } cParserDTS::~cParserDTS() { } void cParserDTS::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { } vdr-plugin-vnsiserver-1.5.2/parser_DTS.h000066400000000000000000000023371303643374600202070ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_DTS_H #define VNSI_DEMUXER_DTS_H #include "parser.h" // --- cParserDTS ------------------------------------------------- class cParserDTS : public cParser { private: public: cParserDTS(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserDTS(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); }; #endif // VNSI_DEMUXER_DTS_H vdr-plugin-vnsiserver-1.5.2/parser_MPEGAudio.c000066400000000000000000000151141303643374600212570ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_MPEGAudio.h" #include "bitstream.h" #include "config.h" #include #include #define MAX_RDS_BUFFER_SIZE 100000 const uint16_t FrequencyTable[3] = { 44100, 48000, 32000 }; const uint16_t BitrateTable[2][3][15] = { { {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 } }, { {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} } }; cParserMPEG2Audio::cParserMPEG2Audio(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps, bool enableRDS) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_PTS = 0; m_DTS = 0; m_FrameSize = 0; m_SampleRate = 0; m_Channels = 0; m_BitRate = 0; m_PesBufferInitialSize = 2048; m_RDSEnabled = enableRDS; m_RDSBufferInitialSize = 384; m_RDSBuffer = NULL; m_RDSBufferSize = 0; m_RDSExtPID = 0; } cParserMPEG2Audio::~cParserMPEG2Audio() { free(m_RDSBuffer); } void cParserMPEG2Audio::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { int p = m_PesParserPtr; int l; while ((l = m_PesBufferPtr - p) > 3) { if (FindHeaders(m_PesBuffer + p, l) < 0) break; p++; } m_PesParserPtr = p; if (m_FoundFrame && l >= m_FrameSize) { bool streamChange = m_Stream->SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); pkt->id = m_pID; pkt->data = &m_PesBuffer[p]; pkt->size = m_FrameSize; pkt->duration = 90000 * 1152 / m_SampleRate; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->streamChange = streamChange; m_PesNextFramePtr = p + m_FrameSize; m_PesParserPtr = 0; m_FoundFrame = false; if (m_RDSEnabled) { /* * Reading of RDS Universal Encoder Communication Protocol * If present it starts on end of a mpeg audio stream and goes * backwards. * See ETSI TS 101 154 - C.4.2.18 for details. */ int rdsl = m_PesBuffer[p+m_FrameSize-2]; // RDS DataFieldLength if (m_PesBuffer[p+m_FrameSize-1] == 0xfd && rdsl > 0) // RDS DataSync = 0xfd @ end { if (m_RDSBuffer == NULL) { m_RDSBufferSize = m_RDSBufferInitialSize; m_RDSBuffer = (uint8_t*)malloc(m_RDSBufferSize); if (m_RDSBuffer == NULL) { ERRORLOG("PVR Parser MPEG2-Audio - %s - malloc failed for RDS data", __FUNCTION__); m_RDSEnabled = false; return; } m_RDSExtPID = m_Stream->AddSideDataType(scRDS); if (!m_RDSExtPID) { ERRORLOG("PVR Parser MPEG2-Audio - %s - failed to add RDS data stream", __FUNCTION__); m_RDSEnabled = false; return; } } if (rdsl >= m_RDSBufferSize) { if (rdsl >= MAX_RDS_BUFFER_SIZE) { ERRORLOG("PVR Parser MPEG2-Audio - %s - max buffer size (%i kB) for RDS data reached, pid: %d", __FUNCTION__, MAX_RDS_BUFFER_SIZE/1024, m_pID); m_RDSEnabled = false; return; } m_RDSBufferSize += m_RDSBufferInitialSize / 10; uint8_t *new_buffer = (uint8_t *)realloc(m_RDSBuffer, m_RDSBufferSize); if (new_buffer == NULL) { ERRORLOG("PVR Parser MPEG2-Audio - %s - realloc for RDS data failed", __FUNCTION__); m_RDSEnabled = false; return; } m_RDSBuffer = new_buffer; } int pes_buffer_ptr = 0; for (int i = m_FrameSize-3; i > m_FrameSize-3-rdsl; i--) // <-- data reverse, from end to start m_RDSBuffer[pes_buffer_ptr++] = m_PesBuffer[p+i]; pkt_side_data->id = m_RDSExtPID; pkt_side_data->data = m_RDSBuffer; pkt_side_data->size = pes_buffer_ptr; pkt_side_data->duration = 0; pkt_side_data->dts = m_curDTS; pkt_side_data->pts = m_curPTS; pkt_side_data->streamChange = false; } } } } int cParserMPEG2Audio::FindHeaders(uint8_t *buf, int buf_size) { if (m_FoundFrame) return -1; if (buf_size < 4) return -1; uint8_t *buf_ptr = buf; if ((buf_ptr[0] == 0xFF && (buf_ptr[1] & 0xE0) == 0xE0)) { cBitstream bs(buf_ptr, 4 * 8); bs.skipBits(11); // syncword int audioVersion = bs.readBits(2); if (audioVersion == 1) return 0; int mpeg2 = !(audioVersion & 1); int mpeg25 = !(audioVersion & 3); int layer = bs.readBits(2); if (layer == 0) return 0; layer = 4 - layer; bs.skipBits(1); // protetion bit int bitrate_index = bs.readBits(4); if (bitrate_index == 15 || bitrate_index == 0) return 0; m_BitRate = BitrateTable[mpeg2][layer - 1][bitrate_index] * 1000; int sample_rate_index = bs.readBits(2); if (sample_rate_index == 3) return 0; m_SampleRate = FrequencyTable[sample_rate_index] >> (mpeg2 + mpeg25); int padding = bs.readBits1(); bs.skipBits(1); // private bit int channel_mode = bs.readBits(2); if (channel_mode == 11) m_Channels = 1; else m_Channels = 2; if (layer == 1) m_FrameSize = (12 * m_BitRate / m_SampleRate + padding) * 4; else m_FrameSize = 144 * m_BitRate / m_SampleRate + padding; m_FoundFrame = true; m_DTS = m_curPTS; m_PTS = m_curPTS; m_curPTS += 90000 * 1152 / m_SampleRate; return -1; } return 0; } vdr-plugin-vnsiserver-1.5.2/parser_MPEGAudio.h000066400000000000000000000032301303643374600212600ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_MPEGAUDIO_H #define VNSI_DEMUXER_MPEGAUDIO_H #include "parser.h" // --- cParserMPEG2Audio ------------------------------------------------- class cParserMPEG2Audio : public cParser { private: int m_SampleRate; int m_Channels; int m_BitRate; int m_FrameSize; int64_t m_PTS; int64_t m_DTS; bool m_RDSEnabled; uint32_t m_RDSExtPID; uint8_t *m_RDSBuffer; int m_RDSBufferSize; int m_RDSBufferPtr; size_t m_RDSBufferInitialSize; int FindHeaders(uint8_t *buf, int buf_size); public: cParserMPEG2Audio(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps, bool enableRDS); virtual ~cParserMPEG2Audio(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); }; #endif // VNSI_DEMUXER_MPEGAUDIO_H vdr-plugin-vnsiserver-1.5.2/parser_MPEGVideo.c000066400000000000000000000140441303643374600212650ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_MPEGVideo.h" #include "bitstream.h" #include "config.h" #include #include using namespace std; #define MPEG_PICTURE_START 0x00000100 #define MPEG_SEQUENCE_START 0x000001b3 #define MPEG_SEQUENCE_EXTENSION 0x000001b5 #define MPEG_SLICE_S 0x00000101 #define MPEG_SLICE_E 0x000001af /** * MPEG2VIDEO frame duration table (in 90kHz clock domain) */ const unsigned int mpeg2video_framedurations[16] = { 0, 3753, 3750, 3600, 3003, 3000, 1800, 1501, 1500, }; cParserMPEG2Video::cParserMPEG2Video(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_FrameDuration = 0; m_vbvDelay = -1; m_vbvSize = 0; m_Height = 0; m_Width = 0; m_Dar = 0.0; m_FpsScale = 0; m_PesBufferInitialSize = 80000; m_IsVideo = true; Reset(); } cParserMPEG2Video::~cParserMPEG2Video() { } void cParserMPEG2Video::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { if (m_PesBufferPtr < 4) return; int p = m_PesParserPtr; uint32_t startcode = m_StartCode; bool frameComplete = false; int l; while ((l = m_PesBufferPtr - p) > 3) { if ((startcode & 0xffffff00) == 0x00000100) { if (Parse_MPEG2Video(startcode, p, frameComplete) < 0) { break; } } startcode = startcode << 8 | m_PesBuffer[p++]; } m_PesParserPtr = p; m_StartCode = startcode; if (frameComplete) { if (!m_NeedSPS && !m_NeedIFrame && m_FrameValid) { if (m_FpsScale == 0) { if (m_FrameDuration != DVD_NOPTS_VALUE) m_FpsScale = m_Stream->Rescale(m_FrameDuration, DVD_TIME_BASE, 90000); else m_FpsScale = 40000; } bool streamChange = m_Stream->SetVideoInformation(m_FpsScale, DVD_TIME_BASE, m_Height, m_Width, m_Dar); pkt->id = m_pID; pkt->size = m_PesNextFramePtr; pkt->data = m_PesBuffer; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->duration = m_FrameDuration; pkt->streamChange = streamChange; } m_StartCode = 0xffffffff; m_PesParserPtr = 0; m_FoundFrame = false; m_FrameValid = true; } } void cParserMPEG2Video::Reset() { cParser::Reset(); m_StartCode = 0xffffffff; m_NeedIFrame = true; m_NeedSPS = true; } int cParserMPEG2Video::Parse_MPEG2Video(uint32_t startcode, int buf_ptr, bool &complete) { int len = m_PesBufferPtr - buf_ptr; uint8_t *buf = m_PesBuffer + buf_ptr; switch (startcode & 0xFF) { case 0: // picture start { if (m_NeedSPS) { m_FoundFrame = true; return 0; } if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } if (len < 4) return -1; if (!Parse_MPEG2Video_PicStart(buf)) return 0; if (!m_FoundFrame) { m_AuPrevDTS = m_AuDTS; if (buf_ptr - 4 >= m_PesTimePos) { m_AuDTS = m_curDTS != DVD_NOPTS_VALUE ? m_curDTS : m_curPTS; m_AuPTS = m_curPTS; } else { m_AuDTS = m_prevDTS != DVD_NOPTS_VALUE ? m_prevDTS : m_prevPTS;; m_AuPTS = m_prevPTS; } } if (m_AuPrevDTS == m_AuDTS) { m_DTS = m_AuDTS + m_PicNumber*m_FrameDuration; m_PTS = m_AuPTS + (m_TemporalReference-m_TrLastTime)*m_FrameDuration; } else { m_PTS = m_AuPTS; m_DTS = m_AuDTS; m_PicNumber = 0; m_TrLastTime = m_TemporalReference; } m_PicNumber++; m_FoundFrame = true; break; } case 0xb3: // Sequence start code { if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } if (len < 8) return -1; if (!Parse_MPEG2Video_SeqStart(buf)) return 0; break; } case 0xb7: // sequence end { if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr; return -1; } break; } default: break; } return 0; } bool cParserMPEG2Video::Parse_MPEG2Video_SeqStart(uint8_t *buf) { cBitstream bs(buf, 8 * 8); m_Width = bs.readBits(12); m_Height = bs.readBits(12); // figure out Display Aspect Ratio uint8_t aspect = bs.readBits(4); switch(aspect) { case 1: m_Dar = 1.0; break; case 2: m_Dar = 4.0/3.0; break; case 3: m_Dar = 16.0/9.0; break; case 4: m_Dar = 2.21; break; default: ERRORLOG("invalid / forbidden DAR in sequence header !"); return false; } m_FrameDuration = mpeg2video_framedurations[bs.readBits(4)]; bs.skipBits(18); bs.skipBits(1); m_vbvSize = bs.readBits(10) * 16 * 1024 / 8; m_NeedSPS = false; return true; } bool cParserMPEG2Video::Parse_MPEG2Video_PicStart(uint8_t *buf) { cBitstream bs(buf, 4 * 8); m_TemporalReference = bs.readBits(10); /* temporal reference */ int pct = bs.readBits(3); if (pct < PKT_I_FRAME || pct > PKT_B_FRAME) return true; /* Illegal picture_coding_type */ if (pct == PKT_I_FRAME) m_NeedIFrame = false; int vbvDelay = bs.readBits(16); /* vbv_delay */ if (vbvDelay == 0xffff) m_vbvDelay = -1; else m_vbvDelay = vbvDelay; return true; } vdr-plugin-vnsiserver-1.5.2/parser_MPEGVideo.h000066400000000000000000000040431303643374600212700ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_MPEGVIDEO_H #define VNSI_DEMUXER_MPEGVIDEO_H #include #include "parser.h" class cBitstream; // --- cParserMPEG2Video ------------------------------------------------- class cParserMPEG2Video : public cParser { private: uint32_t m_StartCode; bool m_NeedIFrame; bool m_NeedSPS; int m_FrameDuration; int m_vbvDelay; /* -1 if CBR */ int m_vbvSize; /* Video buffer size (in bytes) */ int m_Width; int m_Height; float m_Dar; int64_t m_DTS; int64_t m_PTS; int64_t m_AuDTS, m_AuPTS, m_AuPrevDTS; int m_TemporalReference; int m_TrLastTime; int m_PicNumber; int m_FpsScale; int Parse_MPEG2Video(uint32_t startcode, int buf_ptr, bool &complete); bool Parse_MPEG2Video_SeqStart(uint8_t *buf); bool Parse_MPEG2Video_PicStart(uint8_t *buf); public: cParserMPEG2Video(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserMPEG2Video(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); virtual void Reset(); }; #endif // VNSI_DEMUXER_MPEGVIDEO_H vdr-plugin-vnsiserver-1.5.2/parser_Subtitle.c000066400000000000000000000032401303643374600213350ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_Subtitle.h" #include "config.h" #include #include cParserSubtitle::cParserSubtitle(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_PesBufferInitialSize = 4000; } cParserSubtitle::~cParserSubtitle() { } void cParserSubtitle::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { int l = m_PesBufferPtr; if (l >= m_PesPacketLength) { if (l < 2 || m_PesBuffer[0] != 0x20 || m_PesBuffer[1] != 0x00) { Reset(); return; } if(m_PesBuffer[m_PesPacketLength-1] == 0xff) { pkt->id = m_pID; pkt->data = m_PesBuffer+2; pkt->size = m_PesPacketLength-3; pkt->duration = 0; pkt->dts = m_curDTS; pkt->pts = m_curPTS; } m_PesBufferPtr = 0; } } vdr-plugin-vnsiserver-1.5.2/parser_Subtitle.h000066400000000000000000000023701303643374600213450ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_SUBTITLE_H #define VNSI_DEMUXER_SUBTITLE_H #include "parser.h" // --- cParserSubtitle ------------------------------------------------- class cParserSubtitle : public cParser { public: cParserSubtitle(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserSubtitle(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); }; #endif // VNSI_DEMUXER_SUBTITLE_H vdr-plugin-vnsiserver-1.5.2/parser_Teletext.c000066400000000000000000000031071303643374600213420ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_Teletext.h" #include "config.h" #include cParserTeletext::cParserTeletext(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_PesBufferInitialSize = 4000; } cParserTeletext::~cParserTeletext() { } void cParserTeletext::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { int l = m_PesBufferPtr; if (l < 1) return; if (m_PesBuffer[0] < 0x10 || m_PesBuffer[0] > 0x1F) { Reset(); return; } if (l >= m_PesPacketLength) { pkt->id = m_pID; pkt->data = m_PesBuffer; pkt->size = m_PesPacketLength; pkt->duration = 0; pkt->dts = m_curDTS; pkt->pts = m_curPTS; m_PesBufferPtr = 0; } } vdr-plugin-vnsiserver-1.5.2/parser_Teletext.h000066400000000000000000000024631303643374600213530ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_TELETEXT_H #define VNSI_DEMUXER_TELETEXT_H #include "parser.h" // --- cParserTeletext ------------------------------------------------- class cParserTeletext : public cParser { private: int64_t m_lastDTS; int64_t m_lastPTS; public: cParserTeletext(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserTeletext(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); }; #endif // VNSI_DEMUXER_TELETEXT_H vdr-plugin-vnsiserver-1.5.2/parser_h264.c000066400000000000000000000400421303643374600202260ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "parser_h264.h" #include "bitstream.h" #include "config.h" #include #include static const int h264_lev2cpbsize[][2] = { {10, 175}, {11, 500}, {12, 1000}, {13, 2000}, {20, 2000}, {21, 4000}, {22, 4000}, {30, 10000}, {31, 14000}, {32, 20000}, {40, 25000}, {41, 62500}, {42, 62500}, {50, 135000}, {51, 240000}, {-1, -1}, }; cParserH264::cParserH264(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_Height = 0; m_Width = 0; m_FPS = 25; m_FpsScale = 0; m_FrameDuration = 0; m_vbvDelay = -1; m_vbvSize = 0; m_PixelAspect.den = 1; m_PixelAspect.num = 0; memset(&m_streamData, 0, sizeof(m_streamData)); m_PesBufferInitialSize = 240000; m_IsVideo = true; Reset(); } cParserH264::~cParserH264() { } void cParserH264::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { if (m_PesBufferPtr < 4) return; int p = m_PesParserPtr; uint32_t startcode = m_StartCode; bool frameComplete = false; int l; while ((l = m_PesBufferPtr - p) > 3) { if ((startcode & 0xffffff00) == 0x00000100) { if (Parse_H264(startcode, p, frameComplete) < 0) { break; } } startcode = startcode << 8 | m_PesBuffer[p++]; } m_PesParserPtr = p; m_StartCode = startcode; if (frameComplete) { if (!m_NeedSPS && !m_NeedIFrame && m_FrameValid) { double PAR = (double)m_PixelAspect.num/(double)m_PixelAspect.den; double DAR = (PAR * m_Width) / m_Height; DEBUGLOG("H.264 SPS: PAR %i:%i", m_PixelAspect.num, m_PixelAspect.den); DEBUGLOG("H.264 SPS: DAR %.2f", DAR); int duration; if (m_curDTS != DVD_NOPTS_VALUE && m_prevDTS != DVD_NOPTS_VALUE && m_curDTS > m_prevDTS) duration = m_curDTS - m_prevDTS; else duration = m_Stream->Rescale(20000, 90000, DVD_TIME_BASE); if (m_FpsScale == 0) { m_FpsScale = m_Stream->Rescale(duration, DVD_TIME_BASE, 90000); } bool streamChange = m_Stream->SetVideoInformation(m_FpsScale, DVD_TIME_BASE, m_Height, m_Width, DAR); pkt->id = m_pID; pkt->size = m_PesNextFramePtr; pkt->data = m_PesBuffer; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->duration = duration; pkt->streamChange = streamChange; } m_StartCode = 0xffffffff; m_PesParserPtr = 0; m_FoundFrame = false; m_FrameValid = true; } } void cParserH264::Reset() { cParser::Reset(); m_StartCode = 0xffffffff; m_NeedIFrame = true; m_NeedSPS = true; m_NeedPPS = true; memset(&m_streamData, 0, sizeof(m_streamData)); } int cParserH264::Parse_H264(uint32_t startcode, int buf_ptr, bool &complete) { int len = m_PesBufferPtr - buf_ptr; uint8_t *buf = m_PesBuffer + buf_ptr; switch(startcode & 0x9f) { case 1 ... 5: { if (m_NeedSPS || m_NeedPPS) { m_FoundFrame = true; return 0; } // need at least 32 bytes for parsing nal if (len < 32) return -1; h264_private::VCL_NAL vcl; memset(&vcl, 0, sizeof(h264_private::VCL_NAL)); vcl.nal_ref_idc = startcode & 0x60; vcl.nal_unit_type = startcode & 0x1F; if (!Parse_SLH(buf, len, vcl)) return 0; // check for the beginning of a new access unit if (m_FoundFrame && IsFirstVclNal(vcl)) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } if (!m_FoundFrame) { if (buf_ptr - 4 >= m_PesTimePos) { m_DTS = m_curDTS; m_PTS = m_curPTS; } else { m_DTS = m_prevDTS; m_PTS = m_prevPTS; } } m_streamData.vcl_nal = vcl; m_FoundFrame = true; break; } case NAL_SEI: if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } break; case NAL_SPS: { if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } // TODO: how big is SPS? if (len < 256) return -1; if (!Parse_SPS(buf, len)) return 0; m_NeedSPS = false; break; } case NAL_PPS: { if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } // TODO: how big is PPS if (len < 64) return -1; if (!Parse_PPS(buf, len)) return 0; m_NeedPPS = false; break; } case NAL_AUD: if (m_FoundFrame && (m_prevPTS != DVD_NOPTS_VALUE)) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } break; case NAL_END_SEQ: if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr; return -1; } break; case 13 ... 18: if (m_FoundFrame) { complete = true; m_PesNextFramePtr = buf_ptr - 4; return -1; } break; default: break; } return 0; } bool cParserH264::Parse_PPS(uint8_t *buf, int len) { cBitstream bs(buf, len*8); int pps_id = bs.readGolombUE(); int sps_id = bs.readGolombUE(); m_streamData.pps[pps_id].sps = sps_id; bs.readBits1(); m_streamData.pps[pps_id].pic_order_present_flag = bs.readBits1(); return true; } bool cParserH264::Parse_SLH(uint8_t *buf, int len, h264_private::VCL_NAL &vcl) { cBitstream bs(buf, len*8); bs.readGolombUE(); /* first_mb_in_slice */ int slice_type = bs.readGolombUE(); if (slice_type > 4) slice_type -= 5; /* Fixed slice type per frame */ switch (slice_type) { case 0: break; case 1: break; case 2: m_NeedIFrame = false; break; default: return false; } int pps_id = bs.readGolombUE(); int sps_id = m_streamData.pps[pps_id].sps; if (m_streamData.sps[sps_id].cbpsize == 0) return false; m_vbvSize = m_streamData.sps[sps_id].cbpsize; m_vbvDelay = -1; vcl.pic_parameter_set_id = pps_id; vcl.frame_num = bs.readBits(m_streamData.sps[sps_id].log2_max_frame_num); if (!m_streamData.sps[sps_id].frame_mbs_only_flag) { vcl.field_pic_flag = bs.readBits1(); // interlaced // if (vcl.field_pic_flag) // m_FPS *= 2; } if (vcl.field_pic_flag) vcl.bottom_field_flag = bs.readBits1(); if (vcl.nal_unit_type == 5) vcl.idr_pic_id = bs.readGolombUE(); if (m_streamData.sps[sps_id].pic_order_cnt_type == 0) { vcl.pic_order_cnt_lsb = bs.readBits(m_streamData.sps[sps_id].log2_max_pic_order_cnt_lsb); if(m_streamData.pps[pps_id].pic_order_present_flag && !vcl.field_pic_flag) vcl.delta_pic_order_cnt_bottom = bs.readGolombSE(); } if(m_streamData.sps[sps_id].pic_order_cnt_type == 1 && !m_streamData.sps[sps_id].delta_pic_order_always_zero_flag ) { vcl.delta_pic_order_cnt_0 = bs.readGolombSE(); if(m_streamData.pps[pps_id].pic_order_present_flag && !vcl.field_pic_flag ) vcl.delta_pic_order_cnt_1 = bs.readGolombSE(); } vcl.pic_order_cnt_type = m_streamData.sps[sps_id].pic_order_cnt_type; return true; } bool cParserH264::Parse_SPS(uint8_t *buf, int len) { cBitstream bs(buf, len*8); unsigned int tmp, frame_mbs_only; int cbpsize = -1; int profile_idc = bs.readBits(8); /* constraint_set0_flag = bs.readBits1(); */ /* constraint_set1_flag = bs.readBits1(); */ /* constraint_set2_flag = bs.readBits1(); */ /* constraint_set3_flag = bs.readBits1(); */ /* reserved = bs.readBits(4); */ bs.skipBits(8); int level_idc = bs.readBits(8); unsigned int seq_parameter_set_id = bs.readGolombUE(9); unsigned int i = 0; while (h264_lev2cpbsize[i][0] != -1) { if (h264_lev2cpbsize[i][0] >= level_idc) { cbpsize = h264_lev2cpbsize[i][1]; break; } i++; } if (cbpsize < 0) return false; memset(&m_streamData.sps[seq_parameter_set_id], 0, sizeof(h264_private::SPS)); m_streamData.sps[seq_parameter_set_id].cbpsize = cbpsize * 125; /* Convert from kbit to bytes */ if( profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc == 118 || profile_idc == 128 ) { int chroma_format_idc = bs.readGolombUE(9); /* chroma_format_idc */ if(chroma_format_idc == 3) bs.skipBits(1); /* residual_colour_transform_flag */ bs.readGolombUE(); /* bit_depth_luma - 8 */ bs.readGolombUE(); /* bit_depth_chroma - 8 */ bs.skipBits(1); /* transform_bypass */ if (bs.readBits1()) /* seq_scaling_matrix_present */ { for (int i = 0; i < ((chroma_format_idc != 3) ? 8 : 12); i++) { if (bs.readBits1()) /* seq_scaling_list_present */ { int last = 8, next = 8, size = (i<6) ? 16 : 64; for (int j = 0; j < size; j++) { if (next) next = (last + bs.readGolombSE()) & 0xff; last = !next ? last: next; } } } } } int log2_max_frame_num_minus4 = bs.readGolombUE(); /* log2_max_frame_num - 4 */ m_streamData.sps[seq_parameter_set_id].log2_max_frame_num = log2_max_frame_num_minus4 + 4; int pic_order_cnt_type = bs.readGolombUE(9); m_streamData.sps[seq_parameter_set_id].pic_order_cnt_type = pic_order_cnt_type; if (pic_order_cnt_type == 0) { int log2_max_pic_order_cnt_lsb_minus4 = bs.readGolombUE(); /* log2_max_poc_lsb - 4 */ m_streamData.sps[seq_parameter_set_id].log2_max_pic_order_cnt_lsb = log2_max_pic_order_cnt_lsb_minus4 + 4; } else if (pic_order_cnt_type == 1) { m_streamData.sps[seq_parameter_set_id].delta_pic_order_always_zero_flag = bs.readBits1(); bs.readGolombSE(); /* offset_for_non_ref_pic */ bs.readGolombSE(); /* offset_for_top_to_bottom_field */ tmp = bs.readGolombUE(); /* num_ref_frames_in_pic_order_cnt_cycle */ for (unsigned int i = 0; i < tmp; i++) bs.readGolombSE(); /* offset_for_ref_frame[i] */ } else if(pic_order_cnt_type != 2) { /* Illegal poc */ return false; } bs.readGolombUE(9); /* ref_frames */ bs.skipBits(1); /* gaps_in_frame_num_allowed */ m_Width /* mbs */ = bs.readGolombUE() + 1; m_Height /* mbs */ = bs.readGolombUE() + 1; frame_mbs_only = bs.readBits1(); m_streamData.sps[seq_parameter_set_id].frame_mbs_only_flag = frame_mbs_only; DEBUGLOG("H.264 SPS: pic_width: %u mbs", (unsigned) m_Width); DEBUGLOG("H.264 SPS: pic_height: %u mbs", (unsigned) m_Height); DEBUGLOG("H.264 SPS: frame only flag: %d", frame_mbs_only); m_Width *= 16; m_Height *= 16 * (2-frame_mbs_only); if (!frame_mbs_only) { if (bs.readBits1()) /* mb_adaptive_frame_field_flag */ DEBUGLOG("H.264 SPS: MBAFF"); } bs.skipBits(1); /* direct_8x8_inference_flag */ if (bs.readBits1()) /* frame_cropping_flag */ { uint32_t crop_left = bs.readGolombUE(); uint32_t crop_right = bs.readGolombUE(); uint32_t crop_top = bs.readGolombUE(); uint32_t crop_bottom = bs.readGolombUE(); DEBUGLOG("H.264 SPS: cropping %d %d %d %d", crop_left, crop_top, crop_right, crop_bottom); m_Width -= 2*(crop_left + crop_right); if (frame_mbs_only) m_Height -= 2*(crop_top + crop_bottom); else m_Height -= 4*(crop_top + crop_bottom); } /* VUI parameters */ m_PixelAspect.num = 0; if (bs.readBits1()) /* vui_parameters_present flag */ { if (bs.readBits1()) /* aspect_ratio_info_present */ { uint32_t aspect_ratio_idc = bs.readBits(8); DEBUGLOG("H.264 SPS: aspect_ratio_idc %d", aspect_ratio_idc); if (aspect_ratio_idc == 255 /* Extended_SAR */) { m_PixelAspect.num = bs.readBits(16); /* sar_width */ m_PixelAspect.den = bs.readBits(16); /* sar_height */ DEBUGLOG("H.264 SPS: -> sar %dx%d", m_PixelAspect.num, m_PixelAspect.den); } else { static const mpeg_rational_t aspect_ratios[] = { /* page 213: */ /* 0: unknown */ {0, 1}, /* 1...16: */ { 1, 1}, {12, 11}, {10, 11}, {16, 11}, { 40, 33}, {24, 11}, {20, 11}, {32, 11}, {80, 33}, {18, 11}, {15, 11}, {64, 33}, {160, 99}, { 4, 3}, { 3, 2}, { 2, 1} }; if (aspect_ratio_idc < sizeof(aspect_ratios)/sizeof(aspect_ratios[0])) { memcpy(&m_PixelAspect, &aspect_ratios[aspect_ratio_idc], sizeof(mpeg_rational_t)); DEBUGLOG("H.264 SPS: PAR %d / %d", m_PixelAspect.num, m_PixelAspect.den); } else { DEBUGLOG("H.264 SPS: aspect_ratio_idc out of range !"); } } } if (bs.readBits1()) // overscan { bs.readBits1(); // overscan_appropriate_flag } if (bs.readBits1()) // video_signal_type_present_flag { bs.readBits(3); // video_format bs.readBits1(); // video_full_range_flag if (bs.readBits1()) // colour_description_present_flag { bs.readBits(8); // colour_primaries bs.readBits(8); // transfer_characteristics bs.readBits(8); // matrix_coefficients } } if (bs.readBits1()) // chroma_loc_info_present_flag { bs.readGolombUE(); // chroma_sample_loc_type_top_field bs.readGolombUE(); // chroma_sample_loc_type_bottom_field } if (bs.readBits1()) // timing_info_present_flag { // uint32_t num_units_in_tick = bs.readBits(32); // uint32_t time_scale = bs.readBits(32); // int fixed_frame_rate = bs.readBits1(); // if (num_units_in_tick > 0) // m_FPS = time_scale / (num_units_in_tick * 2); } } DEBUGLOG("H.264 SPS: -> video size %dx%d, aspect %d:%d", m_Width, m_Height, m_PixelAspect.num, m_PixelAspect.den); return true; } bool cParserH264::IsFirstVclNal(h264_private::VCL_NAL &vcl) { if (m_streamData.vcl_nal.frame_num != vcl.frame_num) return true; if (m_streamData.vcl_nal.pic_parameter_set_id != vcl.pic_parameter_set_id) return true; if (m_streamData.vcl_nal.field_pic_flag != vcl.field_pic_flag) return true; if (m_streamData.vcl_nal.field_pic_flag && vcl.field_pic_flag) { if (m_streamData.vcl_nal.bottom_field_flag != vcl.bottom_field_flag) return true; } if (m_streamData.vcl_nal.nal_ref_idc == 0 || vcl.nal_ref_idc == 0) { if (m_streamData.vcl_nal.nal_ref_idc != vcl.nal_ref_idc) return true; } if (m_streamData.vcl_nal.pic_order_cnt_type == 0 && vcl.pic_order_cnt_type == 0) { if (m_streamData.vcl_nal.pic_order_cnt_lsb != vcl.pic_order_cnt_lsb) return true; if (m_streamData.vcl_nal.delta_pic_order_cnt_bottom != vcl.delta_pic_order_cnt_bottom) return true; } if (m_streamData.vcl_nal.pic_order_cnt_type == 1 && vcl.pic_order_cnt_type == 1) { if (m_streamData.vcl_nal.delta_pic_order_cnt_0 != vcl.delta_pic_order_cnt_0) return true; if (m_streamData.vcl_nal.delta_pic_order_cnt_1 != vcl.delta_pic_order_cnt_1) return true; } if (m_streamData.vcl_nal.nal_unit_type == 5 || vcl.nal_unit_type == 5) { if (m_streamData.vcl_nal.nal_unit_type != vcl.nal_unit_type) return true; } if (m_streamData.vcl_nal.nal_unit_type == 5 && vcl.nal_unit_type == 5) { if (m_streamData.vcl_nal.idr_pic_id != vcl.idr_pic_id) return true; } return false; } vdr-plugin-vnsiserver-1.5.2/parser_h264.h000066400000000000000000000064231303643374600202400ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_DEMUXER_H264_H #define VNSI_DEMUXER_H264_H #include "parser.h" class cBitstream; // --- cParserH264 ------------------------------------------------- class cParserH264 : public cParser { private: typedef struct h264_private { struct SPS { int frame_duration; int cbpsize; int pic_order_cnt_type; int frame_mbs_only_flag; int log2_max_frame_num; int log2_max_pic_order_cnt_lsb; int delta_pic_order_always_zero_flag; } sps[256]; struct PPS { int sps; int pic_order_present_flag; } pps[256]; struct VCL_NAL { int frame_num; // slice int pic_parameter_set_id; // slice int field_pic_flag; // slice int bottom_field_flag; // slice int delta_pic_order_cnt_bottom; // slice int delta_pic_order_cnt_0; // slice int delta_pic_order_cnt_1; // slice int pic_order_cnt_lsb; // slice int idr_pic_id; // slice int nal_unit_type; int nal_ref_idc; // start code int pic_order_cnt_type; // sps } vcl_nal; } h264_private_t; typedef struct mpeg_rational_s { int num; int den; } mpeg_rational_t; enum { NAL_SLH = 0x01, // Slice Header NAL_SEI = 0x06, // Supplemental Enhancement Information NAL_SPS = 0x07, // Sequence Parameter Set NAL_PPS = 0x08, // Picture Parameter Set NAL_AUD = 0x09, // Access Unit Delimiter NAL_END_SEQ = 0x0A // End of Sequence }; uint32_t m_StartCode; bool m_NeedIFrame; bool m_NeedSPS; bool m_NeedPPS; int m_Width; int m_Height; int m_FPS; int m_FpsScale; mpeg_rational_t m_PixelAspect; int m_FrameDuration; h264_private m_streamData; int m_vbvDelay; /* -1 if CBR */ int m_vbvSize; /* Video buffer size (in bytes) */ int64_t m_DTS; int64_t m_PTS; int Parse_H264(uint32_t startcode, int buf_ptr, bool &complete); bool Parse_PPS(uint8_t *buf, int len); bool Parse_SLH(uint8_t *buf, int len, h264_private::VCL_NAL &vcl); bool Parse_SPS(uint8_t *buf, int len); bool IsFirstVclNal(h264_private::VCL_NAL &vcl); public: cParserH264(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserH264(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); virtual void Reset(); }; #endif // VNSI_DEMUXER_H264_H vdr-plugin-vnsiserver-1.5.2/parser_hevc.c000066400000000000000000000173671303643374600205060ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ // Warning: This is an unfinished port from H.264 to HEVC in alpha state // Tested with German DVB-T2 HD channels #include "parser_hevc.h" #include "bitstream.h" #include "config.h" #include #include cParserHEVC::cParserHEVC(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps) : cParser(pID, stream, ptsWrap, observePtsWraps) { m_Height = 0; m_Width = 0; m_FpsScale = 0; m_PixelAspect.den = 1; m_PixelAspect.num = 0; memset(&m_streamData, 0, sizeof(m_streamData)); m_PesBufferInitialSize = 240000; m_IsVideo = true; Reset(); } cParserHEVC::~cParserHEVC() { } void cParserHEVC::Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data) { if (m_PesBufferPtr < 10) // 2*startcode + header + trail bits return; int p = m_PesParserPtr; uint32_t startcode = m_StartCode; bool frameComplete = false; while (m_PesBufferPtr - p) { startcode = startcode << 8 | m_PesBuffer[p++]; if ((startcode & 0x00ffffff) == 0x00000001) { if (m_LastStartPos != -1) Parse_HEVC(m_LastStartPos, p-m_LastStartPos, &frameComplete); m_LastStartPos = p; if (frameComplete) break; } } m_PesParserPtr = p; m_StartCode = startcode; if (frameComplete) { if (!m_NeedSPS && m_FrameValid) { double PAR = (double)m_PixelAspect.num/(double)m_PixelAspect.den; double DAR = (PAR * m_Width) / m_Height; DEBUGLOG("HEVC SPS: PAR %i:%i", m_PixelAspect.num, m_PixelAspect.den); DEBUGLOG("HEVC SPS: DAR %.2f", DAR); int duration; if (m_curDTS != DVD_NOPTS_VALUE && m_prevDTS != DVD_NOPTS_VALUE && m_curDTS > m_prevDTS) duration = m_curDTS - m_prevDTS; else duration = m_Stream->Rescale(20000, 90000, DVD_TIME_BASE); if (m_FpsScale == 0) m_FpsScale = m_Stream->Rescale(duration, DVD_TIME_BASE, 90000); bool streamChange = m_Stream->SetVideoInformation(m_FpsScale, DVD_TIME_BASE, m_Height, m_Width, DAR); pkt->id = m_pID; pkt->size = m_PesNextFramePtr; pkt->data = m_PesBuffer; pkt->dts = m_DTS; pkt->pts = m_PTS; pkt->duration = duration; pkt->streamChange = streamChange; } m_StartCode = 0xffffffff; m_LastStartPos = -1; m_PesParserPtr = 0; m_FoundFrame = false; m_FrameValid = true; } } void cParserHEVC::Reset() { cParser::Reset(); m_StartCode = 0xffffffff; m_LastStartPos = -1; m_NeedSPS = true; m_NeedPPS = true; memset(&m_streamData, 0, sizeof(m_streamData)); } void cParserHEVC::Parse_HEVC(int buf_ptr, unsigned int NumBytesInNalUnit, bool *complete) { uint8_t *buf = m_PesBuffer + buf_ptr; uint16_t header; HDR_NAL hdr; // nal_unit_header header = (buf[0] << 8) | buf[1]; if (header & 0x8000) // ignore forbidden_bit == 1 return; hdr.nal_unit_type = (header & 0x7e00) >> 9; hdr.nuh_layer_id = (header & 0x1f8) >> 3; hdr.nuh_temporal_id = (header & 0x7) - 1; switch (hdr.nal_unit_type) { case NAL_TRAIL_N ... NAL_RASL_R: case NAL_BLA_W_LP ... NAL_CRA_NUT: { if (m_NeedSPS || m_NeedPPS) { m_FoundFrame = true; return; } hevc_private::VCL_NAL vcl; memset(&vcl, 0, sizeof(hevc_private::VCL_NAL)); Parse_SLH(buf, NumBytesInNalUnit, hdr, vcl); // check for the beginning of a new access unit if (m_FoundFrame && IsFirstVclNal(vcl)) { *complete = true; m_PesNextFramePtr = buf_ptr - 3; return; } if (!m_FoundFrame) { if (buf_ptr - 3 >= m_PesTimePos) { m_DTS = m_curDTS; m_PTS = m_curPTS; } else { m_DTS = m_prevDTS; m_PTS = m_prevPTS; } } m_streamData.vcl_nal = vcl; m_FoundFrame = true; break; } case NAL_PFX_SEI_NUT: if (m_FoundFrame) { *complete = true; m_PesNextFramePtr = buf_ptr - 3; } break; case NAL_VPS_NUT: break; case NAL_SPS_NUT: { if (m_FoundFrame) { *complete = true; m_PesNextFramePtr = buf_ptr - 3; return; } Parse_SPS(buf, NumBytesInNalUnit, hdr); m_NeedSPS = false; break; } case NAL_PPS_NUT: { if (m_FoundFrame) { *complete = true; m_PesNextFramePtr = buf_ptr - 3; return; } Parse_PPS(buf, NumBytesInNalUnit); m_NeedPPS = false; break; } case NAL_AUD_NUT: if (m_FoundFrame && (m_prevPTS != DVD_NOPTS_VALUE)) { *complete = true; m_PesNextFramePtr = buf_ptr - 3; } break; case NAL_EOS_NUT: if (m_FoundFrame) { *complete = true; m_PesNextFramePtr = buf_ptr + 2; } break; case NAL_FD_NUT: case NAL_SFX_SEI_NUT: break; default: INFOLOG("HEVC fixme: nal unknown %i", hdr.nal_unit_type); break; } } void cParserHEVC::Parse_PPS(uint8_t *buf, int len) { cBitstream bs(buf, len*8, true); int pps_id = bs.readGolombUE(); int sps_id = bs.readGolombUE(); m_streamData.pps[pps_id].sps = sps_id; m_streamData.pps[pps_id].dependent_slice_segments_enabled_flag = bs.readBits(1); } void cParserHEVC::Parse_SLH(uint8_t *buf, int len, HDR_NAL hdr, hevc_private::VCL_NAL &vcl) { cBitstream bs(buf, len*8, true); vcl.nal_unit_type = hdr.nal_unit_type; vcl.first_slice_segment_in_pic_flag = bs.readBits(1); if ((hdr.nal_unit_type >= NAL_BLA_W_LP) && (hdr.nal_unit_type <= NAL_RSV_IRAP_VCL23)) bs.skipBits(1); // no_output_of_prior_pics_flag vcl.pic_parameter_set_id = bs.readGolombUE(); } // 7.3.2.2.1 General sequence parameter set RBSP syntax void cParserHEVC::Parse_SPS(uint8_t *buf, int len, HDR_NAL hdr) { cBitstream bs(buf, len*8, true); unsigned int i; int sub_layer_profile_present_flag[8], sub_layer_level_present_flag[8]; bs.skipBits(4); // sps_video_parameter_set_id unsigned int sps_max_sub_layers_minus1 = bs.readBits(3); bs.skipBits(1); // sps_temporal_id_nesting_flag // skip over profile_tier_level bs.skipBits(8 + 32 + 4 + 43 + 1 +8); for (i=0; i 0) { for (i=sps_max_sub_layers_minus1; i<8; i++) bs.skipBits(2); } for (i=0; i. * */ #ifndef VNSI_DEMUXER_HEVC_H #define VNSI_DEMUXER_HEVC_H #include "parser.h" class cBitstream; // --- cParserHEVC ------------------------------------------------- class cParserHEVC : public cParser { private: typedef struct hevc_private { struct PPS { int sps; int dependent_slice_segments_enabled_flag; } pps[64]; struct VCL_NAL { int pic_parameter_set_id; // slice unsigned int first_slice_segment_in_pic_flag; unsigned int nal_unit_type; } vcl_nal; } hevc_private_t; typedef struct HDR_NAL_t { uint nal_unit_type; uint nuh_layer_id; uint nuh_temporal_id; } HDR_NAL; typedef struct mpeg_rational_s { int num; int den; } mpeg_rational_t; enum { NAL_TRAIL_N = 0x00, // Coded slice segment of trailing picture NAL_TRAIL_R = 0x01, // Coded slice segment of trailing picture NAL_TSA_N = 0x02, // Coded slice segment of TSA picture NAL_TSA_R = 0x03, // Coded slice segment of TSA picture NAL_STSA_N = 0x04, // Coded slice segment of STSA picture NAL_STSA_R = 0x05, // Coded slice segment of STSA picture NAL_RADL_N = 0x06, // Coded slice segment of RADL picture NAL_RADL_R = 0x07, // Coded slice segment of RADL picture NAL_RASL_N = 0x08, // Coded slice segment of RASL picture NAL_RASL_R = 0x09, // Coded slice segment of RASL picture NAL_BLA_W_LP = 0x10, // Coded slice segment of a BLA picture NAL_CRA_NUT = 0x15, // Coded slice segment of a CRA picture NAL_RSV_IRAP_VCL22 = 0x16, // Reserved IRAP VCL NAL unit types NAL_RSV_IRAP_VCL23 = 0x17, // Reserved IRAP VCL NAL unit types NAL_VPS_NUT = 0x20, // Video Parameter SET NAL_SPS_NUT = 0x21, // Sequence Parameter Set NAL_PPS_NUT = 0x22, // Picture Parameter Set NAL_AUD_NUT = 0x23, // Access Unit Delimiter NAL_EOS_NUT = 0x24, // End of Sequence NAL_EOB_NUT = 0x25, // End of Bitstream NAL_FD_NUT = 0x26, // Filler Data NAL_PFX_SEI_NUT = 0x27, // Supplemental Enhancement Information NAL_SFX_SEI_NUT = 0x28, // Supplemental Enhancement Information }; uint32_t m_StartCode; int m_LastStartPos; bool m_NeedSPS; bool m_NeedPPS; int m_Width; int m_Height; int m_FpsScale; mpeg_rational_t m_PixelAspect; hevc_private m_streamData; int64_t m_DTS; int64_t m_PTS; void Parse_HEVC(int buf_ptr, unsigned int NumBytesInNalUnit, bool *complete); void Parse_PPS(uint8_t *buf, int len); void Parse_SLH(uint8_t *buf, int len, HDR_NAL hdr, hevc_private::VCL_NAL &vcl); void Parse_SPS(uint8_t *buf, int len, HDR_NAL hdr); bool IsFirstVclNal(hevc_private::VCL_NAL &vcl); public: cParserHEVC(int pID, cTSStream *stream, sPtsWrap *ptsWrap, bool observePtsWraps); virtual ~cParserHEVC(); virtual void Parse(sStreamPacket *pkt, sStreamPacket *pkt_side_data); virtual void Reset(); }; #endif // VNSI_DEMUXER_HEVC_H vdr-plugin-vnsiserver-1.5.2/po/000077500000000000000000000000001303643374600164415ustar00rootroot00000000000000vdr-plugin-vnsiserver-1.5.2/po/de_DE.po000066400000000000000000000034221303643374600177420ustar00rootroot00000000000000# VDR VNSI plugin language source file. # Copyright (C) 2015 Alwin Esch # This file is distributed under the same license as the VDR package. # msgid "" msgstr "" "Project-Id-Version: VNSI-Server 1.0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-26 21:50+0300\n" "PO-Revision-Date: 2015-01-23 21:46+0100\n" "Last-Translator: Alwin Esch\n" "Language-Team: German\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Your scanner version doesnt support services - Please upgrade." msgstr "" msgid "Your scanner version is to old - Please upgrade." msgstr "" msgid "Off" msgstr "Aus" msgid "RAM" msgstr "RAM" msgid "File" msgstr "Datei" msgid "Time Shift Mode" msgstr "Time Shift Modus" msgid "TS Buffersize (RAM) (1-80) x 100MB" msgstr "TS Puffergröße (RAM) (1-80) x 100MB" msgid "TS Buffersize (File) (1-10) x 1GB" msgstr "TS Puffergröße (Datei) (1-10) x 1GB" msgid "TS Buffer Directory" msgstr "TS-Puffer-Verzeichnis" msgid "Play Recording instead of live" msgstr "Wiedergeben als Aufzeichnung statt Live" msgid "Avoid EPG scan while streaming" msgstr "Keine EPG suche während der Wiedergabe durchführen" msgid "Disable scramble timeout" msgstr "" msgid "Disable cam blacklist" msgstr "" msgid "Recording with the same name exists" msgstr "Aufnahme mit der selben größe existiert" msgid "Error while read last filenumber" msgstr "Fehler beim lesen der letzen Dateinummer" msgid "Error while accessing vdrfile" msgstr "Fehler beim zugriff auf VDR-Datei" msgid "Error while accessing indexfile" msgstr "Fehler beim Zugriff der Indexdatei" msgid "Deleted recording vanished" msgstr "Gelöschte Aufnahme verschwunden" #~ msgid "PMT Timeout (0-10)" #~ msgstr "PMT Auszeit (0-10)" vdr-plugin-vnsiserver-1.5.2/po/lt_LT.po000066400000000000000000000034701303643374600200230ustar00rootroot00000000000000# VDR VNSI plugin language source file. # Copyright (C) 2015 Alwin Esch # This file is distributed under the same license as the VDR package. # msgid "" msgstr "" "Project-Id-Version: VNSI-Server 1.0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-26 21:50+0300\n" "PO-Revision-Date: 2015-02-11 22:30+0200\n" "Last-Translator: Valdemaras Pipiras\n" "Language-Team: Lithuanian\n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Your scanner version doesnt support services - Please upgrade." msgstr "" msgid "Your scanner version is to old - Please upgrade." msgstr "" msgid "Off" msgstr "Išjungta" msgid "RAM" msgstr "RAM" msgid "File" msgstr "Failas" msgid "Time Shift Mode" msgstr "Atidėto žiūrėjimo (TS) būsena" msgid "TS Buffersize (RAM) (1-80) x 100MB" msgstr "TS buferio dydis (RAM) (1-80) x 100MB" msgid "TS Buffersize (File) (1-10) x 1GB" msgstr "TS buferio dydis (Failas) (1-10) x 1GB" msgid "TS Buffer Directory" msgstr "TS katalogas buferiavimui" msgid "Play Recording instead of live" msgstr "Groti įrašą vietoj gyvos transliacijos" msgid "Avoid EPG scan while streaming" msgstr "Vengti EPG skanavimo kol vyksta transliacija" msgid "Disable scramble timeout" msgstr "" msgid "Disable cam blacklist" msgstr "" msgid "Recording with the same name exists" msgstr "Jau yra įrašų tokiu pat pavadinimu" msgid "Error while read last filenumber" msgstr "Klaida nuskaitant paskutinių failų skaičių" msgid "Error while accessing vdrfile" msgstr "Klaida bandant atidaryti VDR failą" msgid "Error while accessing indexfile" msgstr "Klaida bandant atidaryti index failą" msgid "Deleted recording vanished" msgstr "Ištrintas įrašas galutinai išvalytas" #~ msgid "PMT Timeout (0-10)" #~ msgstr "PMT (0-10)" vdr-plugin-vnsiserver-1.5.2/recordingscache.c000066400000000000000000000064321303643374600213170ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "recordingscache.h" #include "config.h" #include "vnsiclient.h" #include "hash.h" cRecordingsCache::cRecordingsCache() { } cRecordingsCache::~cRecordingsCache() { } cRecordingsCache& cRecordingsCache::GetInstance() { static cRecordingsCache singleton; return singleton; } uint32_t cRecordingsCache::Register(const cRecording* recording, bool deleted) { cString filename = recording->FileName(); uint32_t uid = CreateStringHash(filename); m_mutex.Lock(); if(m_recordings.find(uid) == m_recordings.end()) { DEBUGLOG("%s - uid: %08x '%s'", __FUNCTION__, uid, (const char*)filename); m_recordings[uid].filename = filename; m_recordings[uid].isDeleted = deleted; } m_mutex.Unlock(); return uid; } const cRecording* cRecordingsCache::Lookup(uint32_t uid) { DEBUGLOG("%s - lookup uid: %08x", __FUNCTION__, uid); if(m_recordings.find(uid) == m_recordings.end()) { DEBUGLOG("%s - not found !", __FUNCTION__); return NULL; } m_mutex.Lock(); cString filename = m_recordings[uid].filename; DEBUGLOG("%s - filename: %s", __FUNCTION__, (const char*)filename); const cRecording* r; if (!m_recordings[uid].isDeleted) { #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; r = Recordings->GetByName(filename); #else r = Recordings.GetByName(filename); #endif } else { #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_READ; r = DeletedRecordings->GetByName(filename); #else r = DeletedRecordings.GetByName(filename); #endif } DEBUGLOG("%s - recording %s", __FUNCTION__, (r == NULL) ? "not found !" : "found"); m_mutex.Unlock(); return r; } cRecording* cRecordingsCache::LookupWrite(uint32_t uid) { DEBUGLOG("%s - lookup uid: %08x", __FUNCTION__, uid); if(m_recordings.find(uid) == m_recordings.end()) { DEBUGLOG("%s - not found !", __FUNCTION__); return NULL; } m_mutex.Lock(); cString filename = m_recordings[uid].filename; DEBUGLOG("%s - filename: %s", __FUNCTION__, (const char*)filename); cRecording* r; if (!m_recordings[uid].isDeleted) { #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; r = Recordings->GetByName(filename); #else r = Recordings.GetByName(filename); #endif } else { #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_WRITE; r = DeletedRecordings->GetByName(filename); #else r = DeletedRecordings.GetByName(filename); #endif } DEBUGLOG("%s - recording %s", __FUNCTION__, (r == NULL) ? "not found !" : "found"); m_mutex.Unlock(); return r; } vdr-plugin-vnsiserver-1.5.2/recordingscache.h000066400000000000000000000027361303643374600213270ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_RECORDINGSCACHE_H #define VNSI_RECORDINGSCACHE_H #include #include #include #include #include class cRecordingsCache { protected: cRecordingsCache(); virtual ~cRecordingsCache(); public: static cRecordingsCache& GetInstance(); uint32_t Register(const cRecording* recording, bool deleted = false); const cRecording* Lookup(uint32_t uid); cRecording* LookupWrite(uint32_t uid); private: struct RecordingsInfo { cString filename; bool isDeleted; }; std::map m_recordings; cMutex m_mutex; }; #endif // VNSI_RECORDINGSCACHE_H vdr-plugin-vnsiserver-1.5.2/recplayer.c000066400000000000000000000166171303643374600201700ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2004-2005 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ /* * This code is taken from VOMP for VDR plugin. */ #include "recplayer.h" #include #include #include #include #ifndef O_NOATIME #define O_NOATIME 0 #endif cRecPlayer::cRecPlayer(const cRecording* rec, bool inProgress) :m_inProgress(inProgress), m_recordingFilename(rec->FileName()), m_pesrecording(rec->IsPesRecording()), m_indexFile(m_recordingFilename.c_str(), false, m_pesrecording), m_file(-1), m_fileOpen(-1) { // FIXME find out max file path / name lengths if(m_pesrecording) INFOLOG("recording '%s' is a PES recording", m_recordingFilename.c_str()); scan(); } void cRecPlayer::cleanup() { m_segments.clear(); } void cRecPlayer::scan() { struct stat s; closeFile(); m_totalLength = 0; m_fileOpen = -1; m_totalFrames = 0; cleanup(); for(int i = 0; ; i++) // i think we only need one possible loop { fileNameFromIndex(i); if(stat(m_fileName, &s) == -1) { break; } cSegment segment; segment.start = m_totalLength; segment.end = segment.start + s.st_size; m_segments.push_back(segment); m_totalLength += s.st_size; INFOLOG("File %i found, size: %lu, totalLength now %lu", i, s.st_size, m_totalLength); } m_totalFrames = m_indexFile.Last(); INFOLOG("total frames: %u", m_totalFrames); } void cRecPlayer::reScan() { struct stat s; m_totalLength = 0; for(size_t i = 0; ; i++) // i think we only need one possible loop { fileNameFromIndex(i); if(stat(m_fileName, &s) == -1) { break; } cSegment* segment; if (m_segments.size() < i+1) { m_segments.push_back(cSegment()); segment = &m_segments.back(); segment->start = m_totalLength; } else segment = &m_segments[i]; segment->end = segment->start + s.st_size; m_totalLength += s.st_size; } m_totalFrames = m_indexFile.Last(); } cRecPlayer::~cRecPlayer() { cleanup(); closeFile(); } char* cRecPlayer::fileNameFromIndex(int index) { if (m_pesrecording) snprintf(m_fileName, sizeof(m_fileName), "%s/%03i.vdr", m_recordingFilename.c_str(), index+1); else snprintf(m_fileName, sizeof(m_fileName), "%s/%05i.ts", m_recordingFilename.c_str(), index+1); return m_fileName; } bool cRecPlayer::openFile(int index) { if (index == m_fileOpen) return true; closeFile(); fileNameFromIndex(index); INFOLOG("openFile called for index %i string:%s", index, m_fileName); m_file = open(m_fileName, O_RDONLY); if (m_file == -1) { INFOLOG("file failed to open"); m_fileOpen = -1; return false; } m_fileOpen = index; return true; } void cRecPlayer::closeFile() { if(m_file == -1) { return; } INFOLOG("file closed"); close(m_file); m_file = -1; m_fileOpen = -1; } uint64_t cRecPlayer::getLengthBytes() { return m_totalLength; } uint32_t cRecPlayer::getLengthFrames() { return m_totalFrames; } int cRecPlayer::getBlock(unsigned char* buffer, uint64_t position, int amount) { // dont let the block be larger than 256 kb if (amount > 512*1024) amount = 512*1024; if ((uint64_t)amount > m_totalLength) amount = m_totalLength; if (position >= m_totalLength) { reScan(); if (position >= m_totalLength) { return 0; } } if ((position + amount) > m_totalLength) amount = m_totalLength - position; // work out what block "position" is in std::vector::iterator begin = m_segments.begin(), end = m_segments.end(), segmentIterator = end; for (std::vector::iterator i = begin; i != end; ++i) { if ((position >= i->start) && (position < i->end)) { segmentIterator = i; break; } } // segment not found / invalid position if (segmentIterator == end) return 0; // open file (if not already open) if (!openFile(std::distance(begin, segmentIterator))) return 0; // work out position in current file uint64_t filePosition = position - segmentIterator->start; // seek to position if(lseek(m_file, filePosition, SEEK_SET) == -1) { ERRORLOG("unable to seek to position: %lu", filePosition); return 0; } // try to read the block int bytes_read = read(m_file, buffer, amount); // we may got stuck at end of segment if ((bytes_read == 0) && (position < m_totalLength)) bytes_read += getBlock(buffer, position+1 , amount); if(bytes_read <= 0) { return 0; } if (!m_inProgress) { #ifndef __FreeBSD__ // Tell linux not to bother keeping the data in the FS cache posix_fadvise(m_file, filePosition, bytes_read, POSIX_FADV_DONTNEED); #endif } return bytes_read; } uint64_t cRecPlayer::positionFromFrameNumber(uint32_t frameNumber) { uint16_t retFileNumber; off_t retFileOffset; bool retPicType; int retLength; if (!m_indexFile.Get((int)frameNumber, &retFileNumber, &retFileOffset, &retPicType, &retLength)) return 0; if (retFileNumber >= m_segments.size()) return 0; uint64_t position = m_segments[retFileNumber].start + retFileOffset; return position; } uint32_t cRecPlayer::frameNumberFromPosition(uint64_t position) { if (position >= m_totalLength) { DEBUGLOG("Client asked for data starting past end of recording!"); return m_totalFrames; } std::vector::iterator begin = m_segments.begin(), end = m_segments.end(), segmentIterator = end; for (std::vector::iterator i = begin; i != end; ++i) { if ((position >= i->start) && (position < i->end)) { segmentIterator = i; break; } } if (segmentIterator == end) return m_totalFrames; uint32_t askposition = position - segmentIterator->start; int segmentNumber = std::distance(begin, segmentIterator); return m_indexFile.Get((int)segmentNumber, askposition); } bool cRecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) { // 0 = backwards // 1 = forwards uint16_t waste1; off_t waste2; int iframeLength; int indexReturnFrameNumber; indexReturnFrameNumber = (uint32_t)m_indexFile.GetNextIFrame(frameNumber, (direction==1 ? true : false), &waste1, &waste2, &iframeLength); DEBUGLOG("GNIF input framenumber:%u, direction=%u, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); if (indexReturnFrameNumber == -1) return false; *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber); *rframeNumber = (uint32_t)indexReturnFrameNumber; *rframeLength = (uint32_t)iframeLength; return true; } vdr-plugin-vnsiserver-1.5.2/recplayer.h000066400000000000000000000042051303643374600201630ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2004-2005 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ /* * This code is taken from VOMP for VDR plugin. */ #ifndef VNSI_RECPLAYER_H #define VNSI_RECPLAYER_H #include #include #include "config.h" #include #include class cSegment { public: uint64_t start; uint64_t end; }; class cRecPlayer { public: cRecPlayer(const cRecording* rec, bool inProgress = false); ~cRecPlayer(); uint64_t getLengthBytes(); uint32_t getLengthFrames(); int getBlock(unsigned char* buffer, uint64_t position, int amount); bool openFile(int index); void closeFile(); void scan(); void reScan(); uint64_t positionFromFrameNumber(uint32_t frameNumber); uint32_t frameNumberFromPosition(uint64_t position); bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); private: void cleanup(); char* fileNameFromIndex(int index); void checkBufferSize(int s); const bool m_inProgress; const std::string m_recordingFilename; const bool m_pesrecording; cIndexFile m_indexFile; int m_file; int m_fileOpen; char m_fileName[512]; std::vector m_segments; uint64_t m_totalLength; uint32_t m_totalFrames; }; #endif // VNSI_RECPLAYER_H vdr-plugin-vnsiserver-1.5.2/requestpacket.c000066400000000000000000000070171303643374600210540ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2007 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "requestpacket.h" #include "vnsicommand.h" #include "config.h" #include #include #ifndef __FreeBSD__ #include #else #include #define __be64_to_cpu be64toh #define __cpu_to_be64 htobe64 #endif cRequestPacket::cRequestPacket(uint32_t requestID, uint32_t opcode, uint8_t* data, size_t dataLength) : userData(data), userDataLength(dataLength), opCode(opcode), requestID(requestID) { packetPos = 0; channelID = 0; streamID = 0; flag = 0; } cRequestPacket::~cRequestPacket() { delete[] userData; } bool cRequestPacket::end() const { return (packetPos >= userDataLength); } char* cRequestPacket::extract_String() { char *p = (char *)&userData[packetPos]; const char *end = (const char *)memchr(p, '\0', userDataLength - packetPos); if (end == NULL) /* string is not terminated - fail */ throw MalformedVNSIPacket(); int length = end - p; packetPos += length + 1; return p; } uint8_t cRequestPacket::extract_U8() { if ((packetPos + sizeof(uint8_t)) > userDataLength) throw MalformedVNSIPacket(); uint8_t uc = userData[packetPos]; packetPos += sizeof(uint8_t); return uc; } uint32_t cRequestPacket::extract_U32() { if ((packetPos + sizeof(uint32_t)) > userDataLength) throw MalformedVNSIPacket(); uint32_t ul; memcpy(&ul, &userData[packetPos], sizeof(uint32_t)); ul = ntohl(ul); packetPos += sizeof(uint32_t); return ul; } uint64_t cRequestPacket::extract_U64() { if ((packetPos + sizeof(uint64_t)) > userDataLength) throw MalformedVNSIPacket(); uint64_t ull; memcpy(&ull, &userData[packetPos], sizeof(uint64_t)); ull = __be64_to_cpu(ull); packetPos += sizeof(uint64_t); return ull; } int64_t cRequestPacket::extract_S64() { if ((packetPos + sizeof(int64_t)) > userDataLength) throw MalformedVNSIPacket(); int64_t ll; memcpy(&ll, &userData[packetPos], sizeof(int64_t)); ll = __be64_to_cpu(ll); packetPos += sizeof(int64_t); return ll; } double cRequestPacket::extract_Double() { if ((packetPos + sizeof(uint64_t)) > userDataLength) throw MalformedVNSIPacket(); uint64_t ull; memcpy(&ull, &userData[packetPos], sizeof(uint64_t)); ull = __be64_to_cpu(ull); double d; memcpy(&d, &ull, sizeof(double)); packetPos += sizeof(uint64_t); return d; } int32_t cRequestPacket::extract_S32() { if ((packetPos + sizeof(int32_t)) > userDataLength) throw MalformedVNSIPacket(); int32_t l; memcpy(&l, &userData[packetPos], sizeof(int32_t)); l = ntohl(l); packetPos += sizeof(int32_t); return l; } uint8_t* cRequestPacket::getData() { return userData; } vdr-plugin-vnsiserver-1.5.2/requestpacket.h000066400000000000000000000042261303643374600210600ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2007 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_REQUESTPACKET_H #define VNSI_REQUESTPACKET_H #include #include #include class MalformedVNSIPacket : public std::runtime_error { public: MalformedVNSIPacket() :std::runtime_error("Malformed VNSI packet") {} }; class cRequestPacket { public: cRequestPacket(uint32_t requestID, uint32_t opcode, uint8_t* data, size_t dataLength); ~cRequestPacket(); size_t getDataLength() const { return userDataLength; } uint32_t getChannelID() const { return channelID; } uint32_t getRequestID() const { return requestID; } uint32_t getStreamID() const { return streamID; } uint32_t getFlag() const { return flag; } uint32_t getOpCode() const { return opCode; } char* extract_String(); uint8_t extract_U8(); uint32_t extract_U32(); uint64_t extract_U64(); int64_t extract_S64(); int32_t extract_S32(); double extract_Double(); bool end() const; // If you call this, the memory becomes yours. Free with free() uint8_t* getData(); private: uint8_t* userData; size_t userDataLength; size_t packetPos; uint32_t opCode; uint32_t channelID; uint32_t requestID; uint32_t streamID; uint32_t flag; // stream only }; #endif // VNSI_REQUESTPACKET_H vdr-plugin-vnsiserver-1.5.2/responsepacket.c000066400000000000000000000164731303643374600212300ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2007 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ /* * This code is taken from VOMP for VDR plugin. */ #include "responsepacket.h" #include "vnsicommand.h" #include "config.h" #include #include #include #ifndef __FreeBSD__ #include #else #include #define __be64_to_cpu be64toh #define __cpu_to_be64 htobe64 #endif /* Packet format for an RR channel response: 4 bytes = channel ID = 1 (request/response channel) 4 bytes = request ID (from serialNumber) 4 bytes = length of the rest of the packet ? bytes = rest of packet. depends on packet */ cResponsePacket::cResponsePacket() { buffer = NULL; bufSize = 0; bufUsed = 0; } cResponsePacket::~cResponsePacket() { if (buffer) free(buffer); } void cResponsePacket::initBuffers() { if (buffer == NULL) { bufSize = 512; buffer = (uint8_t*)malloc(bufSize); } } void cResponsePacket::init(uint32_t requestID) { initBuffers(); uint32_t ul; ul = htonl(VNSI_CHANNEL_REQUEST_RESPONSE); // RR channel memcpy(&buffer[0], &ul, sizeof(uint32_t)); ul = htonl(requestID); memcpy(&buffer[4], &ul, sizeof(uint32_t)); ul = 0; memcpy(&buffer[userDataLenPos], &ul, sizeof(uint32_t)); bufUsed = headerLength; } void cResponsePacket::initScan(uint32_t opCode) { initBuffers(); uint32_t ul; ul = htonl(VNSI_CHANNEL_SCAN); // RR channel memcpy(&buffer[0], &ul, sizeof(uint32_t)); ul = htonl(opCode); memcpy(&buffer[4], &ul, sizeof(uint32_t)); ul = 0; memcpy(&buffer[userDataLenPos], &ul, sizeof(uint32_t)); bufUsed = headerLength; } void cResponsePacket::initStatus(uint32_t opCode) { initBuffers(); uint32_t ul; ul = htonl(VNSI_CHANNEL_STATUS); // RR channel memcpy(&buffer[0], &ul, sizeof(uint32_t)); ul = htonl(opCode); memcpy(&buffer[4], &ul, sizeof(uint32_t)); ul = 0; memcpy(&buffer[userDataLenPos], &ul, sizeof(uint32_t)); bufUsed = headerLength; } void cResponsePacket::initStream(uint32_t opCode, uint32_t streamID, uint32_t duration, int64_t pts, int64_t dts, uint32_t serial) { initBuffers(); uint32_t ul; uint64_t ull; ul = htonl(VNSI_CHANNEL_STREAM); // stream channel memcpy(&buffer[0], &ul, sizeof(uint32_t)); ul = htonl(opCode); // Stream packet operation code memcpy(&buffer[4], &ul, sizeof(uint32_t)); ul = htonl(streamID); // Stream ID memcpy(&buffer[8], &ul, sizeof(uint32_t)); ul = htonl(duration); // Duration memcpy(&buffer[12], &ul, sizeof(uint32_t)); ull = __cpu_to_be64(pts); // PTS memcpy(&buffer[16], &ull, sizeof(uint64_t)); ull = __cpu_to_be64(dts); // DTS memcpy(&buffer[24], &ull, sizeof(uint64_t)); ul = htonl(serial); memcpy(&buffer[32], &ul, sizeof(uint32_t)); ul = 0; memcpy(&buffer[userDataLenPosStream], &ul, sizeof(uint32_t)); bufUsed = headerLengthStream; } void cResponsePacket::initOsd(uint32_t opCode, int32_t wnd, int32_t color, int32_t x0, int32_t y0, int32_t x1, int32_t y1) { initBuffers(); uint32_t ul; int32_t l; ul = htonl(VNSI_CHANNEL_OSD); // stream OSD memcpy(&buffer[0], &ul, sizeof(uint32_t)); ul = htonl(opCode); // OSD operation code memcpy(&buffer[4], &ul, sizeof(uint32_t)); l = htonl(wnd); // Window memcpy(&buffer[8], &l, sizeof(int32_t)); l = htonl(color); // Color memcpy(&buffer[12], &l, sizeof(int32_t)); l = htonl(x0); // x0 memcpy(&buffer[16], &l, sizeof(int32_t)); l = htonl(y0); // y0 memcpy(&buffer[20], &l, sizeof(int32_t)); l = htonl(x1); // x1 memcpy(&buffer[24], &l, sizeof(int32_t)); l = htonl(y1); // y1 memcpy(&buffer[28], &l, sizeof(int32_t)); ul = 0; memcpy(&buffer[userDataLenPosOSD], &ul, sizeof(uint32_t)); bufUsed = headerLengthOSD; } void cResponsePacket::finalise() { uint32_t ul = htonl(bufUsed - headerLength); memcpy(&buffer[userDataLenPos], &ul, sizeof(uint32_t)); } void cResponsePacket::finaliseStream() { uint32_t ul = htonl(bufUsed - headerLengthStream); memcpy(&buffer[userDataLenPosStream], &ul, sizeof(uint32_t)); } void cResponsePacket::finaliseOSD() { uint32_t ul = htonl(bufUsed - headerLengthOSD); memcpy(&buffer[userDataLenPosOSD], &ul, sizeof(uint32_t)); } bool cResponsePacket::copyin(const uint8_t* src, uint32_t len) { if (!checkExtend(len)) return false; memcpy(buffer + bufUsed, src, len); bufUsed += len; return true; } uint8_t* cResponsePacket::reserve(uint32_t len) { if (!checkExtend(len)) return 0; uint8_t* result = buffer + bufUsed; bufUsed += len; return result; } bool cResponsePacket::unreserve(uint32_t len) { if(bufUsed < len) return false; bufUsed -= len; return true; } bool cResponsePacket::add_String(const char* string) { uint32_t len = strlen(string) + 1; if (!checkExtend(len)) return false; memcpy(buffer + bufUsed, string, len); bufUsed += len; return true; } bool cResponsePacket::add_U32(uint32_t ul) { if (!checkExtend(sizeof(uint32_t))) return false; uint32_t tmp = htonl(ul); memcpy(&buffer[bufUsed], &tmp, sizeof(uint32_t)); bufUsed += sizeof(uint32_t); return true; } bool cResponsePacket::add_U8(uint8_t c) { if (!checkExtend(sizeof(uint8_t))) return false; buffer[bufUsed] = c; bufUsed += sizeof(uint8_t); return true; } bool cResponsePacket::add_S32(int32_t l) { if (!checkExtend(sizeof(int32_t))) return false; int32_t tmp = htonl(l); memcpy(&buffer[bufUsed], &tmp, sizeof(int32_t)); bufUsed += sizeof(int32_t); return true; } bool cResponsePacket::add_U64(uint64_t ull) { if (!checkExtend(sizeof(uint64_t))) return false; uint64_t tmp = __cpu_to_be64(ull); memcpy(&buffer[bufUsed], &tmp, sizeof(uint64_t)); bufUsed += sizeof(uint64_t); return true; } bool cResponsePacket::add_double(double d) { if (!checkExtend(sizeof(double))) return false; uint64_t ull; memcpy(&ull, &d, sizeof(double)); ull = __cpu_to_be64(ull); memcpy(&buffer[bufUsed], &ull, sizeof(uint64_t)); bufUsed += sizeof(uint64_t); return true; } bool cResponsePacket::checkExtend(uint32_t by) { if ((bufUsed + by) < bufSize) return true; if (512 > by) by = 512; uint8_t* newBuf = (uint8_t*)realloc(buffer, bufSize + by); if (!newBuf) return false; buffer = newBuf; bufSize += by; return true; } vdr-plugin-vnsiserver-1.5.2/responsepacket.h000066400000000000000000000050141303643374600212220ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2007 Chris Tallon * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ /* * This code is taken from VOMP for VDR plugin. */ #ifndef VNSI_RESPONSEPACKET_H #define VNSI_RESPONSEPACKET_H #include class cResponsePacket { public: cResponsePacket(); ~cResponsePacket(); void init(uint32_t requestID); void initScan(uint32_t opCode); void initStatus(uint32_t opCode); void initStream(uint32_t opCode, uint32_t streamID, uint32_t duration, int64_t pts, int64_t dts, uint32_t serial); void initOsd(uint32_t opCode, int32_t wnd, int32_t color, int32_t x0, int32_t y0, int32_t x1, int32_t y1); void finalise(); void finaliseStream(); void finaliseOSD(); bool copyin(const uint8_t* src, uint32_t len); uint8_t* reserve(uint32_t len); bool unreserve(uint32_t len); bool add_String(const char* string); bool add_U32(uint32_t ul); bool add_S32(int32_t l); bool add_U8(uint8_t c); bool add_U64(uint64_t ull); bool add_double(double d); uint8_t* getPtr() { return buffer; } uint32_t getLen() { return bufUsed; } uint32_t getStreamHeaderLength() { return headerLengthStream; } ; uint32_t getOSDHeaderLength() { return headerLengthOSD; } ; void setLen(uint32_t len) { bufUsed = len; } private: uint8_t* buffer; uint32_t bufSize; uint32_t bufUsed; void initBuffers(); bool checkExtend(uint32_t by); const static uint32_t headerLength = 12; const static uint32_t userDataLenPos = 8; const static uint32_t headerLengthStream = 40; const static uint32_t userDataLenPosStream = 36; const static uint32_t headerLengthOSD = 36; const static uint32_t userDataLenPosOSD = 32; }; #endif // VNSI_RESPONSEPACKET_H vdr-plugin-vnsiserver-1.5.2/setup.c000066400000000000000000000073711303643374600173370ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "setup.h" #include "vnsicommand.h" int PmtTimeout = 0; int TimeshiftMode = 0; int TimeshiftBufferSize = 5; int TimeshiftBufferFileSize = 6; char TimeshiftBufferDir[PATH_MAX] = "\0"; int PlayRecording = 0; int AvoidEPGScan = 1; int DisableScrambleTimeout = 0; int DisableCamBlacklist = 0; cMenuSetupVNSI::cMenuSetupVNSI(void) { timeshiftModesTexts[0] = tr("Off"); timeshiftModesTexts[1] = tr("RAM"); timeshiftModesTexts[2] = tr("File"); newTimeshiftMode = TimeshiftMode; Add(new cMenuEditStraItem( tr("Time Shift Mode"), &newTimeshiftMode, 3, timeshiftModesTexts)); newTimeshiftBufferSize = TimeshiftBufferSize; Add(new cMenuEditIntItem( tr("TS Buffersize (RAM) (1-80) x 100MB"), &newTimeshiftBufferSize)); newTimeshiftBufferFileSize = TimeshiftBufferFileSize; Add(new cMenuEditIntItem( tr("TS Buffersize (File) (1-10) x 1GB"), &newTimeshiftBufferFileSize)); strn0cpy(newTimeshiftBufferDir, TimeshiftBufferDir, sizeof(newTimeshiftBufferDir)); Add(new cMenuEditStrItem(tr("TS Buffer Directory"), newTimeshiftBufferDir, sizeof(newTimeshiftBufferDir))); newPlayRecording = PlayRecording; Add(new cMenuEditBoolItem( tr("Play Recording instead of live"), &newPlayRecording)); newAvoidEPGScan = AvoidEPGScan; Add(new cMenuEditBoolItem( tr("Avoid EPG scan while streaming"), &newAvoidEPGScan)); newDisableScrambleTimeout = DisableScrambleTimeout; Add(new cMenuEditBoolItem( tr("Disable scramble timeout"), &newDisableScrambleTimeout)); newDisableCamBlacklist = DisableCamBlacklist; Add(new cMenuEditBoolItem( tr("Disable cam blacklist"), &newDisableCamBlacklist)); } void cMenuSetupVNSI::Store(void) { if (newPmtTimeout > 10 || newPmtTimeout < 0) newPmtTimeout = 2; SetupStore(CONFNAME_PMTTIMEOUT, PmtTimeout = newPmtTimeout); SetupStore(CONFNAME_TIMESHIFT, TimeshiftMode = newTimeshiftMode); if (newTimeshiftBufferSize > 80) newTimeshiftBufferSize = 80; else if (newTimeshiftBufferSize < 1) newTimeshiftBufferSize = 1; SetupStore(CONFNAME_TIMESHIFTBUFFERSIZE, TimeshiftBufferSize = newTimeshiftBufferSize); if (newTimeshiftBufferFileSize > 20) newTimeshiftBufferFileSize = 20; else if (newTimeshiftBufferFileSize < 1) newTimeshiftBufferFileSize = 1; SetupStore(CONFNAME_TIMESHIFTBUFFERFILESIZE, TimeshiftBufferFileSize = newTimeshiftBufferFileSize); strn0cpy(TimeshiftBufferDir, newTimeshiftBufferDir, sizeof(TimeshiftBufferDir)); if (*TimeshiftBufferDir && TimeshiftBufferDir[strlen(TimeshiftBufferDir)-1] == '/') /* strip trailing slash */ TimeshiftBufferDir[strlen(TimeshiftBufferDir)-1] = 0; SetupStore(CONFNAME_TIMESHIFTBUFFERDIR, TimeshiftBufferDir); SetupStore(CONFNAME_PLAYRECORDING, PlayRecording = newPlayRecording); SetupStore(CONFNAME_AVOIDEPGSCAN, AvoidEPGScan = newAvoidEPGScan); SetupStore(CONFNAME_DISABLESCRAMBLETIMEOUT, DisableScrambleTimeout = newDisableScrambleTimeout); SetupStore(CONFNAME_DISABLECAMBLACKLIST, DisableCamBlacklist = newDisableCamBlacklist); } vdr-plugin-vnsiserver-1.5.2/setup.h000066400000000000000000000025101303643374600173320ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_SETUP_H #define VNSI_SETUP_H #include class cMenuSetupVNSI : public cMenuSetupPage { private: int newPmtTimeout; int newTimeshiftMode; const char *timeshiftModesTexts[3]; int newTimeshiftBufferSize; int newTimeshiftBufferFileSize; char newTimeshiftBufferDir[PATH_MAX]; int newPlayRecording; int newAvoidEPGScan; int newDisableScrambleTimeout; int newDisableCamBlacklist; protected: virtual void Store(void); public: cMenuSetupVNSI(void); }; #endif // VNSI_SETUP_H vdr-plugin-vnsiserver-1.5.2/status.c000066400000000000000000000163251303643374600175210ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2014 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "status.h" #include "vnsi.h" #include #include #include #include #include #include #include #include #include cVNSIStatus::cVNSIStatus() : cThread("VNSIStatus") { } cVNSIStatus::~cVNSIStatus() { Shutdown(); } void cVNSIStatus::Init(CVNSITimers *timers) { m_vnsiTimers = timers; Start(); } void cVNSIStatus::Shutdown() { Cancel(5); cMutexLock lock(&m_mutex); m_clients.clear(); } static bool CheckFileSuffix(const char *name, const char *suffix, size_t suffix_length) { size_t name_length = strlen(name); return name_length > suffix_length && memcmp(name + name_length - suffix_length, suffix, suffix_length) == 0; } static void DeleteFiles(const char *directory_path, const char *suffix) { const size_t suffix_length = strlen(suffix); DIR *dir = opendir(directory_path); if (dir == nullptr) return; std::string path(directory_path); path.push_back('/'); const size_t start = path.size(); while (auto *e = readdir(dir)) { if (CheckFileSuffix(e->d_name, suffix, suffix_length)) { path.replace(start, path.size(), e->d_name); if (unlink(path.c_str()) < 0) { ERRORLOG("Failed to delete %s: %s", path.c_str(), strerror(errno)); } } } closedir(dir); } void cVNSIStatus::Action(void) { cTimeMs chanTimer(0); cTimeMs epgTimer(0); // get initial state of the recordings #if VDRVERSNUM >= 20301 cStateKey chanState; const cChannels *channels = cChannels::GetChannelsRead(chanState); chanState.Remove(false); #endif // get initial state of the recordings #if VDRVERSNUM >= 20301 cStateKey recState; const cRecordings *recordings = cRecordings::GetRecordingsRead(recState); recState.Remove(false); #else int recState = -1; Recordings.StateChanged(recState); #endif // get initial state of the timers #if VDRVERSNUM >= 20301 cStateKey timerState; const cTimers *timers = cTimers::GetTimersRead(timerState); timerState.Remove(false); #else int timerState = -1; Timers.Modified(timerState); #endif // vnsitimer int vnsitimerState; m_vnsiTimers->StateChange(vnsitimerState); // last update of epg #if VDRVERSNUM >= 20301 cStateKey epgState; const cSchedules *epg = cSchedules::GetSchedulesRead(epgState); epgState.Remove(false); #else time_t epgUpdate = cSchedules::Modified(); #endif // delete old timeshift file struct stat sb; if ((*TimeshiftBufferDir) && stat(TimeshiftBufferDir, &sb) == 0 && S_ISDIR(sb.st_mode)) { DeleteFiles(TimeshiftBufferDir, ".vnsi"); } else { #if VDRVERSNUM >= 20102 DeleteFiles(cVideoDirectory::Name(), ".vnsi"); #else DeleteFiles(VideoDirectory, ".vnsi"); #endif } // set thread priority SetPriority(1); while (Running()) { m_mutex.Lock(); // remove disconnected clients for (ClientList::iterator i = m_clients.begin(); i != m_clients.end();) { if (!i->Active()) { INFOLOG("Client with ID %u seems to be disconnected, removing from client list", i->GetID()); i = m_clients.erase(i); } else { i++; } } /*! * Don't to updates during running channel scan, KODI's PVR manager becomes * restarted of finished scan. */ if (!cVNSIClient::InhibidDataUpdates()) { // reset inactivity timeout as long as there are clients connected if (!m_clients.empty()) { ShutdownHandler.SetUserInactiveTimeout(); } // trigger clients to reload the modified channel list if(chanTimer.TimedOut()) { #if VDRVERSNUM >= 20301 if (channels->Lock(chanState)) { chanState.Remove(false); INFOLOG("Requesting clients to reload channel list"); for (auto &i : m_clients) i.ChannelsChange(); chanTimer.Set(5000); } #else int modified = Channels.Modified(); if (modified) { Channels.SetModified((modified == CHANNELSMOD_USER) ? true : false); INFOLOG("Requesting clients to reload channel list"); for (auto &i : m_clients) i.ChannelsChange(); } chanTimer.Set(5000); #endif } #if VDRVERSNUM >= 20301 if (recordings->Lock(recState)) { recState.Remove(); INFOLOG("Requesting clients to reload recordings list"); for (auto &i : m_clients) { i.RecordingsChange(); } } if (timers->Lock(timerState)) { timerState.Remove(false); INFOLOG("Requesting clients to reload timers"); for (auto &i : m_clients) { i.SignalTimerChange(); } } if (m_vnsiTimers->StateChange(vnsitimerState)) { INFOLOG("Requesting clients to reload vnsi-timers"); for (auto &i : m_clients) { i.SignalTimerChange(); } } if (epgTimer.TimedOut()) { if (epg->Lock(epgState)) { epgState.Remove(false); DEBUGLOG("Requesting clients to load epg"); int callAgain = 0; for (auto &i : m_clients) { callAgain |= i.EpgChange(); } if (callAgain & VNSI_EPG_AGAIN) { epgTimer.Set(100); epgState.Reset(); } else { if (callAgain & VNSI_EPG_PAUSE) { epgState.Reset(); } epgTimer.Set(5000); m_vnsiTimers->Scan(); } } } #else // update recordings if(Recordings.StateChanged(recState)) { INFOLOG("Recordings state changed (%i)", recState); INFOLOG("Requesting clients to reload recordings list"); for (auto &i : m_clients) i.RecordingsChange(); } // update timers if(Timers.Modified(timerState)) { INFOLOG("Timers state changed (%i)", timerState); INFOLOG("Requesting clients to reload timers"); for (auto &i : m_clients) { i.SignalTimerChange(); } } // update epg if((cSchedules::Modified() > epgUpdate + 10) || time(NULL) > epgUpdate + 300) { for (auto &i : m_clients) { i.EpgChange(); } epgUpdate = time(NULL); } #endif } m_mutex.Unlock(); usleep(250*1000); } } vdr-plugin-vnsiserver-1.5.2/status.h000066400000000000000000000027541303643374600175270ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2014 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include #include "vnsitimer.h" #include "vnsiclient.h" class cVNSIClient; typedef std::list ClientList; class cVNSIStatus : public cThread { public: cVNSIStatus(); virtual ~cVNSIStatus(); cVNSIStatus(const cVNSIStatus &) = delete; cVNSIStatus &operator=(const cVNSIStatus &) = delete; void Init(CVNSITimers *timers); void Shutdown(); template void AddClient(Args&&... args) { cMutexLock lock(&m_mutex); m_clients.emplace_back(std::forward(args)...); } protected: virtual void Action(void); ClientList m_clients; cMutex m_mutex; CVNSITimers *m_vnsiTimers; }; vdr-plugin-vnsiserver-1.5.2/streamer.c000066400000000000000000000460041303643374600200150ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include #include #include #include #include #include "config.h" #include "streamer.h" #include "cxsocket.h" #include "vnsicommand.h" #include "responsepacket.h" #include "vnsi.h" #include "videobuffer.h" // --- cLiveStreamer ------------------------------------------------- cLiveStreamer::cLiveStreamer(int clientID, bool bAllowRDS, uint8_t timeshift, uint32_t timeout) : cThread("cLiveStreamer stream processor") , m_ClientID(clientID) , m_scanTimeout(timeout) , m_Demuxer(bAllowRDS) , m_VideoInput(m_Event) { m_Timeshift = timeshift; memset(&m_FrontendInfo, 0, sizeof(m_FrontendInfo)); if(m_scanTimeout == 0) m_scanTimeout = VNSIServerConfig.stream_timeout; } cLiveStreamer::~cLiveStreamer() { DEBUGLOG("Started to delete live streamer"); Cancel(5); Close(); DEBUGLOG("Finished to delete live streamer"); } bool cLiveStreamer::Open(int serial) { Close(); #if APIVERSNUM >= 10725 m_Device = cDevice::GetDevice(m_Channel, m_Priority, true, true); #else m_Device = cDevice::GetDevice(m_Channel, m_Priority, true); #endif if (!m_Device) return false; bool recording = false; if (VNSIServerConfig.testStreamActive) // test harness { recording = true; m_VideoBuffer = cVideoBuffer::Create(VNSIServerConfig.testStreamFile); } else if (PlayRecording && serial == -1) { #if VDRVERSNUM >= 20301 LOCK_TIMERS_READ; for (const cTimer *timer = Timers->First(); timer; timer = Timers->Next(timer)) #else for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) #endif { if (timer && timer->Recording() && timer->Channel() == m_Channel) { #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; cTimer t(*timer); cRecording matchRec(&t, t.Event()); const cRecording *rec; { rec = Recordings->GetByName(matchRec.FileName()); if (!rec) { return false; } } #else Recordings.Load(); cRecording matchRec(timer, timer->Event()); cRecording *rec; { cThreadLock RecordingsLock(&Recordings); rec = Recordings.GetByName(matchRec.FileName()); if (!rec) { return false; } } #endif m_VideoBuffer = cVideoBuffer::Create(rec); recording = true; break; } } } if (!recording) { m_VideoBuffer = cVideoBuffer::Create(m_ClientID, m_Timeshift); } if (!m_VideoBuffer) return false; if (!recording) { if (m_Channel && ((m_Channel->Source() >> 24) == 'V')) m_IsMPEGPS = true; if (!m_VideoInput.Open(m_Channel, m_Priority, m_VideoBuffer)) { ERRORLOG("Can't switch to channel %i - %s", m_Channel->Number(), m_Channel->Name()); return false; } } m_Demuxer.Open(*m_Channel, m_VideoBuffer); if (serial >= 0) m_Demuxer.SetSerial(serial); return true; } void cLiveStreamer::Close(void) { INFOLOG("LiveStreamer::Close - close"); m_VideoInput.Close(); m_Demuxer.Close(); if (m_VideoBuffer) { delete m_VideoBuffer; m_VideoBuffer = NULL; } if (m_Frontend >= 0) { close(m_Frontend); m_Frontend = -1; } } void cLiveStreamer::Action(void) { int ret; sStreamPacket pkt_data; sStreamPacket pkt_side_data; // Additional data memset(&pkt_data, 0, sizeof(sStreamPacket)); memset(&pkt_side_data, 0, sizeof(sStreamPacket)); bool requestStreamChangeData = false; bool requestStreamChangeSideData = false; cTimeMs last_info(1000); cTimeMs bufferStatsTimer(1000); int openFailCount = 0; while (Running()) { auto retune = m_VideoInput.ReceivingStatus(); if (retune == cVideoInput::RETUNE) // allow timeshift playback when retune == cVideoInput::CLOSE ret = -1; else ret = m_Demuxer.Read(&pkt_data, &pkt_side_data); if (ret > 0) { if (pkt_data.pmtChange) { requestStreamChangeData = true; requestStreamChangeSideData = true; } // Process normal data if present if (pkt_data.data) { if (pkt_data.streamChange || requestStreamChangeData) sendStreamChange(); requestStreamChangeData = false; if (pkt_data.reftime) { sendRefTime(&pkt_data); pkt_data.reftime = 0; } sendStreamPacket(&pkt_data); } // If some additional data is present inside the stream, it is written there (currently RDS inside MPEG2-Audio) if (pkt_side_data.data) { if (pkt_side_data.streamChange || requestStreamChangeSideData) sendStreamChange(); requestStreamChangeSideData = false; sendStreamPacket(&pkt_side_data); pkt_side_data.data = NULL; } // send signal info every 10 sec. if(last_info.TimedOut()) { last_info.Set(10000); sendSignalInfo(); // prevent EPG scan (activity timeout is 60s) // EPG scan can cause artifacts on dual tuner cards if (AvoidEPGScan) { EITScanner.Activity(); } } // send buffer stats if(bufferStatsTimer.TimedOut()) { sendBufferStatus(); bufferStatsTimer.Set(1000); } } else if (ret == -1) { // no data if (retune == cVideoInput::CLOSE) { m_Socket->Shutdown(); break; } if (m_Demuxer.GetError() & ERROR_CAM_ERROR) { INFOLOG("CAM error, try reset"); cCamSlot *cs = m_Device->CamSlot(); if (cs) cs->StopDecrypting(); retune = cVideoInput::RETUNE; } if (retune == cVideoInput::RETUNE) { INFOLOG("re-tuning..."); m_VideoInput.Close(); if (!m_VideoInput.Open(m_Channel, m_Priority, m_VideoBuffer)) { if (++openFailCount == 3) { openFailCount = 0; cCondWait::SleepMs(2000); } else cCondWait::SleepMs(100); } else openFailCount = 0; } else m_Event.Wait(10); if(m_last_tick.Elapsed() >= (uint64_t)(m_scanTimeout*1000)) { sendStreamStatus(); m_last_tick.Set(0); m_SignalLost = true; } } else if (ret == -2) { if (!Open(m_Demuxer.GetSerial())) { m_Socket->Shutdown(); break; } } } INFOLOG("exit streamer thread"); } bool cLiveStreamer::StreamChannel(const cChannel *channel, int priority, cxSocket *Socket, cResponsePacket *resp) { if (channel == NULL) { ERRORLOG("Starting streaming of channel without valid channel"); return false; } m_Channel = channel; m_Priority = priority; m_Socket = Socket; if (m_Priority < 0) m_Priority = 0; if (!Open()) return false; // Send the OK response here, that it is before the Stream end message resp->add_U32(VNSI_RET_OK); resp->finalise(); m_Socket->write(resp->getPtr(), resp->getLen()); Activate(true); INFOLOG("Successfully switched to channel %i - %s", m_Channel->Number(), m_Channel->Name()); return true; } inline void cLiveStreamer::Activate(bool On) { if (On) { DEBUGLOG("VDR active, sending stream start message"); Start(); } else { DEBUGLOG("VDR inactive, sending stream end message"); Cancel(5); } } void cLiveStreamer::sendStreamPacket(sStreamPacket *pkt) { if(pkt == NULL) return; if(pkt->size == 0) return; m_streamHeader.initStream(VNSI_STREAM_MUXPKT, pkt->id, pkt->duration, pkt->pts, pkt->dts, pkt->serial); m_streamHeader.setLen(m_streamHeader.getStreamHeaderLength() + pkt->size); m_streamHeader.finaliseStream(); m_Socket->LockWrite(); m_Socket->write(m_streamHeader.getPtr(), m_streamHeader.getStreamHeaderLength(), -1, true); m_Socket->write(pkt->data, pkt->size); m_Socket->UnlockWrite(); m_last_tick.Set(0); m_SignalLost = false; } void cLiveStreamer::sendStreamChange() { cResponsePacket resp; resp.initStream(VNSI_STREAM_CHANGE, 0, 0, 0, 0, 0); uint32_t FpsScale, FpsRate, Height, Width; double Aspect; uint32_t Channels, SampleRate, BitRate, BitsPerSample, BlockAlign; for (cTSStream* stream = m_Demuxer.GetFirstStream(); stream; stream = m_Demuxer.GetNextStream()) { resp.add_U32(stream->GetPID()); if (stream->Type() == stMPEG2AUDIO) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("MPEG2AUDIO"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); for (const auto &i : stream->GetSideDataTypes()) { resp.add_U32(i.first); if (i.second == scRDS) { resp.add_String("RDS"); resp.add_String(stream->GetLanguage()); resp.add_U32(stream->GetPID()); } } } else if (stream->Type() == stMPEG2VIDEO) { stream->GetVideoInformation(FpsScale, FpsRate, Height, Width, Aspect); resp.add_String("MPEG2VIDEO"); resp.add_U32(FpsScale); resp.add_U32(FpsRate); resp.add_U32(Height); resp.add_U32(Width); resp.add_double(Aspect); } else if (stream->Type() == stAC3) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("AC3"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); } else if (stream->Type() == stH264) { stream->GetVideoInformation(FpsScale, FpsRate, Height, Width, Aspect); resp.add_String("H264"); resp.add_U32(FpsScale); resp.add_U32(FpsRate); resp.add_U32(Height); resp.add_U32(Width); resp.add_double(Aspect); } else if (stream->Type() == stHEVC) { stream->GetVideoInformation(FpsScale, FpsRate, Height, Width, Aspect); resp.add_String("HEVC"); resp.add_U32(FpsScale); resp.add_U32(FpsRate); resp.add_U32(Height); resp.add_U32(Width); resp.add_double(Aspect); } else if (stream->Type() == stDVBSUB) { resp.add_String("DVBSUB"); resp.add_String(stream->GetLanguage()); resp.add_U32(stream->CompositionPageId()); resp.add_U32(stream->AncillaryPageId()); } else if (stream->Type() == stTELETEXT) { resp.add_String("TELETEXT"); resp.add_String(stream->GetLanguage()); resp.add_U32(stream->CompositionPageId()); resp.add_U32(stream->AncillaryPageId()); } else if (stream->Type() == stAACADTS) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("AAC"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); } else if (stream->Type() == stAACLATM) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("AAC_LATM"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); } else if (stream->Type() == stEAC3) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("EAC3"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); } else if (stream->Type() == stDTS) { stream->GetAudioInformation(Channels, SampleRate, BitRate, BitsPerSample, BlockAlign); resp.add_String("DTS"); resp.add_String(stream->GetLanguage()); resp.add_U32(Channels); resp.add_U32(SampleRate); resp.add_U32(BlockAlign); resp.add_U32(BitRate); resp.add_U32(BitsPerSample); } } resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } void cLiveStreamer::sendSignalInfo() { /* If no frontend is found m_Frontend is set to -2, in this case return a empty signalinfo package */ if (m_Frontend == -2) { cResponsePacket resp; resp.initStream(VNSI_STREAM_SIGNALINFO, 0, 0, 0, 0, 0); resp.add_String(*cString::sprintf("Unknown")); resp.add_String(*cString::sprintf("Unknown")); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); return; } if (m_Channel && ((m_Channel->Source() >> 24) == 'V')) { if (m_Frontend < 0) { for (int i = 0; i < 8; i++) { m_DeviceString = cString::sprintf("/dev/video%d", i); m_Frontend = open(m_DeviceString, O_RDONLY | O_NONBLOCK); if (m_Frontend >= 0) { if (ioctl(m_Frontend, VIDIOC_QUERYCAP, &m_vcap) < 0) { ERRORLOG("cannot read analog frontend info."); close(m_Frontend); m_Frontend = -1; memset(&m_vcap, 0, sizeof(m_vcap)); continue; } break; } } if (m_Frontend < 0) m_Frontend = -2; } if (m_Frontend >= 0) { cResponsePacket resp; resp.initStream(VNSI_STREAM_SIGNALINFO, 0, 0, 0, 0, 0); resp.add_String(*cString::sprintf("Analog #%s - %s (%s)", *m_DeviceString, (char *) m_vcap.card, m_vcap.driver)); resp.add_String(""); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } } else { if (m_Frontend < 0) { m_DeviceString = cString::sprintf(FRONTEND_DEVICE, m_Device->CardIndex(), 0); m_Frontend = open(m_DeviceString, O_RDONLY | O_NONBLOCK); if (m_Frontend >= 0) { if (ioctl(m_Frontend, FE_GET_INFO, &m_FrontendInfo) < 0) { ERRORLOG("cannot read frontend info."); close(m_Frontend); m_Frontend = -2; memset(&m_FrontendInfo, 0, sizeof(m_FrontendInfo)); return; } } } if (m_Frontend >= 0) { cResponsePacket resp; resp.initStream(VNSI_STREAM_SIGNALINFO, 0, 0, 0, 0, 0); fe_status_t status; uint16_t fe_snr; uint16_t fe_signal; uint32_t fe_ber; uint32_t fe_unc; memset(&status, 0, sizeof(status)); ioctl(m_Frontend, FE_READ_STATUS, &status); if (ioctl(m_Frontend, FE_READ_SIGNAL_STRENGTH, &fe_signal) == -1) fe_signal = -2; if (ioctl(m_Frontend, FE_READ_SNR, &fe_snr) == -1) fe_snr = -2; if (ioctl(m_Frontend, FE_READ_BER, &fe_ber) == -1) fe_ber = -2; if (ioctl(m_Frontend, FE_READ_UNCORRECTED_BLOCKS, &fe_unc) == -1) fe_unc = -2; switch (m_Channel->Source() & cSource::st_Mask) { case cSource::stSat: resp.add_String(*cString::sprintf("DVB-S%s #%d - %s", (m_FrontendInfo.caps & 0x10000000) ? "2" : "", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); break; case cSource::stCable: resp.add_String(*cString::sprintf("DVB-C #%d - %s", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); break; case cSource::stTerr: resp.add_String(*cString::sprintf("DVB-T #%d - %s", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); break; case cSource::stAtsc: resp.add_String(*cString::sprintf("ATSC #%d - %s", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); break; default: resp.add_U8(0); break; } resp.add_String(*cString::sprintf("%s:%s:%s:%s:%s", (status & FE_HAS_LOCK) ? "LOCKED" : "-", (status & FE_HAS_SIGNAL) ? "SIGNAL" : "-", (status & FE_HAS_CARRIER) ? "CARRIER" : "-", (status & FE_HAS_VITERBI) ? "VITERBI" : "-", (status & FE_HAS_SYNC) ? "SYNC" : "-")); resp.add_U32(fe_snr); resp.add_U32(fe_signal); resp.add_U32(fe_ber); resp.add_U32(fe_unc); resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } } } void cLiveStreamer::sendStreamStatus() { cResponsePacket resp; resp.initStream(VNSI_STREAM_STATUS, 0, 0, 0, 0, 0); uint16_t error = m_Demuxer.GetError(); if (error & ERROR_PES_SCRAMBLE) { INFOLOG("Channel: scrambled %d", error); resp.add_String(cString::sprintf("Channel: scrambled (%d)", error)); } else if (error & ERROR_PES_STARTCODE) { INFOLOG("Channel: startcode %d", error); resp.add_String(cString::sprintf("Channel: encrypted? (%d)", error)); } else if (error & ERROR_DEMUX_NODATA) { INFOLOG("Channel: no data %d", error); resp.add_String(cString::sprintf("Channel: no data")); } else { INFOLOG("Channel: unknown error %d", error); resp.add_String(cString::sprintf("Channel: unknown error (%d)", error)); } resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } void cLiveStreamer::sendBufferStatus() { cResponsePacket resp; resp.initStream(VNSI_STREAM_BUFFERSTATS, 0, 0, 0, 0, 0); uint32_t start, end; bool timeshift; m_Demuxer.BufferStatus(timeshift, start, end); resp.add_U8(timeshift); resp.add_U32(start); resp.add_U32(end); resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } void cLiveStreamer::sendRefTime(sStreamPacket *pkt) { if(pkt == NULL) return; cResponsePacket resp; resp.initStream(VNSI_STREAM_REFTIME, 0, 0, 0, 0, 0); resp.add_U32(pkt->reftime); resp.add_U64(pkt->pts); resp.finaliseStream(); m_Socket->write(resp.getPtr(), resp.getLen()); } bool cLiveStreamer::SeekTime(int64_t time, uint32_t &serial) { bool ret = m_Demuxer.SeekTime(time); serial = m_Demuxer.GetSerial(); return ret; } void cLiveStreamer::RetuneChannel(const cChannel *channel) { if (m_Channel != channel || !m_VideoInput.IsOpen()) return; INFOLOG("re-tune to channel %s", m_Channel->Name()); m_VideoInput.RequestRetune(); } vdr-plugin-vnsiserver-1.5.2/streamer.h000066400000000000000000000071141303643374600200210ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_RECEIVER_H #define VNSI_RECEIVER_H #include #include #include #include #include #include #include "parser.h" #include "responsepacket.h" #include "demuxer.h" #include "videoinput.h" class cxSocket; class cChannel; class cTSParser; class cResponsePacket; class cVideoBuffer; class cVideoInput; class cLiveStreamer : public cThread { friend class cParser; void sendStreamPacket(sStreamPacket *pkt); void sendStreamChange(); void sendSignalInfo(); void sendStreamStatus(); void sendBufferStatus(); void sendRefTime(sStreamPacket *pkt); const int m_ClientID; const cChannel *m_Channel = nullptr; /*!> Channel to stream */ cDevice *m_Device; cxSocket *m_Socket = nullptr; /*!> The socket class to communicate with client */ int m_Frontend = -1; /*!> File descriptor to access used receiving device */ dvb_frontend_info m_FrontendInfo; /*!> DVB Information about the receiving device (DVB only) */ v4l2_capability m_vcap; /*!> PVR Information about the receiving device (pvrinput only) */ cString m_DeviceString; /*!> The name of the receiving device */ bool m_startup = true; bool m_IsAudioOnly = false; /*!> Set to true if streams contains only audio */ bool m_IsMPEGPS = false; /*!> TS Stream contains MPEG PS data like from pvrinput */ uint32_t m_scanTimeout; /*!> Channel scanning timeout (in seconds) */ cTimeMs m_last_tick; bool m_SignalLost = false; bool m_IFrameSeen = false; cResponsePacket m_streamHeader; cVNSIDemuxer m_Demuxer; cVideoBuffer *m_VideoBuffer = nullptr; cVideoInput m_VideoInput; int m_Priority; uint8_t m_Timeshift; cCondWait m_Event; protected: virtual void Action(void); bool Open(int serial = -1); void Close(); public: cLiveStreamer(int clientID, bool bAllowRDS, uint8_t timeshift, uint32_t timeout = 0); virtual ~cLiveStreamer(); cLiveStreamer(const cLiveStreamer &) = delete; cLiveStreamer &operator=(const cLiveStreamer &) = delete; void Activate(bool On); bool StreamChannel(const cChannel *channel, int priority, cxSocket *Socket, cResponsePacket* resp); bool IsStarting() { return m_startup; } bool IsAudioOnly() { return m_IsAudioOnly; } bool IsMPEGPS() { return m_IsMPEGPS; } bool SeekTime(int64_t time, uint32_t &serial); void RetuneChannel(const cChannel *channel); }; #endif // VNSI_RECEIVER_H vdr-plugin-vnsiserver-1.5.2/videobuffer.c000066400000000000000000000504161303643374600204750ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "videobuffer.h" #include "config.h" #include "vnsi.h" #include "recplayer.h" #include #include #include #include #include #include #include class cVideoBufferSimple : public cVideoBuffer { friend class cVideoBuffer; public: virtual void Put(const uint8_t *buf, unsigned int size); virtual int ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime); protected: cVideoBufferSimple(); cRingBufferLinear m_Buffer; int m_BytesConsumed; }; cVideoBufferSimple::cVideoBufferSimple() :m_Buffer(MEGABYTE(5), TS_SIZE * 2, false) { m_Buffer.SetTimeouts(0, 100); m_BytesConsumed = 0; } void cVideoBufferSimple::Put(const uint8_t *buf, unsigned int size) { m_Buffer.Put(buf, size); } int cVideoBufferSimple::ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) { int readBytes; if (m_BytesConsumed) { m_Buffer.Del(m_BytesConsumed); } m_BytesConsumed = 0; *buf = m_Buffer.Get(readBytes); if (!(*buf) || readBytes < TS_SIZE) { usleep(100); return 0; } /* Make sure we are looking at a TS packet */ while (readBytes > TS_SIZE) { if ((*buf)[0] == TS_SYNC_BYTE && (*buf)[TS_SIZE] == TS_SYNC_BYTE) break; m_BytesConsumed++; (*buf)++; readBytes--; } if ((*buf)[0] != TS_SYNC_BYTE) { m_Buffer.Del(m_BytesConsumed); m_BytesConsumed = 0; return 0; } m_BytesConsumed += TS_SIZE; endTime = 0; wrapTime = 0; return TS_SIZE; } //----------------------------------------------------------------------------- #define MARGIN 40000 class cVideoBufferTimeshift : public cVideoBuffer { friend class cVideoBuffer; public: virtual off_t GetPosMin(); virtual off_t GetPosMax(); virtual off_t GetPosCur(); virtual void GetPositions(off_t *cur, off_t *min, off_t *max); virtual bool HasBuffer() { return true; }; protected: cVideoBufferTimeshift(); virtual bool Init() = 0; virtual off_t Available(); off_t m_BufferSize; off_t m_WritePtr; off_t m_ReadPtr; bool m_BufferFull; unsigned int m_Margin; unsigned int m_BytesConsumed; cMutex m_Mutex; }; cVideoBufferTimeshift::cVideoBufferTimeshift() { m_Margin = TS_SIZE*2; m_BufferFull = false; m_ReadPtr = 0; m_WritePtr = 0; m_BytesConsumed = 0; } off_t cVideoBufferTimeshift::GetPosMin() { off_t ret; if (!m_BufferFull) return 0; ret = m_WritePtr + MARGIN * 2; if (ret >= m_BufferSize) ret -= m_BufferSize; return ret; } off_t cVideoBufferTimeshift::GetPosMax() { off_t ret = m_WritePtr; if (ret < GetPosMin()) ret += m_BufferSize; return ret; } off_t cVideoBufferTimeshift::GetPosCur() { off_t ret = m_ReadPtr; if (ret < GetPosMin()) ret += m_BufferSize; return ret; } void cVideoBufferTimeshift::GetPositions(off_t *cur, off_t *min, off_t *max) { cMutexLock lock(&m_Mutex); *cur = GetPosCur(); *min = GetPosMin(); *min = (*min > *cur) ? *cur : *min; *max = GetPosMax(); } off_t cVideoBufferTimeshift::Available() { cMutexLock lock(&m_Mutex); off_t ret; if (m_ReadPtr <= m_WritePtr) ret = m_WritePtr - m_ReadPtr; else ret = m_BufferSize - (m_ReadPtr - m_WritePtr); return ret; } //----------------------------------------------------------------------------- class cVideoBufferRAM : public cVideoBufferTimeshift { friend class cVideoBuffer; public: virtual void Put(const uint8_t *buf, unsigned int size); virtual int ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime); virtual void SetPos(off_t pos); protected: cVideoBufferRAM(); virtual ~cVideoBufferRAM(); virtual bool Init(); uint8_t *m_Buffer; uint8_t *m_BufferPtr; }; cVideoBufferRAM::cVideoBufferRAM() { m_Buffer = 0; } cVideoBufferRAM::~cVideoBufferRAM() { free(m_Buffer); } bool cVideoBufferRAM::Init() { m_BufferSize = (off_t)TimeshiftBufferSize*100*1000*1000; INFOLOG("allocated timeshift buffer with size: %ld", m_BufferSize); m_Buffer = (uint8_t*)malloc(m_BufferSize + m_Margin); m_BufferPtr = m_Buffer + m_Margin; if (!m_Buffer) return false; else return true; } void cVideoBufferRAM::SetPos(off_t pos) { cMutexLock lock(&m_Mutex); m_ReadPtr = pos; if (m_ReadPtr >= m_BufferSize) m_ReadPtr -= m_BufferSize; m_BytesConsumed = 0; } void cVideoBufferRAM::Put(const uint8_t *buf, unsigned int size) { if (Available() + MARGIN >= m_BufferSize) { return; } if ((m_BufferSize - m_WritePtr) <= size) { int bytes = m_BufferSize - m_WritePtr; memcpy(m_BufferPtr+m_WritePtr, buf, bytes); size -= bytes; buf += bytes; cMutexLock lock(&m_Mutex); m_WritePtr = 0; } memcpy(m_BufferPtr+m_WritePtr, buf, size); cMutexLock lock(&m_Mutex); m_WritePtr += size; if (!m_BufferFull) { if ((m_WritePtr + 2*MARGIN) > m_BufferSize) { m_BufferFull = true; time(&m_bufferWrapTime); } } time(&m_bufferEndTime); } int cVideoBufferRAM::ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) { // move read pointer if (m_BytesConsumed) { cMutexLock lock(&m_Mutex); m_ReadPtr += m_BytesConsumed; if (m_ReadPtr >= m_BufferSize) m_ReadPtr -= m_BufferSize; endTime = m_bufferEndTime; wrapTime = m_bufferWrapTime; } m_BytesConsumed = 0; // check if we have anything to read off_t readBytes = Available(); if (readBytes < m_Margin) { return 0; } // if we are close to end, copy margin to front if (m_ReadPtr > (m_BufferSize - m_Margin)) { int bytesToCopy = m_BufferSize - m_ReadPtr; memmove(m_Buffer + (m_Margin - bytesToCopy), m_BufferPtr + m_ReadPtr, bytesToCopy); *buf = m_Buffer + (m_Margin - bytesToCopy); } else *buf = m_BufferPtr + m_ReadPtr; // Make sure we are looking at a TS packet while (readBytes > TS_SIZE) { if ((*buf)[0] == TS_SYNC_BYTE && (*buf)[TS_SIZE] == TS_SYNC_BYTE) break; m_BytesConsumed++; (*buf)++; readBytes--; } if ((*buf)[0] != TS_SYNC_BYTE) { return 0; } m_BytesConsumed += TS_SIZE; return TS_SIZE; } //----------------------------------------------------------------------------- class cVideoBufferFile : public cVideoBufferTimeshift { friend class cVideoBuffer; public: virtual off_t GetPosMax(); virtual void Put(const uint8_t *buf, unsigned int size); virtual int ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime); virtual void SetPos(off_t pos); protected: cVideoBufferFile(); cVideoBufferFile(int clientID); virtual ~cVideoBufferFile(); virtual bool Init(); virtual int ReadBytes(uint8_t *buf, off_t pos, unsigned int size); int m_ClientID; cString m_Filename; int m_Fd; uint8_t *m_ReadCache; unsigned int m_ReadCachePtr; unsigned int m_ReadCacheSize; unsigned int m_ReadCacheMaxSize; }; cVideoBufferFile::cVideoBufferFile() { } cVideoBufferFile::cVideoBufferFile(int clientID) { m_ClientID = clientID; m_Fd = 0; m_ReadCacheSize = 0; m_ReadCache = 0; } cVideoBufferFile::~cVideoBufferFile() { if (m_Fd) { close(m_Fd); unlink(m_Filename); m_Fd = 0; } if (m_ReadCache) free(m_ReadCache); } bool cVideoBufferFile::Init() { m_ReadCache = 0; m_ReadCacheMaxSize = 32000; m_ReadCache = (uint8_t*)malloc(m_ReadCacheMaxSize); if (!m_ReadCache) return false; m_BufferSize = (off_t)TimeshiftBufferFileSize*1000*1000*1000; struct stat sb; if ((*TimeshiftBufferDir) && stat(TimeshiftBufferDir, &sb) == 0 && S_ISDIR(sb.st_mode)) { m_Filename = cString::sprintf("%s/Timeshift-%d.vnsi", TimeshiftBufferDir, m_ClientID); } else #if VDRVERSNUM >= 20102 m_Filename = cString::sprintf("%s/Timeshift-%d.vnsi", cVideoDirectory::Name(), m_ClientID); #else m_Filename = cString::sprintf("%s/Timeshift-%d.vnsi", VideoDirectory, m_ClientID); #endif m_Fd = open(m_Filename, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); if (m_Fd == -1) { ERRORLOG("Could not open file: %s", (const char*)m_Filename); return false; } m_WritePtr = lseek(m_Fd, m_BufferSize - 1, SEEK_SET); if (m_WritePtr == -1) { ERRORLOG("(Init) Could not seek file: %s", (const char*)m_Filename); return false; } char tmp = '0'; if (safe_write(m_Fd, &tmp, 1) < 0) { ERRORLOG("(Init) Could not write to file: %s", (const char*)m_Filename); return false; } m_WritePtr = 0; m_ReadPtr = 0; m_ReadCacheSize = 0; return true; } void cVideoBufferFile::SetPos(off_t pos) { cMutexLock lock(&m_Mutex); m_ReadPtr = pos; if (m_ReadPtr >= m_BufferSize) m_ReadPtr -= m_BufferSize; m_BytesConsumed = 0; m_ReadCacheSize = 0; } off_t cVideoBufferFile::GetPosMax() { off_t posMax = cVideoBufferTimeshift::GetPosMax(); if (posMax >= m_ReadCacheMaxSize) posMax -= m_ReadCacheMaxSize; else posMax = 0; return posMax; } void cVideoBufferFile::Put(const uint8_t *buf, unsigned int size) { if (Available() + MARGIN >= m_BufferSize) { return; } if ((m_BufferSize - m_WritePtr) <= size) { int bytes = m_BufferSize - m_WritePtr; int p = 0; off_t ptr = m_WritePtr; while(bytes > 0) { p = pwrite(m_Fd, buf, bytes, ptr); if (p < 0) { ERRORLOG("Could not write to file: %s", (const char*)m_Filename); return; } size -= p; bytes -= p; buf += p; ptr += p; } cMutexLock lock(&m_Mutex); m_WritePtr = 0; } off_t ptr = m_WritePtr; int bytes = size; int p; while(bytes > 0) { p = pwrite(m_Fd, buf, bytes, ptr); if (p < 0) { ERRORLOG("Could not write to file: %s", (const char*)m_Filename); return; } bytes -= p; buf += p; ptr += p; } cMutexLock lock(&m_Mutex); m_WritePtr += size; if (!m_BufferFull) { if ((m_WritePtr + 2*MARGIN) > m_BufferSize) { m_BufferFull = true; time(&m_bufferWrapTime); } } time(&m_bufferEndTime); } int cVideoBufferFile::ReadBytes(uint8_t *buf, off_t pos, unsigned int size) { int p; for (;;) { p = pread(m_Fd, buf, size, pos); if (p < 0 && errno == EINTR) { continue; } return p; } } int cVideoBufferFile::ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) { // move read pointer if (m_BytesConsumed) { cMutexLock lock(&m_Mutex); m_ReadPtr += m_BytesConsumed; if (m_ReadPtr >= m_BufferSize) m_ReadPtr -= m_BufferSize; m_ReadCachePtr += m_BytesConsumed; endTime = m_bufferEndTime; wrapTime = m_bufferWrapTime; } m_BytesConsumed = 0; // check if we have anything to read off_t readBytes; if (m_ReadCacheSize && ((m_ReadCachePtr + m_Margin) <= m_ReadCacheSize)) { readBytes = m_ReadCacheSize - m_ReadCachePtr; *buf = m_ReadCache + m_ReadCachePtr; } else if ((readBytes = Available()) >= m_ReadCacheMaxSize) { if (m_ReadPtr + m_ReadCacheMaxSize <= m_BufferSize) { m_ReadCacheSize = ReadBytes(m_ReadCache, m_ReadPtr, m_ReadCacheMaxSize); if (m_ReadCacheSize < 0) { ERRORLOG("Could not read file: %s", (const char*)m_Filename); return 0; } if (m_ReadCacheSize < m_Margin) { ERRORLOG("Could not read file (margin): %s , read: %d", (const char*)m_Filename, m_ReadCacheSize); m_ReadCacheSize = 0; return 0; } readBytes = m_ReadCacheSize; *buf = m_ReadCache; m_ReadCachePtr = 0; } else { m_ReadCacheSize = ReadBytes(m_ReadCache, m_ReadPtr, m_BufferSize - m_ReadPtr); if ((m_ReadCacheSize < m_Margin) && (m_ReadCacheSize != (m_BufferSize - m_ReadPtr))) { ERRORLOG("Could not read file (end): %s", (const char*)m_Filename); m_ReadCacheSize = 0; return 0; } readBytes = ReadBytes(m_ReadCache + m_ReadCacheSize, 0, m_ReadCacheMaxSize - m_ReadCacheSize); if (readBytes < 0) { ERRORLOG("Could not read file (end): %s", (const char*)m_Filename); m_ReadCacheSize = 0; return 0; } m_ReadCacheSize += readBytes; if (m_ReadCacheSize < m_Margin) { ERRORLOG("Could not read file (margin): %s", (const char*)m_Filename); m_ReadCacheSize = 0; return 0; } readBytes = m_ReadCacheSize; *buf = m_ReadCache; m_ReadCachePtr = 0; } } else return 0; // Make sure we are looking at a TS packet while (readBytes > TS_SIZE) { if ((*buf)[0] == TS_SYNC_BYTE && (*buf)[TS_SIZE] == TS_SYNC_BYTE) break; m_BytesConsumed++; (*buf)++; readBytes--; } if ((*buf)[0] != TS_SYNC_BYTE) { return 0; } m_BytesConsumed += TS_SIZE; return TS_SIZE; } //----------------------------------------------------------------------------- class cVideoBufferRecording : public cVideoBufferFile { friend class cVideoBuffer; public: virtual off_t GetPosMax(); virtual void Put(const uint8_t *buf, unsigned int size); virtual int ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime); virtual time_t GetRefTime(); protected: cVideoBufferRecording(const cRecording *rec); virtual ~cVideoBufferRecording(); virtual bool Init(); virtual off_t Available(); off_t GetPosEnd(); cRecPlayer *m_RecPlayer; const cRecording *m_Recording; cTimeMs m_ScanTimer; }; cVideoBufferRecording::cVideoBufferRecording(const cRecording *rec) { m_Recording = rec; m_ReadCacheSize = 0; m_ReadCache = 0; } cVideoBufferRecording::~cVideoBufferRecording() { INFOLOG("delete cVideoBufferRecording"); if (m_RecPlayer) delete m_RecPlayer; } off_t cVideoBufferRecording::GetPosMax() { m_RecPlayer->reScan(); m_WritePtr = m_RecPlayer->getLengthBytes(); return cVideoBufferFile::GetPosMax(); } void cVideoBufferRecording::Put(const uint8_t *buf, unsigned int size) { } bool cVideoBufferRecording::Init() { m_ReadCacheMaxSize = 32000; m_ReadCache = (uint8_t*)malloc(m_ReadCacheMaxSize); if (!m_ReadCache) return false; m_RecPlayer = new cRecPlayer(m_Recording, true); if (!m_RecPlayer) return false; m_WritePtr = 0; m_ReadPtr = 0; m_ReadCacheSize = 0; m_InputAttached = false; m_ScanTimer.Set(0); return true; } time_t cVideoBufferRecording::GetRefTime() { return m_Recording->Start(); } off_t cVideoBufferRecording::Available() { if (m_ScanTimer.TimedOut()) { m_RecPlayer->reScan(); m_ScanTimer.Set(1000); } m_BufferSize = m_WritePtr = m_RecPlayer->getLengthBytes(); return cVideoBufferTimeshift::Available(); } int cVideoBufferRecording::ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) { // move read pointer if (m_BytesConsumed) { m_ReadPtr += m_BytesConsumed; if (m_ReadPtr >= m_BufferSize) { m_ReadPtr -= m_BufferSize; ERRORLOG("cVideoBufferRecording::ReadBlock - unknown error"); } m_ReadCachePtr += m_BytesConsumed; } m_BytesConsumed = 0; // check if we have anything to read off_t readBytes; if (m_ReadCacheSize && ((m_ReadCachePtr + m_Margin) <= m_ReadCacheSize)) { readBytes = m_ReadCacheSize - m_ReadCachePtr; *buf = m_ReadCache + m_ReadCachePtr; } else if ((readBytes = Available()) >= m_ReadCacheMaxSize) { if (m_ReadPtr + m_ReadCacheMaxSize <= m_BufferSize) { m_ReadCacheSize = m_RecPlayer->getBlock(m_ReadCache, m_ReadPtr, m_ReadCacheMaxSize); if (m_ReadCacheSize < 0) { ERRORLOG("Could not read file, size: %d", m_ReadCacheSize); m_ReadCacheSize = 0; return 0; } readBytes = m_ReadCacheSize; *buf = m_ReadCache; m_ReadCachePtr = 0; } else { ERRORLOG("cVideoBufferRecording::ReadBlock - unknown error"); return 0; } } else return 0; // Make sure we are looking at a TS packet while (readBytes > TS_SIZE) { if ((*buf)[0] == TS_SYNC_BYTE && (*buf)[TS_SIZE] == TS_SYNC_BYTE) break; m_BytesConsumed++; (*buf)++; readBytes--; } if ((*buf)[0] != TS_SYNC_BYTE) { return 0; } m_BytesConsumed += TS_SIZE; time(&endTime); wrapTime = 0; return TS_SIZE; } //----------------------------------------------------------------------------- class cVideoBufferTest : public cVideoBufferFile { friend class cVideoBuffer; public: virtual off_t GetPosMax(); virtual void Put(const uint8_t *buf, unsigned int size); protected: cVideoBufferTest(cString filename); virtual ~cVideoBufferTest(); virtual bool Init(); virtual off_t Available(); off_t GetPosEnd(); }; cVideoBufferTest::cVideoBufferTest(cString filename) { m_Filename = filename; m_Fd = 0; m_ReadCacheSize = 0; } cVideoBufferTest::~cVideoBufferTest() { if (m_Fd) { close(m_Fd); m_Fd = 0; } } off_t cVideoBufferTest::GetPosMax() { m_WritePtr = GetPosEnd(); return cVideoBufferTimeshift::GetPosMax(); } off_t cVideoBufferTest::GetPosEnd() { off_t cur = lseek(m_Fd, 0, SEEK_CUR); off_t end = lseek(m_Fd, 0, SEEK_END); lseek(m_Fd, cur, SEEK_SET); return end; } void cVideoBufferTest::Put(const uint8_t *buf, unsigned int size) { } bool cVideoBufferTest::Init() { m_ReadCache = 0; m_ReadCacheMaxSize = 8000; m_ReadCache = (uint8_t*)malloc(m_ReadCacheMaxSize); if (!m_ReadCache) return false; m_Fd = open(m_Filename, O_RDONLY); if (m_Fd == -1) { ERRORLOG("Could not open file: %s", (const char*)m_Filename); return false; } m_WritePtr = 0; m_ReadPtr = 0; m_ReadCacheSize = 0; m_InputAttached = false; return true; } off_t cVideoBufferTest::Available() { m_BufferSize = m_WritePtr = GetPosEnd(); return cVideoBufferTimeshift::Available(); } //----------------------------------------------------------------------------- cVideoBuffer::cVideoBuffer() { m_CheckEof = false; m_InputAttached = true; m_bufferEndTime = 0; m_bufferWrapTime = 0; } cVideoBuffer::~cVideoBuffer() { } cVideoBuffer* cVideoBuffer::Create(int clientID, uint8_t timeshift) { // no time shift if (TimeshiftMode == 0 || timeshift == 0) { cVideoBufferSimple *buffer = new cVideoBufferSimple(); return buffer; } // buffer in ram else if (TimeshiftMode == 1) { cVideoBufferRAM *buffer = new cVideoBufferRAM(); if (!buffer->Init()) { delete buffer; return NULL; } else return buffer; } // buffer in file else if (TimeshiftMode == 2) { cVideoBufferFile *buffer = new cVideoBufferFile(clientID); if (!buffer->Init()) { delete buffer; return NULL; } else return buffer; } else return NULL; } cVideoBuffer* cVideoBuffer::Create(cString filename) { INFOLOG("Open recording: %s", (const char*)filename); cVideoBufferTest *buffer = new cVideoBufferTest(filename); if (!buffer->Init()) { delete buffer; return NULL; } else return buffer; } cVideoBuffer* cVideoBuffer::Create(const cRecording *rec) { INFOLOG("Open recording: %s", rec->FileName()); cVideoBufferRecording *buffer = new cVideoBufferRecording(rec); if (!buffer->Init()) { delete buffer; return NULL; } else return buffer; } int cVideoBuffer::Read(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) { int count = ReadBlock(buf, size, endTime, wrapTime); // check for end of file if (!m_InputAttached && count != TS_SIZE) { if (m_CheckEof && m_Timer.TimedOut()) { INFOLOG("Recoding - end of file"); return -2; } else if (!m_CheckEof) { m_CheckEof = true; m_Timer.Set(3000); } } else m_CheckEof = false; return count; } void cVideoBuffer::AttachInput(bool attach) { m_InputAttached = attach; } time_t cVideoBuffer::GetRefTime() { time_t t; time(&t); return t; } vdr-plugin-vnsiserver-1.5.2/videobuffer.h000066400000000000000000000036031303643374600204760ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include #include class cRecording; class cVideoBuffer { public: virtual ~cVideoBuffer(); static cVideoBuffer* Create(int clientID, uint8_t timeshift); static cVideoBuffer* Create(cString filename); static cVideoBuffer* Create(const cRecording *rec); virtual void Put(const uint8_t *buf, unsigned int size) = 0; virtual int ReadBlock(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime) = 0; virtual off_t GetPosMin() { return 0; }; virtual off_t GetPosMax() { return 0; }; virtual off_t GetPosCur() { return 0; }; virtual void GetPositions(off_t *cur, off_t *min, off_t *max) {}; virtual void SetPos(off_t pos) {}; virtual void SetCache(bool on) {}; virtual bool HasBuffer() { return false; }; virtual time_t GetRefTime(); int Read(uint8_t **buf, unsigned int size, time_t &endTime, time_t &wrapTime); void AttachInput(bool attach); protected: cVideoBuffer(); cTimeMs m_Timer; bool m_CheckEof; bool m_InputAttached; time_t m_bufferEndTime; time_t m_bufferWrapTime; }; vdr-plugin-vnsiserver-1.5.2/videoinput.c000066400000000000000000000546201303643374600203640ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * * */ #include "config.h" #include "videoinput.h" #include "videobuffer.h" #include "vnsi.h" #include #include #include #include #include #include #include #include #include #include // --- cLiveReceiver ------------------------------------------------- class cLiveReceiver: public cReceiver { public: cLiveReceiver(cVideoInput *VideoInput, const cChannel *Channel, int Priority); virtual ~cLiveReceiver(); protected: virtual void Activate(bool On); #if VDRVERSNUM >= 20301 virtual void Receive(const uchar *Data, int Length); #else virtual void Receive(uchar *Data, int Length); #endif cVideoInput *m_VideoInput; }; cLiveReceiver::cLiveReceiver(cVideoInput *VideoInput, const cChannel *Channel, int Priority) : cReceiver(Channel, Priority) , m_VideoInput(VideoInput) { SetPids(NULL); } cLiveReceiver::~cLiveReceiver() { } //void cLiveReceiver #if VDRVERSNUM >= 20301 void cLiveReceiver::Receive(const uchar *Data, int Length) #else void cLiveReceiver::Receive(uchar *Data, int Length) #endif { m_VideoInput->Receive(Data, Length); } void cLiveReceiver::Activate(bool On) { INFOLOG("activate live receiver: %d", On); if (!On) m_VideoInput->RequestRetune(); } // --- cLivePatFilter ---------------------------------------------------- class cLivePatFilter : public cFilter { private: int m_pmtPid; int m_pmtSid; int m_pmtVersion; const cChannel *m_Channel; cVideoInput *m_VideoInput; virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); public: cLivePatFilter(cVideoInput *VideoInput, const cChannel *Channel); }; cLivePatFilter::cLivePatFilter(cVideoInput *VideoInput, const cChannel *Channel) { DEBUGLOG("cStreamdevPatFilter(\"%s\")", Channel->Name()); m_Channel = Channel; m_VideoInput = VideoInput; m_pmtPid = 0; m_pmtSid = 0; m_pmtVersion = -1; Set(0x00, 0x00); // PAT } void cLivePatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) { #if VDRVERSNUM < 20104 if (Pid == 0x00) { if (Tid == 0x00) { SI::PAT pat(Data, false); if (!pat.CheckCRCAndParse()) return; SI::PAT::Association assoc; for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) { if (!assoc.isNITPid()) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; const cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), assoc.getServiceId()); #else const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId()); #endif if (Channel && (Channel == m_Channel)) { int prevPmtPid = m_pmtPid; if (0 != (m_pmtPid = assoc.getPid())) { if (m_pmtPid != prevPmtPid) { m_pmtSid = assoc.getServiceId(); Add(m_pmtPid, 0x02); m_pmtVersion = -1; break; } return; } } } } } } else if (Pid == m_pmtPid && Tid == SI::TableIdPMT && Source() && Transponder()) { SI::PMT pmt(Data, false); if (!pmt.CheckCRCAndParse()) return; if (pmt.getServiceId() != m_pmtSid) return; // skip broken PMT records if (m_pmtVersion != -1) { if (m_pmtVersion != pmt.getVersionNumber()) { cFilter::Del(m_pmtPid, 0x02); m_pmtPid = 0; // this triggers PAT scan } return; } m_pmtVersion = pmt.getVersionNumber(); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; const cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId()); #else cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId()); #endif if (Channel) { // Scan the stream-specific loop: SI::PMT::Stream stream; int Vpid = 0; int Ppid = 0; int Vtype = 0; int Apids[MAXAPIDS + 1] = { 0 }; // these lists are zero-terminated int Atypes[MAXAPIDS + 1] = { 0 }; int Dpids[MAXDPIDS + 1] = { 0 }; int Dtypes[MAXDPIDS + 1] = { 0 }; int Spids[MAXSPIDS + 1] = { 0 }; uchar SubtitlingTypes[MAXSPIDS + 1] = { 0 }; uint16_t CompositionPageIds[MAXSPIDS + 1] = { 0 }; uint16_t AncillaryPageIds[MAXSPIDS + 1] = { 0 }; char ALangs[MAXAPIDS][MAXLANGCODE2] = { "" }; char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" }; int Tpid = 0; int NumApids = 0; int NumDpids = 0; int NumSpids = 0; for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) { bool ProcessCaDescriptors = false; int esPid = stream.getPid(); switch (stream.getStreamType()) { case 1: // STREAMTYPE_11172_VIDEO case 2: // STREAMTYPE_13818_VIDEO case 0x1B: // MPEG4 Vpid = esPid; Ppid = pmt.getPCRPid(); Vtype = stream.getStreamType(); ProcessCaDescriptors = true; break; case 3: // STREAMTYPE_11172_AUDIO case 4: // STREAMTYPE_13818_AUDIO case 0x0F: // ISO/IEC 13818-7 Audio with ADTS transport syntax case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax { if (NumApids < MAXAPIDS) { Apids[NumApids] = esPid; Atypes[NumApids] = stream.getStreamType(); SI::Descriptor *d; for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { switch (d->getDescriptorTag()) { case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; SI::ISO639LanguageDescriptor::Language l; char *s = ALangs[NumApids]; int n = 0; for (SI::Loop::Iterator it; ld->languageLoop.getNext(l, it); ) { if (*ld->languageCode != '-') { // some use "---" to indicate "none" if (n > 0) *s++ = '+'; strn0cpy(s, I18nNormalizeLanguageCode(l.languageCode), MAXLANGCODE1); s += strlen(s); if (n++ > 1) break; } } } break; default: ; } delete d; } NumApids++; } ProcessCaDescriptors = true; } break; case 5: // STREAMTYPE_13818_PRIVATE case 6: // STREAMTYPE_13818_PES_PRIVATE //XXX case 8: // STREAMTYPE_13818_DSMCC { int dpid = 0; int dtype = 0; char lang[MAXLANGCODE1] = { 0 }; SI::Descriptor *d; for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { switch (d->getDescriptorTag()) { case SI::AC3DescriptorTag: case SI::EnhancedAC3DescriptorTag: dpid = esPid; dtype = d->getDescriptorTag(); ProcessCaDescriptors = true; break; case SI::SubtitlingDescriptorTag: if (NumSpids < MAXSPIDS) { Spids[NumSpids] = esPid; SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d; SI::SubtitlingDescriptor::Subtitling sub; char *s = SLangs[NumSpids]; int n = 0; for (SI::Loop::Iterator it; sd->subtitlingLoop.getNext(sub, it); ) { if (sub.languageCode[0]) { SubtitlingTypes[NumSpids] = sub.getSubtitlingType(); CompositionPageIds[NumSpids] = sub.getCompositionPageId(); AncillaryPageIds[NumSpids] = sub.getAncillaryPageId(); if (n > 0) *s++ = '+'; strn0cpy(s, I18nNormalizeLanguageCode(sub.languageCode), MAXLANGCODE1); s += strlen(s); if (n++ > 1) break; } } NumSpids++; } break; case SI::TeletextDescriptorTag: Tpid = esPid; break; case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); } break; default: ; } delete d; } if (dpid) { if (NumDpids < MAXDPIDS) { Dpids[NumDpids] = dpid; Dtypes[NumDpids] = dtype; strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); NumDpids++; } } } break; case 0x80: // STREAMTYPE_USER_PRIVATE #if APIVERSNUM >= 10728 if (Setup.StandardCompliance == STANDARD_ANSISCTE) #endif { // DigiCipher II VIDEO (ANSI/SCTE 57) Vpid = esPid; Ppid = pmt.getPCRPid(); Vtype = 0x02; // compression based upon MPEG-2 ProcessCaDescriptors = true; break; } // fall through case 0x81: // STREAMTYPE_USER_PRIVATE #if APIVERSNUM >= 10728 if (Setup.StandardCompliance == STANDARD_ANSISCTE) #endif { // ATSC A/53 AUDIO (ANSI/SCTE 57) char lang[MAXLANGCODE1] = { 0 }; SI::Descriptor *d; for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { switch (d->getDescriptorTag()) { case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); } break; default: ; } delete d; } if (NumDpids < MAXDPIDS) { Dpids[NumDpids] = esPid; Dtypes[NumDpids] = SI::AC3DescriptorTag; strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); NumDpids++; } ProcessCaDescriptors = true; break; } // fall through case 0x82: // STREAMTYPE_USER_PRIVATE #if APIVERSNUM >= 10728 if (Setup.StandardCompliance == STANDARD_ANSISCTE) #endif { // STANDARD SUBTITLE (ANSI/SCTE 27) //TODO break; } // fall through case 0x83 ... 0xFF: // STREAMTYPE_USER_PRIVATE { char lang[MAXLANGCODE1] = { 0 }; bool IsAc3 = false; SI::Descriptor *d; for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { switch (d->getDescriptorTag()) { case SI::RegistrationDescriptorTag: { SI::RegistrationDescriptor *rd = (SI::RegistrationDescriptor *)d; // http://www.smpte-ra.org/mpegreg/mpegreg.html switch (rd->getFormatIdentifier()) { case 0x41432D33: // 'AC-3' IsAc3 = true; break; default: //printf("Format identifier: 0x%08X (pid: %d)\n", rd->getFormatIdentifier(), esPid); break; } } break; case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); } break; default: ; } delete d; } if (IsAc3) { if (NumDpids < MAXDPIDS) { Dpids[NumDpids] = esPid; Dtypes[NumDpids] = SI::AC3DescriptorTag; strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); NumDpids++; } ProcessCaDescriptors = true; } } break; default: ;//printf("PID: %5d %5d %2d %3d %3d\n", pmt.getServiceId(), stream.getPid(), stream.getStreamType(), pmt.getVersionNumber(), Channel->Number()); } } DEBUGLOG("Pat/Pmt Filter received pmt change"); cChannel *pmtChannel = m_VideoInput->PmtChannel(); pmtChannel->Modification(); pmtChannel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); pmtChannel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); if (pmtChannel->Modification(CHANNELMOD_PIDS)) m_VideoInput->RequestRetune(); } } #endif } // --- cDummyReceiver ---------------------------------------------------- // This dummy receiver is used to detect if a recording/streaming task // with a higher priority has acquired the device and detached *all* // receivers from it. class cDummyReceiver : public cReceiver { public: // Return a new or existing dummy receiver attached to the device. static std::shared_ptr Create(cDevice *device); virtual ~cDummyReceiver() {Detach();} bool BeenDetached() {return m_BeenDetached;} protected: #if VDRVERSNUM >= 20301 virtual void Receive(const uchar *Data, int Length) {} #else virtual void Receive(uchar *Data, int Length) {} #endif virtual void Activate(bool On); private: static std::vector> s_Pool; static cMutex s_PoolMutex; std::atomic m_BeenDetached; cDummyReceiver() : cReceiver(NULL, MINPRIORITY), m_BeenDetached(false) {} }; std::vector> cDummyReceiver::s_Pool; cMutex cDummyReceiver::s_PoolMutex; void cDummyReceiver::Activate(bool On) { INFOLOG("Dummy receiver (%p) %s", this, (On)? "activated" : "deactivated"); if (!On) m_BeenDetached = true; } std::shared_ptr cDummyReceiver::Create(cDevice *device) { if (!device) return nullptr; cMutexLock MutexLock(&s_PoolMutex); // cleanup s_Pool.erase(std::remove_if(s_Pool.begin(), s_Pool.end(), [](const std::weak_ptr &p) -> bool {return !p.lock();}), s_Pool.end()); // find an active receiver for the device for (auto p : s_Pool) { auto recv = p.lock(); if (!recv->BeenDetached() && recv->Device() == device) return recv; } auto recv = std::shared_ptr(new cDummyReceiver); if (device->AttachReceiver(recv.get())) { s_Pool.push_back(recv); return recv; } return nullptr; } // ---------------------------------------------------------------------------- cVideoInput::cVideoInput(cCondWait &event) : m_Event(event) , m_RetuneRequested(false) { m_Device = NULL; m_camSlot = nullptr; m_PatFilter = NULL; m_Receiver = NULL; m_Channel = NULL; m_VideoBuffer = NULL; m_Priority = 0; m_DataSeen = false; } cVideoInput::~cVideoInput() { Close(); } bool cVideoInput::Open(const cChannel *channel, int priority, cVideoBuffer *videoBuffer) { m_VideoBuffer = videoBuffer; m_Channel = channel; m_Priority = priority; m_RetuneRequested = false; m_DataSeen = false; m_Device = cDevice::GetDevice(m_Channel, m_Priority, false); m_camSlot = nullptr; if (m_Device != NULL) { INFOLOG("Successfully found following device: %p (%d) for receiving, priority=%d", m_Device, m_Device ? m_Device->CardIndex() + 1 : 0, m_Priority); if (m_Device->SwitchChannel(m_Channel, false)) { m_Device->SetCurrentChannel(m_Channel); #if VDRVERSNUM < 20104 m_PatFilter = new cLivePatFilter(this, m_Channel); m_Device->AttachFilter(m_PatFilter); #endif m_PmtChannel = *m_Channel; m_Receiver = new cLiveReceiver(this, m_Channel, m_Priority); m_Receiver->SetPids(NULL); m_Receiver->SetPids(&m_PmtChannel); m_Receiver->AddPid(m_PmtChannel.Tpid()); m_DummyReceiver = cDummyReceiver::Create(m_Device); if (!m_DummyReceiver) return false; m_camSlot = m_Device->CamSlot(); #if VDRVERSNUM >= 20107 if (DisableScrambleTimeout && m_camSlot) { // HACK cDevice::AttachReceiver() doesn't start scrambling // timer if priority == MINPRIORITY m_Receiver->SetPriority(MINPRIORITY); if (!m_Device->AttachReceiver(m_Receiver)) return false; if (m_camSlot) m_camSlot->StartDecrypting(); m_Receiver->SetPriority(m_Priority); } else #endif { if (!m_Device->AttachReceiver(m_Receiver)) return false; } m_VideoBuffer->AttachInput(true); return true; } } return false; } void cVideoInput::Close() { INFOLOG("close video input ..."); if (m_Device) { if (DisableCamBlacklist) { // HACK Undo ChannelCamRelations.SetChecked() - see cDevice::Action(). // Note: m_Device->CamSlot() returns NULL after SetChecked() is called. // Use m_camSlot here. if (m_Receiver && m_camSlot) { ChannelCamRelations.ClrChecked(m_Receiver->ChannelID(), m_camSlot->SlotNumber()); } } if (m_Receiver) { DEBUGLOG("Detaching Live Receiver"); m_Device->Detach(m_Receiver); } else { DEBUGLOG("No live receiver present"); } if (m_PatFilter) { DEBUGLOG("Detaching Live Filter"); m_Device->Detach(m_PatFilter); } else { DEBUGLOG("No live filter present"); } m_DummyReceiver.reset(); if (m_Receiver) { DEBUGLOG("Deleting Live Receiver"); DELETENULL(m_Receiver); } if (m_PatFilter) { DEBUGLOG("Deleting Live Filter"); DELETENULL(m_PatFilter); } } m_Channel = NULL; m_Device = NULL; if (m_VideoBuffer) { m_VideoBuffer->AttachInput(false); m_VideoBuffer = NULL; } } bool cVideoInput::IsOpen() { if (m_Channel) return true; else return false; } cChannel *cVideoInput::PmtChannel() { return &m_PmtChannel; } inline void cVideoInput::Receive(const uchar *data, int length) { if (!m_DataSeen) { // generate pat/pmt so we can configure parsers later cPatPmtGenerator patPmtGenerator(&m_PmtChannel); m_VideoBuffer->Put(patPmtGenerator.GetPat(), TS_SIZE); int Index = 0; while (uchar *pmt = patPmtGenerator.GetPmt(Index)) m_VideoBuffer->Put(pmt, TS_SIZE); m_DataSeen = true; } m_VideoBuffer->Put(data, length); } void cVideoInput::RequestRetune() { m_RetuneRequested = true; m_Event.Signal(); } cVideoInput::eReceivingStatus cVideoInput::ReceivingStatus() { if (!m_Device || !m_DummyReceiver) return RETUNE; if (m_RetuneRequested) { (void)m_Device->Receiving(); // wait for the receivers mutex if (m_DummyReceiver->BeenDetached()) // DetachAllReceivers() was called return CLOSE; else return RETUNE; } return NORMAL; } vdr-plugin-vnsiserver-1.5.2/videoinput.h000066400000000000000000000035421303643374600203660ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include #include #include class cLivePatFilter; class cLiveReceiver; class cVideoBuffer; class cDevice; class cDummyReceiver; class cCamSlot; class cVideoInput { friend class cLivePatFilter; friend class cLiveReceiver; public: cVideoInput(cCondWait &event); virtual ~cVideoInput(); bool Open(const cChannel *channel, int priority, cVideoBuffer *videoBuffer); void Close(); bool IsOpen(); void RequestRetune(); enum eReceivingStatus {NORMAL, RETUNE, CLOSE}; eReceivingStatus ReceivingStatus(); protected: cChannel *PmtChannel(); void Receive(const uchar *data, int length); cDevice *m_Device; cCamSlot *m_camSlot; cLivePatFilter *m_PatFilter; cLiveReceiver *m_Receiver; const cChannel *m_Channel; cVideoBuffer *m_VideoBuffer; int m_Priority; bool m_DataSeen; cChannel m_PmtChannel; cCondWait &m_Event; std::shared_ptr m_DummyReceiver; std::atomic m_RetuneRequested; }; vdr-plugin-vnsiserver-1.5.2/vnsi.c000066400000000000000000000154671303643374600171630ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "vnsi.h" #include "vnsicommand.h" #include "setup.h" #include #include cPluginVNSIServer* cPluginVNSIServer::VNSIServer = NULL; cPluginVNSIServer::cPluginVNSIServer(void) { Server = NULL; VNSIServer = NULL; } const char *cPluginVNSIServer::CommandLineHelp(void) { return " -t n, --timeout=n stream data timeout in seconds (default: 10)\n" " -d , --device act as the primary device\n" " -s n, --test=n TS stream test file to simulate as channel\n" " -p n, --port=n tcp port to listen on\n"; } bool cPluginVNSIServer::ProcessArgs(int argc, char *argv[]) { // Implement command line argument processing here if applicable. static struct option long_options[] = { { "port", required_argument, NULL, 'p' }, { "timeout", required_argument, NULL, 't' }, { "device", no_argument, NULL, 'd' }, { "test", required_argument, NULL, 'T' }, { NULL, no_argument, NULL, 0 } }; int c; while ((c = getopt_long(argc, argv, "t:dT:p:", long_options, NULL)) != -1) { switch (c) { case 'p': if(optarg != NULL) VNSIServerConfig.listen_port = atoi(optarg); break; case 't': if(optarg != NULL) VNSIServerConfig.stream_timeout = atoi(optarg); break; case 'd': VNSIServerConfig.device = true; break; case 'T': if(optarg != NULL) { VNSIServerConfig.testStreamFile = optarg; struct stat file_stat; if (stat(VNSIServerConfig.testStreamFile, &file_stat) == 0) { VNSIServerConfig.testStreamActive = true; printf("vnsiserver: requested test stream file '%s' played now on all channels\n", *VNSIServerConfig.testStreamFile); } else printf("vnsiserver: requested test stream file '%s' not present, started without\n", *VNSIServerConfig.testStreamFile); } break; default: return false; } } return true; } bool cPluginVNSIServer::Initialize(void) { // Initialize any background activities the plugin shall perform. VNSIServerConfig.ConfigDirectory = ConfigDirectory(PLUGIN_NAME_I18N); VNSIServer = this; return true; } bool cPluginVNSIServer::Start(void) { INFOLOG("Starting vnsi server at port=%d\n", VNSIServerConfig.listen_port); Server = new cVNSIServer(VNSIServerConfig.listen_port); return true; } void cPluginVNSIServer::Stop(void) { delete Server; Server = NULL; } void cPluginVNSIServer::Housekeeping(void) { // Perform any cleanup or other regular tasks. } void cPluginVNSIServer::MainThreadHook(void) { // Perform actions in the context of the main program thread. // WARNING: Use with great care - see PLUGINS.html! } cString cPluginVNSIServer::Active(void) { // Return a message string if shutdown should be postponed return NULL; } time_t cPluginVNSIServer::WakeupTime(void) { // Return custom wakeup time for shutdown script return 0; } cMenuSetupPage *cPluginVNSIServer::SetupMenu(void) { // Return a setup menu in case the plugin supports one. return new cMenuSetupVNSI; } bool cPluginVNSIServer::SetupParse(const char *Name, const char *Value) { // Parse your own setup parameters and store their values. if (!strcasecmp(Name, CONFNAME_PMTTIMEOUT)) PmtTimeout = atoi(Value); else if (!strcasecmp(Name, CONFNAME_TIMESHIFT)) TimeshiftMode = atoi(Value); else if (!strcasecmp(Name, CONFNAME_TIMESHIFTBUFFERSIZE)) TimeshiftBufferSize = atoi(Value); else if (!strcasecmp(Name, CONFNAME_TIMESHIFTBUFFERFILESIZE)) TimeshiftBufferFileSize = atoi(Value); else if (!strcasecmp(Name, CONFNAME_TIMESHIFTBUFFERDIR)) { strn0cpy(TimeshiftBufferDir, Value, sizeof(TimeshiftBufferDir)); if (*TimeshiftBufferDir && TimeshiftBufferDir[strlen(TimeshiftBufferDir)-1] == '/') /* strip trailing slash */ TimeshiftBufferDir[strlen(TimeshiftBufferDir)-1] = 0; } else if (!strcasecmp(Name, CONFNAME_PLAYRECORDING)) PlayRecording = atoi(Value); else if (!strcasecmp(Name, CONFNAME_AVOIDEPGSCAN)) AvoidEPGScan = atoi(Value); else if (!strcasecmp(Name, CONFNAME_DISABLESCRAMBLETIMEOUT)) DisableScrambleTimeout = atoi(Value); else if (!strcasecmp(Name, CONFNAME_DISABLECAMBLACKLIST)) DisableCamBlacklist = atoi(Value); else return false; return true; } bool cPluginVNSIServer::Service(const char *Id, void *Data) { // Handle custom service requests from other plugins return false; } const char **cPluginVNSIServer::SVDRPHelpPages(void) { // Return help text for SVDRP commands this plugin implements return NULL; } cString cPluginVNSIServer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { // Process SVDRP commands this plugin implements return NULL; } void cPluginVNSIServer::StoreSetup(const char *Name, int Value) { if (VNSIServer) { if (VNSIServer->SetupParse(Name, itoa(Value))) { VNSIServer->SetupStore(Name, Value); Setup.Save(); } } } bool cDvbVsniDeviceProbe::Probe(int Adapter, int Frontend) { if (VNSIServerConfig.device) { new cDvbVnsiDevice(Adapter, Frontend); return true; } else return false; } cDvbVnsiDevice::cDvbVnsiDevice(int Adapter, int Frontend) :cDvbDevice(Adapter, Frontend) { VNSIServerConfig.pDevice = this; m_hasDecoder = false; } cDvbVnsiDevice::~cDvbVnsiDevice() { } bool cDvbVnsiDevice::HasDecoder(void) const { cMutexLock lock((cMutex*)&m_mutex); return m_hasDecoder; } int cDvbVnsiDevice::PlayVideo(const uchar *Data, int Length) { return Length; } int cDvbVnsiDevice::PlayAudio(const uchar *Data, int Length, uchar Id) { return Length; } void cDvbVnsiDevice::ActivateDecoder(bool active) { cMutexLock lock(&m_mutex); m_hasDecoder = active; } VDRPLUGINCREATOR(cPluginVNSIServer); // Don't touch this! vdr-plugin-vnsiserver-1.5.2/vnsi.h000066400000000000000000000056331303643374600171620ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include #include #include #include "vnsiserver.h" static const char *VERSION = "1.5.2"; static const char *DESCRIPTION = "VDR-Network-Streaming-Interface (VNSI) Server"; extern int PmtTimeout; extern int TimeshiftMode; extern int TimeshiftBufferSize; extern int TimeshiftBufferFileSize; extern char TimeshiftBufferDir[PATH_MAX]; extern int PlayRecording; extern int AvoidEPGScan; extern int DisableScrambleTimeout; extern int DisableCamBlacklist; class cDvbVsniDeviceProbe : public cDvbDeviceProbe { public: virtual bool Probe(int Adapter, int Frontend); }; class cPluginVNSIServer : public cPlugin { private: cVNSIServer *Server; static cPluginVNSIServer *VNSIServer; cDvbVsniDeviceProbe probe; public: cPluginVNSIServer(void); virtual const char *Version(void) { return VERSION; } virtual const char *Description(void) { return DESCRIPTION; } virtual const char *CommandLineHelp(void); virtual bool ProcessArgs(int argc, char *argv[]); virtual bool Initialize(void); virtual bool Start(void); virtual void Stop(void); virtual void Housekeeping(void); virtual void MainThreadHook(void); virtual cString Active(void); virtual time_t WakeupTime(void); virtual const char *MainMenuEntry(void) { return NULL; } virtual cOsdObject *MainMenuAction(void) { return NULL; } virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data = NULL); virtual const char **SVDRPHelpPages(void); virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); static void StoreSetup(const char *Name, int Value); }; class cDvbVnsiDevice : public cDvbDevice { public: cDvbVnsiDevice(int Adapter, int Frontend); virtual ~cDvbVnsiDevice(); virtual bool HasDecoder(void) const; int PlayVideo(const uchar *Data, int Length); int PlayAudio(const uchar *Data, int Length, uchar Id); void ActivateDecoder(bool active); protected: bool m_hasDecoder; cMutex m_mutex; }; vdr-plugin-vnsiserver-1.5.2/vnsiclient.c000066400000000000000000002367721303643374600203660ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "vnsiclient.h" #include "vnsi.h" #include "config.h" #include "vnsicommand.h" #include "recordingscache.h" #include "streamer.h" #include "vnsiserver.h" #include "recplayer.h" #include "vnsiosd.h" #include "requestpacket.h" #include "responsepacket.h" #include "hash.h" #include "channelfilter.h" #include "channelscancontrol.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include cMutex cVNSIClient::m_timerLock; bool cVNSIClient::m_inhibidDataUpdates = false; cVNSIClient::cVNSIClient(int fd, unsigned int id, const char *ClientAdr, CVNSITimers &timers) : m_Id(id), m_socket(fd), m_ClientAddress(ClientAdr), m_ChannelScanControl(this), m_vnsiTimers(timers) { SetDescription("VNSI Client %u->%s", id, ClientAdr); Start(); } cVNSIClient::~cVNSIClient() { DEBUGLOG("%s", __FUNCTION__); StopChannelStreaming(); m_ChannelScanControl.StopScan(); m_socket.Shutdown(); Cancel(10); DEBUGLOG("done"); } void cVNSIClient::Action(void) { uint32_t channelID; uint32_t requestID; uint32_t opcode; uint32_t dataLength; uint8_t* data; while (Running()) { if (!m_socket.read((uint8_t*)&channelID, sizeof(uint32_t))) break; channelID = ntohl(channelID); if (channelID == 1) { if (!m_socket.read((uint8_t*)&requestID, sizeof(uint32_t), 10000)) break; requestID = ntohl(requestID); if (!m_socket.read((uint8_t*)&opcode, sizeof(uint32_t), 10000)) break; opcode = ntohl(opcode); if (!m_socket.read((uint8_t*)&dataLength, sizeof(uint32_t), 10000)) break; dataLength = ntohl(dataLength); if (dataLength > 200000) // a random sanity limit { ERRORLOG("dataLength > 200000!"); break; } if (dataLength) { try { data = new uint8_t[dataLength]; } catch (const std::bad_alloc &) { ERRORLOG("Extra data buffer malloc error"); break; } if (!m_socket.read(data, dataLength, 10000)) { ERRORLOG("Could not read data"); free(data); break; } } else { data = NULL; } DEBUGLOG("Received chan=%u, ser=%u, op=%u, edl=%u", channelID, requestID, opcode, dataLength); if (!m_loggedIn && (opcode != VNSI_LOGIN)) { ERRORLOG("Clients must be logged in before sending commands! Aborting."); if (data) free(data); break; } try { cRequestPacket req(requestID, opcode, data, dataLength); processRequest(req); } catch (const std::exception &e) { ERRORLOG("%s", e.what()); break; } } else { ERRORLOG("Incoming channel number unknown"); break; } } /* If thread is ended due to closed connection delete a possible running stream here */ StopChannelStreaming(); m_ChannelScanControl.StopScan(); // Shutdown OSD delete m_Osd; m_Osd = NULL; } bool cVNSIClient::StartChannelStreaming(cResponsePacket &resp, const cChannel *channel, int32_t priority, uint8_t timeshift, uint32_t timeout) { delete m_Streamer; m_Streamer = new cLiveStreamer(m_Id, m_bSupportRDS, timeshift, timeout); m_isStreaming = m_Streamer->StreamChannel(channel, priority, &m_socket, &resp); return m_isStreaming; } void cVNSIClient::StopChannelStreaming() { m_isStreaming = false; delete m_Streamer; m_Streamer = NULL; } void cVNSIClient::SignalTimerChange() { cMutexLock lock(&m_msgLock); if (m_StatusInterfaceEnabled) { cResponsePacket resp; resp.initStatus(VNSI_STATUS_TIMERCHANGE); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } } void cVNSIClient::ChannelsChange() { cMutexLock lock(&m_msgLock); if (!m_StatusInterfaceEnabled) return; cResponsePacket resp; resp.initStatus(VNSI_STATUS_CHANNELCHANGE); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::RecordingsChange() { cMutexLock lock(&m_msgLock); if (!m_StatusInterfaceEnabled) return; cResponsePacket resp; resp.initStatus(VNSI_STATUS_RECORDINGSCHANGE); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } int cVNSIClient::EpgChange() { int callAgain = 0; cMutexLock lock(&m_msgLock); if (!m_StatusInterfaceEnabled) return callAgain; #if VDRVERSNUM >= 20301 cStateKey SchedulesStateKey(true); const cSchedules *schedules = cSchedules::GetSchedulesRead(SchedulesStateKey); if (!schedules) { return callAgain; } #else cSchedulesLock MutexLock; const cSchedules *schedules = cSchedules::Schedules(MutexLock); if (!schedules) return callAgain; #endif for (const cSchedule *schedule = schedules->First(); schedule; schedule = schedules->Next(schedule)) { const cEvent *lastEvent = schedule->Events()->Last(); if (!lastEvent) continue; #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByChannelID(schedule->ChannelID()); #else Channels.Lock(false); const cChannel *channel = Channels.GetByChannelID(schedule->ChannelID()); Channels.Unlock(); #endif if (!channel) continue; if (!VNSIChannelFilter.PassFilter(*channel)) continue; uint32_t channelId = CreateStringHash(schedule->ChannelID().ToString()); auto it = m_epgUpdate.find(channelId); if (it == m_epgUpdate.end() || it->second.attempts > 3 || it->second.lastEvent >= lastEvent->StartTime()) { continue; } time_t now = time(nullptr); if ((now - it->second.lastTrigger) < 5) { callAgain = VNSI_EPG_PAUSE; continue; } it->second.attempts++; it->second.lastTrigger = now; DEBUGLOG("Trigger EPG update for channel %s, id: %d", channel->Name(), channelId); cResponsePacket resp; resp.initStatus(VNSI_STATUS_EPGCHANGE); resp.add_U32(channelId); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); callAgain = VNSI_EPG_AGAIN; break; } #if VDRVERSNUM >= 20301 SchedulesStateKey.Remove(); #endif return callAgain; } void cVNSIClient::Recording(const cDevice *Device, const char *Name, const char *FileName, bool On) { cMutexLock lock(&m_msgLock); if (m_StatusInterfaceEnabled) { cResponsePacket resp; resp.initStatus(VNSI_STATUS_RECORDING); resp.add_U32(Device->CardIndex()); resp.add_U32(On); if (Name) resp.add_String(Name); else resp.add_String(""); if (FileName) resp.add_String(FileName); else resp.add_String(""); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } } void cVNSIClient::OsdStatusMessage(const char *Message) { cMutexLock lock(&m_msgLock); if (m_StatusInterfaceEnabled && Message) { /* Ignore this messages */ if (strcasecmp(Message, trVDR("Channel not available!")) == 0) return; else if (strcasecmp(Message, trVDR("Delete timer?")) == 0) return; else if (strcasecmp(Message, trVDR("Delete recording?")) == 0) return; else if (strcasecmp(Message, trVDR("Press any key to cancel shutdown")) == 0) return; else if (strcasecmp(Message, trVDR("Press any key to cancel restart")) == 0) return; else if (strcasecmp(Message, trVDR("Editing - shut down anyway?")) == 0) return; else if (strcasecmp(Message, trVDR("Recording - shut down anyway?")) == 0) return; else if (strcasecmp(Message, trVDR("shut down anyway?")) == 0) return; else if (strcasecmp(Message, trVDR("Recording - restart anyway?")) == 0) return; else if (strcasecmp(Message, trVDR("Editing - restart anyway?")) == 0) return; else if (strcasecmp(Message, trVDR("Delete channel?")) == 0) return; else if (strcasecmp(Message, trVDR("Timer still recording - really delete?")) == 0) return; else if (strcasecmp(Message, trVDR("Delete marks information?")) == 0) return; else if (strcasecmp(Message, trVDR("Delete resume information?")) == 0) return; else if (strcasecmp(Message, trVDR("CAM is in use - really reset?")) == 0) return; else if (strcasecmp(Message, trVDR("CAM activated!")) == 0) return; else if (strcasecmp(Message, trVDR("Really restart?")) == 0) return; else if (strcasecmp(Message, trVDR("Stop recording?")) == 0) return; else if (strcasecmp(Message, trVDR("Cancel editing?")) == 0) return; else if (strcasecmp(Message, trVDR("Cutter already running - Add to cutting queue?")) == 0) return; else if (strcasecmp(Message, trVDR("No index-file found. Creating may take minutes. Create one?")) == 0) return; else if (strncmp(Message, trVDR("VDR will shut down in"), 21) == 0) return; cResponsePacket resp; resp.initStatus(VNSI_STATUS_MESSAGE); resp.add_U32(0); resp.add_String(Message); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } } #if VDRVERSNUM >= 20104 void cVNSIClient::ChannelChange(const cChannel *Channel) { cMutexLock lock(&m_msgLock); if (m_isStreaming && m_Streamer) { m_Streamer->RetuneChannel(Channel); } } #endif bool cVNSIClient::processRequest(cRequestPacket &req) { cMutexLock lock(&m_msgLock); bool result = false; switch(req.getOpCode()) { /** OPCODE 1 - 19: VNSI network functions for general purpose */ case VNSI_LOGIN: result = process_Login(req); break; case VNSI_GETTIME: result = process_GetTime(req); break; case VNSI_ENABLESTATUSINTERFACE: result = process_EnableStatusInterface(req); break; case VNSI_PING: result = process_Ping(req); break; case VNSI_GETSETUP: result = process_GetSetup(req); break; case VNSI_STORESETUP: result = process_StoreSetup(req); break; /** OPCODE 20 - 39: VNSI network functions for live streaming */ case VNSI_CHANNELSTREAM_OPEN: result = processChannelStream_Open(req); break; case VNSI_CHANNELSTREAM_CLOSE: result = processChannelStream_Close(req); break; case VNSI_CHANNELSTREAM_SEEK: result = processChannelStream_Seek(req); break; /** OPCODE 40 - 59: VNSI network functions for recording streaming */ case VNSI_RECSTREAM_OPEN: result = processRecStream_Open(req); break; case VNSI_RECSTREAM_CLOSE: result = processRecStream_Close(req); break; case VNSI_RECSTREAM_GETBLOCK: result = processRecStream_GetBlock(req); break; case VNSI_RECSTREAM_POSTOFRAME: result = processRecStream_PositionFromFrameNumber(req); break; case VNSI_RECSTREAM_FRAMETOPOS: result = processRecStream_FrameNumberFromPosition(req); break; case VNSI_RECSTREAM_GETIFRAME: result = processRecStream_GetIFrame(req); break; case VNSI_RECSTREAM_GETLENGTH: result = processRecStream_GetLength(req); break; /** OPCODE 60 - 79: VNSI network functions for channel access */ case VNSI_CHANNELS_GETCOUNT: result = processCHANNELS_ChannelsCount(req); break; case VNSI_CHANNELS_GETCHANNELS: result = processCHANNELS_GetChannels(req); break; case VNSI_CHANNELGROUP_GETCOUNT: result = processCHANNELS_GroupsCount(req); break; case VNSI_CHANNELGROUP_LIST: result = processCHANNELS_GroupList(req); break; case VNSI_CHANNELGROUP_MEMBERS: result = processCHANNELS_GetGroupMembers(req); break; case VNSI_CHANNELS_GETCAIDS: result = processCHANNELS_GetCaids(req); break; case VNSI_CHANNELS_GETWHITELIST: result = processCHANNELS_GetWhitelist(req); break; case VNSI_CHANNELS_GETBLACKLIST: result = processCHANNELS_GetBlacklist(req); break; case VNSI_CHANNELS_SETWHITELIST: result = processCHANNELS_SetWhitelist(req); break; case VNSI_CHANNELS_SETBLACKLIST: result = processCHANNELS_SetBlacklist(req); break; /** OPCODE 80 - 99: VNSI network functions for timer access */ case VNSI_TIMER_GETCOUNT: result = processTIMER_GetCount(req); break; case VNSI_TIMER_GET: result = processTIMER_Get(req); break; case VNSI_TIMER_GETLIST: result = processTIMER_GetList(req); break; case VNSI_TIMER_ADD: result = processTIMER_Add(req); break; case VNSI_TIMER_DELETE: result = processTIMER_Delete(req); break; case VNSI_TIMER_UPDATE: result = processTIMER_Update(req); break; case VNSI_TIMER_GETTYPES: result = processTIMER_GetTypes(req); break; /** OPCODE 100 - 119: VNSI network functions for recording access */ case VNSI_RECORDINGS_DISKSIZE: result = processRECORDINGS_GetDiskSpace(req); break; case VNSI_RECORDINGS_GETCOUNT: result = processRECORDINGS_GetCount(req); break; case VNSI_RECORDINGS_GETLIST: result = processRECORDINGS_GetList(req); break; case VNSI_RECORDINGS_RENAME: result = processRECORDINGS_Rename(req); break; case VNSI_RECORDINGS_DELETE: result = processRECORDINGS_Delete(req); break; case VNSI_RECORDINGS_GETEDL: result = processRECORDINGS_GetEdl(req); break; /** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ case VNSI_EPG_GETFORCHANNEL: result = processEPG_GetForChannel(req); break; /** OPCODE 140 - 159: VNSI network functions for channel scanning */ case VNSI_SCAN_SUPPORTED: result = processSCAN_ScanSupported(req); break; case VNSI_SCAN_GETCOUNTRIES: result = processSCAN_GetCountries(req); break; case VNSI_SCAN_GETSATELLITES: result = processSCAN_GetSatellites(req); break; case VNSI_SCAN_START: result = processSCAN_Start(req); break; case VNSI_SCAN_STOP: result = processSCAN_Stop(req); break; case VNSI_SCAN_SUPPORTED_TYPES: result = processSCAN_GetSupportedTypes(req); break; /** OPCODE 160 - 179: VNSI network functions for OSD */ case VNSI_OSD_CONNECT: result = processOSD_Connect(req); break; case VNSI_OSD_DISCONNECT: result = processOSD_Disconnect(); break; case VNSI_OSD_HITKEY: result = processOSD_Hitkey(req); break; /** OPCODE 180 - 189: VNSI network functions for deleted recording access */ case VNSI_RECORDINGS_DELETED_ACCESS_SUPPORTED: result = processRECORDINGS_DELETED_Supported(req); break; case VNSI_RECORDINGS_DELETED_GETCOUNT: result = processRECORDINGS_DELETED_GetCount(req); break; case VNSI_RECORDINGS_DELETED_GETLIST: result = processRECORDINGS_DELETED_GetList(req); break; case VNSI_RECORDINGS_DELETED_DELETE: result = processRECORDINGS_DELETED_Delete(req); break; case VNSI_RECORDINGS_DELETED_UNDELETE: result = processRECORDINGS_DELETED_Undelete(req); break; case VNSI_RECORDINGS_DELETED_DELETE_ALL: result = processRECORDINGS_DELETED_DeleteAll(req); break; } return result; } /** OPCODE 1 - 19: VNSI network functions for general purpose */ bool cVNSIClient::process_Login(cRequestPacket &req) /* OPCODE 1 */ { if (req.getDataLength() <= 4) return false; m_protocolVersion = req.extract_U32(); req.extract_U8(); const char *clientName = req.extract_String(); INFOLOG("Welcome client '%s' with protocol version '%u'", clientName, m_protocolVersion); // Send the login reply time_t timeNow = time(NULL); struct tm* timeStruct = localtime(&timeNow); int timeOffset = timeStruct->tm_gmtoff; cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(VNSI_PROTOCOLVERSION); resp.add_U32(timeNow); resp.add_S32(timeOffset); resp.add_String("VDR-Network-Streaming-Interface (VNSI) Server"); resp.add_String(VNSI_SERVER_VERSION); resp.finalise(); if (m_protocolVersion < VNSI_MIN_PROTOCOLVERSION) ERRORLOG("Client '%s' have a not allowed protocol version '%u', terminating client", clientName, m_protocolVersion); else SetLoggedIn(true); if (m_protocolVersion < VNSI_RDS_PROTOCOLVERSION) { INFOLOG("RDS not supported on client '%s' and stream type disabled", clientName); m_bSupportRDS = false; } else { m_bSupportRDS = true; } m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::process_GetTime(cRequestPacket &req) /* OPCODE 2 */ { time_t timeNow = time(NULL); struct tm* timeStruct = localtime(&timeNow); int timeOffset = timeStruct->tm_gmtoff; cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(timeNow); resp.add_S32(timeOffset); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::process_EnableStatusInterface(cRequestPacket &req) { bool enabled = req.extract_U8(); SetStatusInterface(enabled); SetPriority(1); cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::process_Ping(cRequestPacket &req) /* OPCODE 7 */ { cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(1); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::process_GetSetup(cRequestPacket &req) /* OPCODE 8 */ { cResponsePacket resp; resp.init(req.getRequestID()); char* name = req.extract_String(); if (!strcasecmp(name, CONFNAME_PMTTIMEOUT)) resp.add_U32(PmtTimeout); else if (!strcasecmp(name, CONFNAME_TIMESHIFT)) resp.add_U32(TimeshiftMode); else if (!strcasecmp(name, CONFNAME_TIMESHIFTBUFFERSIZE)) resp.add_U32(TimeshiftBufferSize); else if (!strcasecmp(name, CONFNAME_TIMESHIFTBUFFERFILESIZE)) resp.add_U32(TimeshiftBufferFileSize); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::process_StoreSetup(cRequestPacket &req) /* OPCODE 9 */ { char* name = req.extract_String(); if (!strcasecmp(name, CONFNAME_PMTTIMEOUT)) { int value = req.extract_U32(); cPluginVNSIServer::StoreSetup(CONFNAME_PMTTIMEOUT, value); } else if (!strcasecmp(name, CONFNAME_TIMESHIFT)) { int value = req.extract_U32(); cPluginVNSIServer::StoreSetup(CONFNAME_TIMESHIFT, value); } else if (!strcasecmp(name, CONFNAME_TIMESHIFTBUFFERSIZE)) { int value = req.extract_U32(); cPluginVNSIServer::StoreSetup(CONFNAME_TIMESHIFTBUFFERSIZE, value); } else if (!strcasecmp(name, CONFNAME_TIMESHIFTBUFFERFILESIZE)) { int value = req.extract_U32(); cPluginVNSIServer::StoreSetup(CONFNAME_TIMESHIFTBUFFERFILESIZE, value); } else if (!strcasecmp(name, CONFNAME_PLAYRECORDING)) { int value = req.extract_U32(); cPluginVNSIServer::StoreSetup(CONFNAME_PLAYRECORDING, value); } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } /** OPCODE 20 - 39: VNSI network functions for live streaming */ bool cVNSIClient::processChannelStream_Open(cRequestPacket &req) /* OPCODE 20 */ { uint32_t uid = req.extract_U32(); int32_t priority = req.extract_S32(); uint8_t timeshift = req.extract_U8(); uint32_t timeout = req.end() ? VNSIServerConfig.stream_timeout : req.extract_U32(); if (m_isStreaming) StopChannelStreaming(); const cChannel *channel = FindChannelByUID(uid); // try channelnumber if (channel == NULL) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; channel = Channels->GetByNumber(uid); #else channel = Channels.GetByNumber(uid); #endif } cResponsePacket resp; resp.init(req.getRequestID()); if (channel == NULL) { ERRORLOG("Can't find channel %08x", uid); resp.add_U32(VNSI_RET_DATAINVALID); } else { if (StartChannelStreaming(resp, channel, priority, timeshift, timeout)) { INFOLOG("Started streaming of channel %s (timeout %i seconds)", channel->Name(), timeout); // return here without sending the response // (was already done in cLiveStreamer::StreamChannel) return true; } DEBUGLOG("Can't stream channel %s", channel->Name()); resp.add_U32(VNSI_RET_DATALOCKED); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return false; } bool cVNSIClient::processChannelStream_Close(cRequestPacket &req) /* OPCODE 21 */ { if (m_isStreaming) StopChannelStreaming(); cResponsePacket resp; resp.init(req.getRequestID()); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processChannelStream_Seek(cRequestPacket &req) /* OPCODE 22 */ { cResponsePacket resp; resp.init(req.getRequestID()); uint32_t serial = 0; if (m_isStreaming && m_Streamer) { int64_t time = req.extract_S64(); if (m_Streamer->SeekTime(time, serial)) resp.add_U32(VNSI_RET_OK); else resp.add_U32(VNSI_RET_ERROR); } else resp.add_U32(VNSI_RET_ERROR); resp.add_U32(serial); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } /** OPCODE 40 - 59: VNSI network functions for recording streaming */ bool cVNSIClient::processRecStream_Open(cRequestPacket &req) /* OPCODE 40 */ { const cRecording *recording = NULL; uint32_t uid = req.extract_U32(); recording = cRecordingsCache::GetInstance().Lookup(uid); cResponsePacket resp; resp.init(req.getRequestID()); if (recording && m_RecPlayer == NULL) { m_RecPlayer = new cRecPlayer(recording); resp.add_U32(VNSI_RET_OK); resp.add_U32(m_RecPlayer->getLengthFrames()); resp.add_U64(m_RecPlayer->getLengthBytes()); resp.add_U8(recording->IsPesRecording());//added for TS } else { resp.add_U32(VNSI_RET_DATAUNKNOWN); ERRORLOG("%s - unable to start recording !", __FUNCTION__); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRecStream_Close(cRequestPacket &req) /* OPCODE 41 */ { delete m_RecPlayer; m_RecPlayer = NULL; cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRecStream_GetBlock(cRequestPacket &req) /* OPCODE 42 */ { if (m_isStreaming) { ERRORLOG("Get block called during live streaming"); return false; } if (!m_RecPlayer) { ERRORLOG("Get block called when no recording open"); return false; } uint64_t position = req.extract_U64(); uint32_t amount = req.extract_U32(); cResponsePacket resp; resp.init(req.getRequestID()); uint8_t* p = resp.reserve(amount); uint32_t amountReceived = m_RecPlayer->getBlock(p, position, amount); if(amount > amountReceived) resp.unreserve(amount - amountReceived); if (!amountReceived) { resp.add_U32(0); DEBUGLOG("written 4(0) as getblock got 0"); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRecStream_PositionFromFrameNumber(cRequestPacket &req) /* OPCODE 43 */ { uint64_t retval = 0; uint32_t frameNumber = req.extract_U32(); if (m_RecPlayer) retval = m_RecPlayer->positionFromFrameNumber(frameNumber); cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U64(retval); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); DEBUGLOG("Wrote posFromFrameNum reply to client"); return true; } bool cVNSIClient::processRecStream_FrameNumberFromPosition(cRequestPacket &req) /* OPCODE 44 */ { uint32_t retval = 0; uint64_t position = req.extract_U64(); if (m_RecPlayer) retval = m_RecPlayer->frameNumberFromPosition(position); cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(retval); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); DEBUGLOG("Wrote frameNumFromPos reply to client"); return true; } bool cVNSIClient::processRecStream_GetIFrame(cRequestPacket &req) /* OPCODE 45 */ { bool success = false; uint32_t frameNumber = req.extract_U32(); uint32_t direction = req.extract_U32(); uint64_t rfilePosition = 0; uint32_t rframeNumber = 0; uint32_t rframeLength = 0; if (m_RecPlayer) success = m_RecPlayer->getNextIFrame(frameNumber, direction, &rfilePosition, &rframeNumber, &rframeLength); cResponsePacket resp; resp.init(req.getRequestID()); // returns file position, frame number, length if (success) { resp.add_U64(rfilePosition); resp.add_U32(rframeNumber); resp.add_U32(rframeLength); } else { resp.add_U32(0); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); DEBUGLOG("Wrote GNIF reply to client %lu %u %u", rfilePosition, rframeNumber, rframeLength); return true; } bool cVNSIClient::processRecStream_GetLength(cRequestPacket &req) /* OPCODE 46 */ { uint64_t length = 0; if (m_RecPlayer) { m_RecPlayer->reScan(); length = m_RecPlayer->getLengthBytes(); } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U64(length); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } /** OPCODE 60 - 79: VNSI network functions for channel access */ bool cVNSIClient::processCHANNELS_ChannelsCount(cRequestPacket &req) /* OPCODE 61 */ { int count = 0; #if VDRVERSNUM >= 20301 { LOCK_CHANNELS_READ; count = Channels->MaxNumber(); } #else Channels.Lock(false); count = Channels.MaxNumber(); Channels.Unlock(); #endif cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(count); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GetChannels(cRequestPacket &req) /* OPCODE 63 */ { if (req.getDataLength() != 5) return false; bool radio = req.extract_U32(); bool filter = req.extract_U8(); #if VDRVERSNUM >= 20301 cStateKey ChannelsKey(true); const cChannels *Channels = cChannels::GetChannelsRead(ChannelsKey); #else Channels.Lock(false); #endif cResponsePacket resp; resp.init(req.getRequestID()); cString caids; int caid; int caid_idx; #if VDRVERSNUM >= 20301 for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { if (radio != cVNSIChannelFilter::IsRadio(channel)) continue; // skip invalid channels if (channel->Sid() == 0) continue; // check filter if (filter && !VNSIChannelFilter.PassFilter(*channel)) continue; uint32_t uuid = CreateChannelUID(channel); resp.add_U32(channel->Number()); resp.add_String(m_toUTF8.Convert(channel->Name())); resp.add_String(m_toUTF8.Convert(channel->Provider())); resp.add_U32(uuid); resp.add_U32(channel->Ca(0)); caid_idx = 0; caids = "caids:"; while((caid = channel->Ca(caid_idx)) != 0) { caids = cString::sprintf("%s%d;", (const char*)caids, caid); caid_idx++; } resp.add_String((const char*)caids); if (m_protocolVersion >= 6) { resp.add_String(CreatePiconRef(channel)); } // create entry in EPG map on first query m_epgUpdate.insert(std::make_pair(uuid, sEpgUpdate())); } #if VDRVERSNUM >= 20301 ChannelsKey.Remove(); #else Channels.Unlock(); #endif resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GroupsCount(cRequestPacket &req) { uint32_t type = req.extract_U32(); m_channelgroups[0].clear(); m_channelgroups[1].clear(); switch(type) { // get groups defined in channels.conf default: case 0: CreateChannelGroups(false); break; // automatically create groups case 1: CreateChannelGroups(true); break; } uint32_t count = m_channelgroups[0].size() + m_channelgroups[1].size(); cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(count); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GroupList(cRequestPacket &req) { uint32_t radio = req.extract_U8(); cResponsePacket resp; resp.init(req.getRequestID()); for (const auto &i : m_channelgroups[radio]) { resp.add_String(i.second.name.c_str()); resp.add_U8(i.second.radio); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GetGroupMembers(cRequestPacket &req) { char* groupname = req.extract_String(); uint32_t radio = req.extract_U8(); bool filter = req.extract_U8(); int index = 0; cResponsePacket resp; resp.init(req.getRequestID()); // unknown group if(m_channelgroups[radio].find(groupname) == m_channelgroups[radio].end()) { resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool automatic = m_channelgroups[radio][groupname].automatic; std::string name; #if VDRVERSNUM >= 20301 cStateKey ChannelsKey(true); const cChannels *Channels = cChannels::GetChannelsRead(ChannelsKey); for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else Channels.Lock(false); for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { if(automatic && !channel->GroupSep()) name = channel->Provider(); else { if(channel->GroupSep()) { name = channel->Name(); continue; } } if(name.empty()) continue; if(cVNSIChannelFilter::IsRadio(channel) != radio) continue; // check filter if (filter && !VNSIChannelFilter.PassFilter(*channel)) continue; if(name == groupname) { resp.add_U32(CreateChannelUID(channel)); resp.add_U32(++index); } } #if VDRVERSNUM >= 20301 ChannelsKey.Remove(); #else Channels.Unlock(); #endif resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GetCaids(cRequestPacket &req) { uint32_t uid = req.extract_U32(); const cChannel *channel = FindChannelByUID(uid); cResponsePacket resp; resp.init(req.getRequestID()); if (channel != NULL) { int caid; int idx = 0; while((caid = channel->Ca(idx)) != 0) { resp.add_U32(caid); idx++; } } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GetWhitelist(cRequestPacket &req) { bool radio = req.extract_U8(); std::vector *providers; if(radio) providers = &VNSIChannelFilter.m_providersRadio; else providers = &VNSIChannelFilter.m_providersVideo; cResponsePacket resp; resp.init(req.getRequestID()); VNSIChannelFilter.m_Mutex.Lock(); for (const auto &i : *providers) { resp.add_String(i.m_name.c_str()); resp.add_U32(i.m_caid); } VNSIChannelFilter.m_Mutex.Unlock(); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_GetBlacklist(cRequestPacket &req) { bool radio = req.extract_U8(); const std::set *channels; if(radio) channels = &VNSIChannelFilter.m_channelsRadio; else channels = &VNSIChannelFilter.m_channelsVideo; cResponsePacket resp; resp.init(req.getRequestID()); VNSIChannelFilter.m_Mutex.Lock(); for (auto i : *channels) { resp.add_U32(i); } VNSIChannelFilter.m_Mutex.Unlock(); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_SetWhitelist(cRequestPacket &req) { bool radio = req.extract_U8(); cVNSIProvider provider; std::vector *providers; if(radio) providers = &VNSIChannelFilter.m_providersRadio; else providers = &VNSIChannelFilter.m_providersVideo; VNSIChannelFilter.m_Mutex.Lock(); providers->clear(); while(!req.end()) { char *str = req.extract_String(); provider.m_name = str; provider.m_caid = req.extract_U32(); providers->push_back(provider); } VNSIChannelFilter.StoreWhitelist(radio); VNSIChannelFilter.m_Mutex.Unlock(); cResponsePacket resp; resp.init(req.getRequestID()); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processCHANNELS_SetBlacklist(cRequestPacket &req) { bool radio = req.extract_U8(); cVNSIProvider provider; std::set *channels; if(radio) channels = &VNSIChannelFilter.m_channelsRadio; else channels = &VNSIChannelFilter.m_channelsVideo; VNSIChannelFilter.m_Mutex.Lock(); channels->clear(); int id; while(!req.end()) { id = req.extract_U32(); channels->insert(id); } VNSIChannelFilter.StoreBlacklist(radio); VNSIChannelFilter.m_Mutex.Unlock(); cResponsePacket resp; resp.init(req.getRequestID()); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } void cVNSIClient::CreateChannelGroups(bool automatic) { std::string groupname; #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else Channels.Lock(false); for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { bool isRadio = cVNSIChannelFilter::IsRadio(channel); if(automatic && !channel->GroupSep()) groupname = channel->Provider(); else if(!automatic && channel->GroupSep()) groupname = channel->Name(); if(groupname.empty()) continue; if(m_channelgroups[isRadio].find(groupname) == m_channelgroups[isRadio].end()) { ChannelGroup group; group.name = groupname; group.radio = isRadio; group.automatic = automatic; m_channelgroups[isRadio][groupname] = group; } } #if VDRVERSNUM < 20301 Channels.Unlock(); #endif } /** OPCODE 80 - 99: VNSI network functions for timer access */ bool cVNSIClient::processTIMER_GetCount(cRequestPacket &req) /* OPCODE 80 */ { cMutexLock lock(&m_timerLock); #if VDRVERSNUM >= 20301 LOCK_TIMERS_READ; int count = Timers->Count() + m_vnsiTimers.GetTimersCount(); #else int count = Timers.Count(); #endif cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(count); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_Get(cRequestPacket &req) /* OPCODE 81 */ { cMutexLock lock(&m_timerLock); uint32_t id = req.extract_U32(); cResponsePacket resp; resp.init(req.getRequestID()); if (id & m_vnsiTimers.VNSITIMER_MASK) { CVNSITimer timer; if (m_vnsiTimers.GetTimer(id, timer)) { resp.add_U32(VNSI_RET_OK); resp.add_U32(VNSI_TIMER_TYPE_EPG_SEARCH); resp.add_U32(id); resp.add_U32(timer.m_enabled); resp.add_U32(0); resp.add_U32(0); resp.add_U32(timer.m_priority); resp.add_U32(timer.m_lifetime); resp.add_U32(0); resp.add_U32(timer.m_channelUID); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_String(timer.m_name.c_str()); resp.add_String(timer.m_search.c_str()); if (m_protocolVersion >= 10) { resp.add_U32(0); } } else { resp.add_U32(VNSI_RET_DATAUNKNOWN); } } else { #if VDRVERSNUM >= 20301 LOCK_TIMERS_READ; int numTimers = Timers->Count(); if (numTimers > 0) { const cTimer *timer = Timers->GetById(id); #else int numTimers = Timers.Count(); if (numTimers > 0) { cTimer *timer = Timers.Get(id-1); #endif if (timer) { resp.add_U32(VNSI_RET_OK); if (m_protocolVersion >= 9) { uint32_t type; if (timer->HasFlags(tfVps)) type = VNSI_TIMER_TYPE_VPS; else type = VNSI_TIMER_TYPE_MAN; resp.add_U32(type); } #if VDRVERSNUM >= 20301 resp.add_U32(timer->Id()); #else resp.add_U32(timer->Index()+1); #endif resp.add_U32(timer->HasFlags(tfActive)); resp.add_U32(timer->Recording()); resp.add_U32(timer->Pending()); resp.add_U32(timer->Priority()); resp.add_U32(timer->Lifetime()); resp.add_U32(timer->Channel()->Number()); resp.add_U32(CreateChannelUID(timer->Channel())); resp.add_U32(timer->StartTime()); resp.add_U32(timer->StopTime()); resp.add_U32(timer->Day()); resp.add_U32(timer->WeekDays()); resp.add_String(m_toUTF8.Convert(timer->File())); if (m_protocolVersion >= 9) { resp.add_String(""); } if (m_protocolVersion >= 10) { resp.add_U32(m_vnsiTimers.GetParent(timer)); } } else resp.add_U32(VNSI_RET_DATAUNKNOWN); } else resp.add_U32(VNSI_RET_DATAUNKNOWN); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_GetList(cRequestPacket &req) /* OPCODE 82 */ { cResponsePacket resp; resp.init(req.getRequestID()); cMutexLock lock(&m_timerLock); #if VDRVERSNUM >= 20301 LOCK_TIMERS_READ; int numVdrTimers = Timers->Count(); int numTimers = numVdrTimers + m_vnsiTimers.GetTimersCount(); resp.add_U32(numTimers); for (int i = 0; i < numVdrTimers; i++) { const cTimer *timer = Timers->Get(i); #else int numTimers = Timers.Count() + m_vnsiTimers.GetTimersCount(); resp.add_U32(numTimers); for (int i = 0; i < numTimers; i++) { cTimer *timer = Timers.Get(i); #endif if (!timer) continue; if (m_protocolVersion >= 9) { uint32_t type; if (timer->HasFlags(tfVps)) type = VNSI_TIMER_TYPE_VPS; else type = VNSI_TIMER_TYPE_MAN; resp.add_U32(type); } #if VDRVERSNUM >= 20301 resp.add_U32(timer->Id()); #else resp.add_U32(timer->Index()+1); #endif resp.add_U32(timer->HasFlags(tfActive)); resp.add_U32(timer->Recording()); resp.add_U32(timer->Pending()); resp.add_U32(timer->Priority()); resp.add_U32(timer->Lifetime()); resp.add_U32(timer->Channel()->Number()); resp.add_U32(CreateChannelUID(timer->Channel())); resp.add_U32(timer->StartTime()); resp.add_U32(timer->StopTime()); resp.add_U32(timer->Day()); resp.add_U32(timer->WeekDays()); resp.add_String(m_toUTF8.Convert(timer->File())); if (m_protocolVersion >= 9) { resp.add_String(""); } if (m_protocolVersion >= 10) { resp.add_U32(m_vnsiTimers.GetParent(timer)); } } std::vector vnsitimers = m_vnsiTimers.GetTimers(); for (auto &vnsitimer : vnsitimers) { resp.add_U32(VNSI_TIMER_TYPE_EPG_SEARCH); resp.add_U32(vnsitimer.m_id | m_vnsiTimers.VNSITIMER_MASK); resp.add_U32(vnsitimer.m_enabled); resp.add_U32(0); resp.add_U32(0); resp.add_U32(vnsitimer.m_priority); resp.add_U32(vnsitimer.m_lifetime); resp.add_U32(0); resp.add_U32(vnsitimer.m_channelUID); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_U32(0); resp.add_String(vnsitimer.m_name.c_str()); resp.add_String(vnsitimer.m_search.c_str()); if (m_protocolVersion >= 10) { resp.add_U32(0); } } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_Add(cRequestPacket &req) /* OPCODE 83 */ { cMutexLock lock(&m_timerLock); uint32_t type = 0; std::string epgsearch; if (m_protocolVersion >= 9) { type = req.extract_U32(); } uint32_t flags = req.extract_U32() > 0 ? tfActive : tfNone; uint32_t priority = req.extract_U32(); uint32_t lifetime = req.extract_U32(); uint32_t channelid = req.extract_U32(); time_t startTime = req.extract_U32(); time_t stopTime = req.extract_U32(); time_t day = req.extract_U32(); uint32_t weekdays = req.extract_U32(); const char *file = req.extract_String(); const char *aux = req.extract_String(); if (m_protocolVersion >= 9) epgsearch = req.extract_String(); uint32_t marginStart = 0; uint32_t marginEnd = 0; if (m_protocolVersion >= 10) { marginStart = req.extract_U32(); marginEnd = req.extract_U32(); } // handle instant timers if(startTime == -1 || startTime == 0) { startTime = time(NULL); } struct tm tm_r; struct tm *time = localtime_r(&startTime, &tm_r); if (day <= 0) day = cTimer::SetTime(startTime, 0); int start = time->tm_hour * 100 + time->tm_min; time = localtime_r(&stopTime, &tm_r); int stop = time->tm_hour * 100 + time->tm_min; if (type == VNSI_TIMER_TYPE_VPS) flags |= tfVps; cString buffer; const cChannel* channel = FindChannelByUID(channelid); if(channel != NULL) { buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, (const char*)channel->GetChannelID().ToString(), *cTimer::PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux); } cResponsePacket resp; resp.init(req.getRequestID()); if (type == VNSI_TIMER_TYPE_EPG_SEARCH) { CVNSITimer vnsitimer; vnsitimer.m_name = aux; vnsitimer.m_channelUID = channelid; vnsitimer.m_search = epgsearch; vnsitimer.m_enabled = flags; vnsitimer.m_priority = priority; vnsitimer.m_lifetime = lifetime; vnsitimer.m_marginStart = marginStart; vnsitimer.m_marginEnd = marginEnd; m_vnsiTimers.Add(std::move(vnsitimer)); resp.add_U32(VNSI_RET_OK); } else { std::unique_ptr timer(new cTimer); if (timer->Parse(buffer)) { #if VDRVERSNUM >= 20301 LOCK_TIMERS_WRITE; const cTimer *t = Timers->GetTimer(timer.get()); if (!t) { INFOLOG("Timer %s added", *timer->ToDescr()); Timers->Add(timer.release()); Timers->SetModified(); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } #else cTimer *t = Timers.GetTimer(timer.get()); if (!t) { INFOLOG("Timer %s added", *timer->ToDescr()); Timers.Add(timer.release()); Timers.SetModified(); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } #endif else { ERRORLOG("Timer already defined: %d %s", t->Index() + 1, *t->ToText()); resp.add_U32(VNSI_RET_DATALOCKED); } } else { ERRORLOG("Error in timer settings"); resp.add_U32(VNSI_RET_DATAINVALID); } } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_Delete(cRequestPacket &req) /* OPCODE 84 */ { cMutexLock lock(&m_timerLock); uint32_t id = req.extract_U32(); bool force = req.extract_U32(); cResponsePacket resp; resp.init(req.getRequestID()); if (id & m_vnsiTimers.VNSITIMER_MASK) { if (m_vnsiTimers.DeleteTimer(id)) { INFOLOG("Deleting vnsitimer %d", id); resp.add_U32(VNSI_RET_OK); } else { ERRORLOG("Unable to delete timer - invalid timer identifier"); resp.add_U32(VNSI_RET_DATAINVALID); } } else { #if VDRVERSNUM >= 20301 LOCK_TIMERS_WRITE; cTimer *timer = Timers->GetById(id); if (timer) { Timers->SetExplicitModify(); { if (timer->Recording()) { if (force) { timer->Skip(); cRecordControls::Process(Timers, time(NULL)); } else { ERRORLOG("Timer \"%i\" is recording and can be deleted (use force=1 to stop it)", id); resp.add_U32(VNSI_RET_RECRUNNING); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } } INFOLOG("Deleting timer %s", *timer->ToDescr()); Timers->Del(timer); Timers->SetModified(); resp.add_U32(VNSI_RET_OK); } } else { ERRORLOG("Error in timer settings"); resp.add_U32(VNSI_RET_DATAINVALID); } #else int timersCount = Timers.Count(); if (id <= 0 || id > (uint32_t)timersCount) { ERRORLOG("Unable to delete timer - invalid timer identifier"); resp.add_U32(VNSI_RET_DATAINVALID); } cTimer *timer = Timers.Get(id-1); if (timer) { if (!Timers.BeingEdited()) { if (timer->Recording()) { if (force) { timer->Skip(); cRecordControls::Process(time(NULL)); } else { ERRORLOG("Timer \"%i\" is recording and can be deleted (use force=1 to stop it)", id); resp.add_U32(VNSI_RET_RECRUNNING); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } } INFOLOG("Deleting timer %s", *timer->ToDescr()); Timers.Del(timer); Timers.SetModified(); resp.add_U32(VNSI_RET_OK); } else { ERRORLOG("Unable to delete timer - timers being edited at VDR"); resp.add_U32(VNSI_RET_DATALOCKED); } } else { ERRORLOG("Error in timer settings"); resp.add_U32(VNSI_RET_DATAINVALID); } #endif } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_Update(cRequestPacket &req) /* OPCODE 85 */ { cMutexLock lock(&m_timerLock); bool active; uint32_t priority, lifetime, channelid, weekdays; uint32_t type = 0; time_t startTime, stopTime, day; const char *file; const char *aux; std::string epgsearch; uint32_t id = req.extract_U32(); cResponsePacket resp; resp.init(req.getRequestID()); type = m_protocolVersion >= 9 ? req.extract_U32() : VNSI_TIMER_TYPE_MAN; active = req.extract_U32(); priority = req.extract_U32(); lifetime = req.extract_U32(); channelid = req.extract_U32(); startTime = req.extract_U32(); stopTime = req.extract_U32(); day = req.extract_U32(); weekdays = req.extract_U32(); file = req.extract_String(); aux = req.extract_String(); if (m_protocolVersion >= 9) { epgsearch = req.extract_String(); } if (id & m_vnsiTimers.VNSITIMER_MASK) { CVNSITimer vnsitimer; vnsitimer.m_name = aux; vnsitimer.m_channelUID = channelid; vnsitimer.m_search = epgsearch; vnsitimer.m_enabled = active; vnsitimer.m_priority = priority; vnsitimer.m_lifetime = lifetime; if (!m_vnsiTimers.UpdateTimer(id, vnsitimer)) { ERRORLOG("Timer \"%u\" not defined", id); resp.add_U32(VNSI_RET_DATAUNKNOWN); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } } else { #if VDRVERSNUM >= 20301 LOCK_TIMERS_WRITE; cTimer *timer = Timers->GetById(id); #else cTimer *timer = Timers.Get(id - 1); #endif if (!timer) { ERRORLOG("Timer \"%u\" not defined", id); resp.add_U32(VNSI_RET_DATAUNKNOWN); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } cTimer t = *timer; struct tm tm_r; struct tm *time = localtime_r(&startTime, &tm_r); if (day <= 0) day = cTimer::SetTime(startTime, 0); int start = time->tm_hour * 100 + time->tm_min; time = localtime_r(&stopTime, &tm_r); int stop = time->tm_hour * 100 + time->tm_min; uint32_t flags = active > 0 ? tfActive : tfNone; if (type == VNSI_TIMER_TYPE_VPS) flags |= tfVps; cString buffer; const cChannel* channel = FindChannelByUID(channelid); if(channel != NULL) { buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, (const char*)channel->GetChannelID().ToString(), *cTimer::PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux); } if (!t.Parse(buffer)) { ERRORLOG("Error in timer settings"); resp.add_U32(VNSI_RET_DATAINVALID); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } *timer = t; #if VDRVERSNUM >= 20301 Timers->SetModified(); #else Timers.SetModified(); #endif } resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processTIMER_GetTypes(cRequestPacket &req) /* OPCODE 80 */ { cResponsePacket resp; resp.init(req.getRequestID()); #if VDRVERSNUM >= 20301 resp.add_U32(VNSI_TIMER_TYPE_EPG_SEARCH); #else resp.add_U32(0); #endif resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } /** OPCODE 100 - 119: VNSI network functions for recording access */ bool cVNSIClient::processRECORDINGS_GetDiskSpace(cRequestPacket &req) /* OPCODE 100 */ { int FreeMB; int UsedMB; #if VDRVERSNUM >= 20102 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB); #else int Percent = VideoDiskSpace(&FreeMB, &UsedMB); #endif int Total = FreeMB + UsedMB; cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(Total); resp.add_U32(FreeMB); resp.add_U32(Percent); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_GetCount(cRequestPacket &req) /* OPCODE 101 */ { cResponsePacket resp; resp.init(req.getRequestID()); #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; resp.add_U32(Recordings->Count()); #else resp.add_U32(Recordings.Count()); #endif resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_GetList(cRequestPacket &req) /* OPCODE 102 */ { cMutexLock lock(&m_timerLock); #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; #else cThreadLock RecordingsLock(&Recordings); #endif cResponsePacket resp; resp.init(req.getRequestID()); #if VDRVERSNUM >= 20301 for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording)) #else for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) #endif { #if APIVERSNUM >= 10705 const cEvent *event = recording->Info()->GetEvent(); #else const cEvent *event = NULL; #endif time_t recordingStart = 0; int recordingDuration = 0; if (event) { recordingStart = event->StartTime(); recordingDuration = event->Duration(); } else { cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); if (rc) { recordingStart = rc->Timer()->StartTime(); recordingDuration = rc->Timer()->StopTime() - recordingStart; } else { #if APIVERSNUM >= 10727 recordingStart = recording->Start(); #else recordingStart = recording->start; #endif } } DEBUGLOG("GRI: RC: recordingStart=%lu recordingDuration=%i", recordingStart, recordingDuration); // recording_time resp.add_U32(recordingStart); // duration resp.add_U32(recordingDuration); // priority #if APIVERSNUM >= 10727 resp.add_U32(recording->Priority()); #else resp.add_U32(recording->priority); #endif // lifetime #if APIVERSNUM >= 10727 resp.add_U32(recording->Lifetime()); #else resp.add_U32(recording->lifetime); #endif // channel_name resp.add_String(recording->Info()->ChannelName() ? m_toUTF8.Convert(recording->Info()->ChannelName()) : ""); if (m_protocolVersion >= 9) { // channel uuid #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByChannelID(recording->Info()->ChannelID()); #else Channels.Lock(false); const cChannel *channel = Channels.GetByChannelID(recording->Info()->ChannelID()); Channels.Unlock(); #endif if (channel) { resp.add_U32(CreateChannelUID(channel)); resp.add_U8(cVNSIChannelFilter::IsRadio(channel) ? 1 : 2); } else { resp.add_U32(0); resp.add_U8(0); } } char* fullname = strdup(recording->Name()); char* recname = strrchr(fullname, FOLDERDELIMCHAR); char* directory = nullptr; if(recname == NULL) { recname = fullname; } else { *recname = 0; recname++; directory = fullname; } // title resp.add_String(m_toUTF8.Convert(recname)); // subtitle if (!isempty(recording->Info()->ShortText())) resp.add_String(m_toUTF8.Convert(recording->Info()->ShortText())); else resp.add_String(""); // description if (!isempty(recording->Info()->Description())) resp.add_String(m_toUTF8.Convert(recording->Info()->Description())); else resp.add_String(""); // directory if(directory != NULL) { char* p = directory; while(*p != 0) { if (*p == FOLDERDELIMCHAR) *p = '/'; else if (*p == '_') *p = ' '; p++; } while(*directory == '/') directory++; } std::string strDirectory; if (directory) strDirectory = directory; int noOfEntries = 1; char* filename = strdup(recording->FileName()); char *pch = strrchr(filename, '/'); if (pch) { int noOfRecs = 0; *pch = 0; char* foldername = filename; struct dirent **fileListTemp; noOfEntries = scandir(foldername, &fileListTemp, NULL, alphasort); for (int i=0; id_name); if (name.find(".rec") != std::string::npos) noOfRecs++; } if (noOfRecs > 1) { strDirectory += "/"; strDirectory += recname; } } free(filename); resp.add_String(strDirectory.empty() ? "" : m_toUTF8.Convert(strDirectory.c_str())); // filename / uid of recording uint32_t uid = cRecordingsCache::GetInstance().Register(recording, false); resp.add_U32(uid); free(fullname); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_Rename(cRequestPacket &req) /* OPCODE 103 */ { uint32_t uid = req.extract_U32(); char* newtitle = req.extract_String(); int r = VNSI_RET_DATAINVALID; #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; #endif const cRecording* recording = cRecordingsCache::GetInstance().Lookup(uid); if(recording != NULL) { // get filename and remove last part (recording time) std::string filename_old(recording->FileName()); std::string::size_type i = filename_old.rfind('/'); if (i != filename_old.npos) filename_old.erase(i); // replace spaces in newtitle strreplace(newtitle, ' ', '_'); std::string filename_new(filename_old); i = filename_new.rfind('/'); if (i != filename_new.npos) filename_new.erase(i + 1); filename_new += newtitle; INFOLOG("renaming recording '%s' to '%s'", filename_old.c_str(), filename_new.c_str()); r = rename(filename_old.c_str(), filename_new.c_str()); #if VDRVERSNUM >= 20301 Recordings->Update(); #else Recordings.Update(); #endif } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(r); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_Delete(cRequestPacket &req) /* OPCODE 104 */ { cString recName; cRecording* recording = NULL; uint32_t uid = req.extract_U32(); recording = cRecordingsCache::GetInstance().LookupWrite(uid); cResponsePacket resp; resp.init(req.getRequestID()); if (recording) { DEBUGLOG("deleting recording: %s", recording->Name()); cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); if (!rc) { if (recording->Delete()) { // Copy svdrdeveldevelp's way of doing this, see if it works #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; Recordings->DelByName(recording->FileName()); #else Recordings.DelByName(recording->FileName()); #endif INFOLOG("Recording \"%s\" deleted", recording->FileName()); resp.add_U32(VNSI_RET_OK); } else { ERRORLOG("Error while deleting recording!"); resp.add_U32(VNSI_RET_ERROR); } } else { ERRORLOG("Recording \"%s\" is in use by timer %d", recording->Name(), rc->Timer()->Index() + 1); resp.add_U32(VNSI_RET_DATALOCKED); } } else { ERRORLOG("Error in recording name \"%s\"", (const char*)recName); resp.add_U32(VNSI_RET_DATAUNKNOWN); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_GetEdl(cRequestPacket &req) /* OPCODE 105 */ { cString recName; const cRecording* recording = NULL; uint32_t uid = req.extract_U32(); recording = cRecordingsCache::GetInstance().Lookup(uid); cResponsePacket resp; resp.init(req.getRequestID()); if (recording) { cMarks marks; if(marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording())) { #if VDRVERSNUM >= 10732 cMark* mark = NULL; double fps = recording->FramesPerSecond(); while((mark = marks.GetNextBegin(mark)) != NULL) { resp.add_U64(mark->Position() *1000 / fps); resp.add_U64(mark->Position() *1000 / fps); resp.add_S32(2); } #endif } } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } /** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ bool cVNSIClient::processEPG_GetForChannel(cRequestPacket &req) /* OPCODE 120 */ { uint32_t channelUID = 0; channelUID = req.extract_U32(); uint32_t startTime = req.extract_U32(); uint32_t duration = req.extract_U32(); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; #else Channels.Lock(false); #endif const cChannel* channel = NULL; channel = FindChannelByUID(channelUID); if(channel != NULL) { DEBUGLOG("get schedule called for channel '%s'", (const char*)channel->GetChannelID().ToString()); } cResponsePacket resp; resp.init(req.getRequestID()); if (!channel) { resp.add_U32(0); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); #if VDRVERSNUM < 20301 Channels.Unlock(); #endif ERRORLOG("written 0 because channel = NULL"); return true; } #if VDRVERSNUM < 20301 cSchedulesLock MutexLock; const cSchedules *Schedules = cSchedules::Schedules(MutexLock); if (!Schedules) { resp.add_U32(0); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); Channels.Unlock(); DEBUGLOG("written 0 because Schedule!s! = NULL"); return true; } #endif #if VDRVERSNUM >= 20301 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID()); #else const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID()); #endif if (!Schedule) { resp.add_U32(0); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); #if VDRVERSNUM < 20301 Channels.Unlock(); #endif DEBUGLOG("written 0 because Schedule = NULL"); return true; } bool atLeastOneEvent = false; uint32_t thisEventID; uint32_t thisEventTime; uint32_t thisEventDuration; uint32_t thisEventContent; uint32_t thisEventRating; const char* thisEventTitle; const char* thisEventSubTitle; const char* thisEventDescription; for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event)) { thisEventID = event->EventID(); thisEventTitle = event->Title(); thisEventSubTitle = event->ShortText(); thisEventDescription = event->Description(); thisEventTime = event->StartTime(); thisEventDuration = event->Duration(); #if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) thisEventContent = event->Contents(); thisEventRating = 0; #elif APIVERSNUM >= 10711 thisEventContent = event->Contents(); thisEventRating = event->ParentalRating(); #else thisEventContent = 0; thisEventRating = 0; #endif //in the past filter if ((thisEventTime + thisEventDuration) < (uint32_t)time(NULL)) continue; //start time filter if ((thisEventTime + thisEventDuration) <= startTime) continue; //duration filter if (duration != 0 && thisEventTime >= (startTime + duration)) continue; if (!thisEventTitle) thisEventTitle = ""; if (!thisEventSubTitle) thisEventSubTitle = ""; if (!thisEventDescription) thisEventDescription = ""; resp.add_U32(thisEventID); resp.add_U32(thisEventTime); resp.add_U32(thisEventDuration); resp.add_U32(thisEventContent); resp.add_U32(thisEventRating); resp.add_String(m_toUTF8.Convert(thisEventTitle)); resp.add_String(m_toUTF8.Convert(thisEventSubTitle)); resp.add_String(m_toUTF8.Convert(thisEventDescription)); atLeastOneEvent = true; } #if VDRVERSNUM < 20301 Channels.Unlock(); #endif DEBUGLOG("Got all event data"); if (!atLeastOneEvent) { resp.add_U32(0); DEBUGLOG("Written 0 because no data"); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); const cEvent *lastEvent = Schedule->Events()->Last(); if (lastEvent) { auto &u = m_epgUpdate[channelUID]; u.lastEvent = lastEvent->StartTime(); u.attempts = 0; } DEBUGLOG("written schedules packet"); return true; } /*! * OPCODE 140 - 169: * VNSI network functions for channel scanning */ bool cVNSIClient::processSCAN_ScanSupported(cRequestPacket &req) /* OPCODE 140 */ { uint32_t retValue = VNSI_RET_NOTSUPPORTED; if (!m_inhibidDataUpdates && m_ChannelScanControl.IsSupported()) retValue = VNSI_RET_OK; cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(retValue); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processSCAN_GetSupportedTypes(cRequestPacket &req) { uint32_t retValue = 0; if (m_ChannelScanControl.IsSupported()) { retValue |= m_ChannelScanControl.SupportsDVB_T() ? VNSI_SCAN_SUPPORT_DVB_T : 0; retValue |= m_ChannelScanControl.SupportsDVB_C() ? VNSI_SCAN_SUPPORT_DVB_C : 0; retValue |= m_ChannelScanControl.SupportsDVB_S() ? VNSI_SCAN_SUPPORT_DVB_S : 0; retValue |= m_ChannelScanControl.SupportsAnalogTV() ? VNSI_SCAN_SUPPORT_ANALOG_TV : 0; retValue |= m_ChannelScanControl.SupportsAnalogRadio() ? VNSI_SCAN_SUPPORT_ANALOG_RADIO : 0; retValue |= m_ChannelScanControl.SupportsATSC() ? VNSI_SCAN_SUPPORT_ATSC : 0; } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(retValue); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processSCAN_GetCountries(cRequestPacket &req) /* OPCODE 141 */ { cResponsePacket resp; resp.init(req.getRequestID()); scannerEntryList list; if (m_ChannelScanControl.GetCountries(list)) { resp.add_U32(VNSI_RET_OK); for (const auto &i : list) { resp.add_U32(i.index); resp.add_String(i.name); resp.add_String(i.longName); } } else { resp.add_U32(VNSI_RET_NOTSUPPORTED); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processSCAN_GetSatellites(cRequestPacket &req) /* OPCODE 142 */ { cResponsePacket resp; resp.init(req.getRequestID()); scannerEntryList list; if (m_ChannelScanControl.GetSatellites(list)) { resp.add_U32(VNSI_RET_OK); for (const auto &i : list) { resp.add_U32(i.index); resp.add_String(i.name); resp.add_String(i.longName); } } else { resp.add_U32(VNSI_RET_NOTSUPPORTED); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processSCAN_Start(cRequestPacket &req) /* OPCODE 143 */ { sScanServiceData svc; svc.type = (int)req.extract_U32(); svc.scan_tv = (bool)req.extract_U8(); svc.scan_radio = (bool)req.extract_U8(); svc.scan_fta = (bool)req.extract_U8(); svc.scan_scrambled = (bool)req.extract_U8(); svc.scan_hd = (bool)req.extract_U8(); svc.CountryIndex = (int)req.extract_U32(); svc.DVBC_Inversion = (int)req.extract_U32(); svc.DVBC_Symbolrate = (int)req.extract_U32(); svc.DVBC_QAM = (int)req.extract_U32(); svc.DVBT_Inversion = (int)req.extract_U32(); svc.SatIndex = (int)req.extract_U32(); svc.ATSC_Type = (int)req.extract_U32(); cResponsePacket resp; resp.init(req.getRequestID()); if (!m_inhibidDataUpdates && m_ChannelScanControl.IsSupported()) { if (m_ChannelScanControl.StartScan(svc)) { resp.add_U32(VNSI_RET_OK); m_inhibidDataUpdates = true; } else resp.add_U32(VNSI_RET_ERROR); } else resp.add_U32(VNSI_RET_NOTSUPPORTED); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processSCAN_Stop(cRequestPacket &req) /* OPCODE 144 */ { m_inhibidDataUpdates = false; cResponsePacket resp; resp.init(req.getRequestID()); if (m_ChannelScanControl.IsSupported()) { if (m_ChannelScanControl.StopScan()) resp.add_U32(VNSI_RET_OK); else resp.add_U32(VNSI_RET_ERROR); } else resp.add_U32(VNSI_RET_NOTSUPPORTED); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } void cVNSIClient::processSCAN_SetPercentage(int percent) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_PERCENTAGE); resp.add_U32(percent); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_SetSignalStrength(int strength, bool locked) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_SIGNAL); resp.add_U32(strength); resp.add_U32(locked); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_SetDeviceInfo(const char *Info) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_DEVICE); resp.add_String(Info); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_SetTransponder(const char *Info) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_TRANSPONDER); resp.add_String(Info); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_NewChannel(const char *Name, bool isRadio, bool isEncrypted, bool isHD) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_NEWCHANNEL); resp.add_U32(isRadio); resp.add_U32(isEncrypted); resp.add_U32(isHD); resp.add_String(Name); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_IsFinished() { cResponsePacket resp; resp.initScan(VNSI_SCANNER_FINISHED); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } void cVNSIClient::processSCAN_SetStatus(int status) { cResponsePacket resp; resp.initScan(VNSI_SCANNER_STATUS); resp.add_U32(status); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); } bool cVNSIClient::processOSD_Connect(cRequestPacket &req) /* OPCODE 160 */ { delete m_Osd; m_Osd = new cVnsiOsdProvider(&m_socket); int osdWidth, osdHeight; double aspect; cDevice::PrimaryDevice()->GetOsdSize(osdWidth, osdHeight, aspect); cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(osdWidth); resp.add_U32(osdHeight); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processOSD_Disconnect() /* OPCODE 161 */ { delete m_Osd; m_Osd = NULL; return true; } bool cVNSIClient::processOSD_Hitkey(cRequestPacket &req) /* OPCODE 162 */ { if (m_Osd) { unsigned int key = req.extract_U32(); cVnsiOsdProvider::SendKey(key); } return true; } /** OPCODE 180 - 189: VNSI network functions for deleted recording access */ bool cVNSIClient::processRECORDINGS_DELETED_Supported(cRequestPacket &req) /* OPCODE 180 */ { cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(VNSI_RET_OK); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_DELETED_GetCount(cRequestPacket &req) /* OPCODE 181 */ { cResponsePacket resp; resp.init(req.getRequestID()); #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_READ; resp.add_U32(DeletedRecordings->Count()); #else resp.add_U32(DeletedRecordings.Count()); #endif resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_DELETED_GetList(cRequestPacket &req) /* OPCODE 182 */ { cResponsePacket resp; resp.init(req.getRequestID()); cMutexLock lock(&m_timerLock); #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_READ; for (const cRecording *recording = DeletedRecordings->First(); recording; recording = DeletedRecordings->Next(recording)) #else cThreadLock RecordingsLock(&Recordings); for (cRecording *recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) #endif { #if APIVERSNUM >= 10705 const cEvent *event = recording->Info()->GetEvent(); #else const cEvent *event = NULL; #endif time_t recordingStart = 0; int recordingDuration = 0; if (event) { recordingStart = event->StartTime(); recordingDuration = event->Duration(); } else { cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); if (rc) { recordingStart = rc->Timer()->StartTime(); recordingDuration = rc->Timer()->StopTime() - recordingStart; } else { #if APIVERSNUM >= 10727 recordingStart = recording->Start(); #else recordingStart = recording->start; #endif } } DEBUGLOG("GRI: RC: recordingStart=%lu recordingDuration=%i", recordingStart, recordingDuration); // recording_time resp.add_U32(recordingStart); // duration resp.add_U32(recordingDuration); // priority #if APIVERSNUM >= 10727 resp.add_U32(recording->Priority()); #else resp.add_U32(recording->priority); #endif // lifetime #if APIVERSNUM >= 10727 resp.add_U32(recording->Lifetime()); #else resp.add_U32(recording->lifetime); #endif // channel_name resp.add_String(recording->Info()->ChannelName() ? m_toUTF8.Convert(recording->Info()->ChannelName()) : ""); char* fullname = strdup(recording->Name()); char* recname = strrchr(fullname, FOLDERDELIMCHAR); char* directory = NULL; if(recname == NULL) { recname = fullname; } else { *recname = 0; recname++; directory = fullname; } // title resp.add_String(m_toUTF8.Convert(recname)); // subtitle if (!isempty(recording->Info()->ShortText())) resp.add_String(m_toUTF8.Convert(recording->Info()->ShortText())); else resp.add_String(""); // description if (!isempty(recording->Info()->Description())) resp.add_String(m_toUTF8.Convert(recording->Info()->Description())); else resp.add_String(""); // directory if(directory != NULL) { char* p = directory; while(*p != 0) { if(*p == FOLDERDELIMCHAR) *p = '/'; if(*p == '_') *p = ' '; p++; } while(*directory == '/') directory++; } resp.add_String((isempty(directory)) ? "" : m_toUTF8.Convert(directory)); // filename / uid of recording uint32_t uid = cRecordingsCache::GetInstance().Register(recording, false); resp.add_U32(uid); free(fullname); } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_DELETED_Delete(cRequestPacket &req) /* OPCODE 183 */ { cResponsePacket resp; resp.init(req.getRequestID()); cString recName; cRecording* recording = NULL; #if VDRVERSNUM >= 20102 cLockFile LockFile(cVideoDirectory::Name()); #else cLockFile LockFile(VideoDirectory); #endif if (LockFile.Lock()) { uint32_t uid = req.extract_U32(); #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_WRITE; for (recording = DeletedRecordings->First(); recording; recording = DeletedRecordings->Next(recording)) #else cThreadLock DeletedRecordingsLock(&DeletedRecordings); for (recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) #endif { if (uid == CreateStringHash(recording->FileName())) { #if VDRVERSNUM >= 20102 if (!cVideoDirectory::RemoveVideoFile(recording->FileName())) #else if (!RemoveVideoFile(recording->FileName())) #endif { ERRORLOG("Error while remove deleted recording (%s)", recording->FileName()); resp.add_U32(VNSI_RET_ERROR); } else { #if VDRVERSNUM >= 20301 DeletedRecordings->Del(recording); DeletedRecordings->Update(); #else DeletedRecordings.Del(recording); DeletedRecordings.Update(); #endif INFOLOG("Recording \"%s\" permanent deleted", recording->FileName()); resp.add_U32(VNSI_RET_OK); } break; } } } resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::Undelete(cRecording* recording) { DEBUGLOG("undelete recording: %s", recording->Name()); char *NewName = strdup(recording->FileName()); char *ext = strrchr(NewName, '.'); if (ext && strcmp(ext, ".del") == 0) { strcpy(ext, ".rec"); if (!access(NewName, F_OK)) { ERRORLOG("Recording with the same name exists (%s)", NewName); OsdStatusMessage(*cString::sprintf("%s (%s)", tr("Recording with the same name exists"), NewName)); } else { if (access(recording->FileName(), F_OK) == 0) { #if VDRVERSNUM >= 20102 if (!cVideoDirectory::RenameVideoFile(recording->FileName(), NewName)) #else if (!RenameVideoFile(recording->FileName(), NewName)) #endif { ERRORLOG("Error while rename deleted recording (%s) to (%s)", recording->FileName(), NewName); } cIndexFile index(NewName, false, recording->IsPesRecording()); int LastFrame = index.Last() - 1; if (LastFrame > 0) { uint16_t FileNumber = 0; off_t FileOffset = 0; index.Get(LastFrame, &FileNumber, &FileOffset); if (FileNumber == 0) { ERRORLOG("while read last filenumber (%s)", NewName); OsdStatusMessage(*cString::sprintf("%s (%s)", tr("Error while read last filenumber"), NewName)); } else { for (int i = 1; i <= FileNumber; i++) { cString temp = cString::sprintf(recording->IsPesRecording() ? "%s/%03d.vdr" : "%s/%05d.ts", (const char *)NewName, i); if (access(*temp, R_OK) != 0) { i = FileNumber; OsdStatusMessage(*cString::sprintf("%s %03d (%s)", tr("Error while accessing vdrfile"), i, NewName)); } } } } else { ERRORLOG("accessing indexfile (%s)", NewName); OsdStatusMessage(*cString::sprintf("%s (%s)", tr("Error while accessing indexfile"), NewName)); } #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; LOCK_DELETEDRECORDINGS_WRITE; DeletedRecordings->Del(recording); Recordings->Update(); DeletedRecordings->Update(); #else DeletedRecordings.Del(recording); Recordings.Update(); DeletedRecordings.Update(); #endif } else { ERRORLOG("deleted recording '%s' vanished", recording->FileName()); OsdStatusMessage(*cString::sprintf("%s \"%s\"", tr("Deleted recording vanished"), recording->FileName())); } } } free(NewName); return true; } bool cVNSIClient::processRECORDINGS_DELETED_Undelete(cRequestPacket &req) /* OPCODE 184 */ { int ret = VNSI_RET_DATAUNKNOWN; #if VDRVERSNUM >= 20102 cLockFile LockFile(cVideoDirectory::Name()); #else cLockFile LockFile(VideoDirectory); #endif if (LockFile.Lock()) { uint32_t uid = req.extract_U32(); #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_WRITE; for (cRecording* recording = DeletedRecordings->First(); recording; recording = DeletedRecordings->Next(recording)) #else cThreadLock DeletedRecordingsLock(&DeletedRecordings); for (cRecording* recording = DeletedRecordings.First(); recording; recording = DeletedRecordings.Next(recording)) #endif { if (uid == CreateStringHash(recording->FileName())) { if (Undelete(recording)) { INFOLOG("Recording \"%s\" undeleted", recording->FileName()); ret = VNSI_RET_OK; } else ret = VNSI_RET_ERROR; break; } } } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(ret); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } bool cVNSIClient::processRECORDINGS_DELETED_DeleteAll(cRequestPacket &req) /* OPCODE 185 */ { int ret = VNSI_RET_OK; #if VDRVERSNUM >= 20102 cLockFile LockFile(cVideoDirectory::Name()); #else cLockFile LockFile(VideoDirectory); #endif if (LockFile.Lock()) { #if VDRVERSNUM >= 20301 LOCK_DELETEDRECORDINGS_WRITE; for (cRecording *recording = DeletedRecordings->First(); recording; ) #else cThreadLock DeletedRecordingsLock(&DeletedRecordings); for (cRecording *recording = DeletedRecordings.First(); recording; ) #endif { #if VDRVERSNUM >= 20301 cRecording *next = DeletedRecordings->Next(recording); #else cRecording *next = DeletedRecordings.Next(recording); #endif #if VDRVERSNUM >= 20102 if (!cVideoDirectory::RemoveVideoFile(recording->FileName())) #else if (!RemoveVideoFile(recording->FileName())) #endif { ERRORLOG("Error while remove deleted recording (%s)", recording->FileName()); ret = VNSI_RET_ERROR; break; } else INFOLOG("Recording \"%s\" permanent deleted", recording->FileName()); recording = next; } #if VDRVERSNUM >= 20301 DeletedRecordings->Clear(); DeletedRecordings->Update(); #else DeletedRecordings.Clear(); DeletedRecordings.Update(); #endif } cResponsePacket resp; resp.init(req.getRequestID()); resp.add_U32(ret); resp.finalise(); m_socket.write(resp.getPtr(), resp.getLen()); return true; } // part of this method is taken from XVDR cString cVNSIClient::CreatePiconRef(const cChannel* channel) { int hash = 0; if(cSource::IsSat(channel->Source())) { int16_t pos = channel->Source() & cSource::st_Pos; hash = pos; #if VDRVERSNUM >= 20101 if(hash < 0) { hash = 3600 + hash; } #endif hash = hash << 16; } else if(cSource::IsCable(channel->Source())) hash = 0xFFFF0000; else if(cSource::IsTerr(channel->Source())) hash = 0xEEEE0000; else if(cSource::IsAtsc(channel->Source())) hash = 0xDDDD0000; cString serviceref = cString::sprintf("1_0_%i_%X_%X_%X_%X_0_0_0", cVNSIChannelFilter::IsRadio(channel) ? 2 : (channel->Vtype() == 27) ? 19 : 1, channel->Sid(), channel->Tid(), channel->Nid(), hash); return serviceref; } vdr-plugin-vnsiserver-1.5.2/vnsiclient.h000066400000000000000000000157411303643374600203620ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include #include #include #include #include "config.h" #include "cxsocket.h" #include "channelscancontrol.h" #include #include #define VNSI_EPG_AGAIN 1 #define VNSI_EPG_PAUSE 2 class cChannel; class cDevice; class cLiveStreamer; class cRequestPacket; class cResponsePacket; class cRecPlayer; class cCmdControl; class cVnsiOsdProvider; class CVNSITimers; class cVNSIClient : public cThread , public cStatus { const unsigned int m_Id; cxSocket m_socket; bool m_loggedIn = false; bool m_StatusInterfaceEnabled = false; cLiveStreamer *m_Streamer = nullptr; bool m_isStreaming = false; bool m_bSupportRDS = false; const cString m_ClientAddress; cRecPlayer *m_RecPlayer = nullptr; cCharSetConv m_toUTF8; uint32_t m_protocolVersion; cMutex m_msgLock; static cMutex m_timerLock; cVnsiOsdProvider *m_Osd = nullptr; CScanControl m_ChannelScanControl; static bool m_inhibidDataUpdates; typedef struct { int attempts = 0; time_t lastEvent = 0; time_t lastTrigger = 0; } sEpgUpdate; std::map m_epgUpdate; CVNSITimers &m_vnsiTimers; protected: bool processRequest(cRequestPacket &req); virtual void Action(void) override; virtual void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On) override; virtual void OsdStatusMessage(const char *Message) override; #if VDRVERSNUM >= 20104 virtual void ChannelChange(const cChannel *Channel) override; #endif public: cVNSIClient(int fd, unsigned int id, const char *ClientAdr, CVNSITimers &timers); virtual ~cVNSIClient(); cVNSIClient(const cVNSIClient &) = delete; cVNSIClient &operator=(const cVNSIClient &) = delete; void ChannelsChange(); void RecordingsChange(); void SignalTimerChange(); int EpgChange(); static bool InhibidDataUpdates() { return m_inhibidDataUpdates; } unsigned int GetID() { return m_Id; } protected: void SetLoggedIn(bool yesNo) { m_loggedIn = yesNo; } void SetStatusInterface(bool yesNo) { m_StatusInterfaceEnabled = yesNo; } bool StartChannelStreaming(cResponsePacket &resp, const cChannel *channel, int32_t priority, uint8_t timeshift, uint32_t timeout); void StopChannelStreaming(); private: typedef struct { bool automatic; bool radio; std::string name; } ChannelGroup; std::map m_channelgroups[2]; bool process_Login(cRequestPacket &r); bool process_GetTime(cRequestPacket &r); bool process_EnableStatusInterface(cRequestPacket &r); bool process_Ping(cRequestPacket &r); bool process_GetSetup(cRequestPacket &r); bool process_StoreSetup(cRequestPacket &r); bool processChannelStream_Open(cRequestPacket &r); bool processChannelStream_Close(cRequestPacket &req); bool processChannelStream_Seek(cRequestPacket &r); bool processRecStream_Open(cRequestPacket &r); bool processRecStream_Close(cRequestPacket &r); bool processRecStream_GetBlock(cRequestPacket &r); bool processRecStream_PositionFromFrameNumber(cRequestPacket &r); bool processRecStream_FrameNumberFromPosition(cRequestPacket &r); bool processRecStream_GetIFrame(cRequestPacket &r); bool processRecStream_GetLength(cRequestPacket &r); bool processCHANNELS_GroupsCount(cRequestPacket &r); bool processCHANNELS_ChannelsCount(cRequestPacket &r); bool processCHANNELS_GroupList(cRequestPacket &r); bool processCHANNELS_GetChannels(cRequestPacket &r); bool processCHANNELS_GetGroupMembers(cRequestPacket &r); bool processCHANNELS_GetCaids(cRequestPacket &r); bool processCHANNELS_GetWhitelist(cRequestPacket &r); bool processCHANNELS_GetBlacklist(cRequestPacket &r); bool processCHANNELS_SetWhitelist(cRequestPacket &r); bool processCHANNELS_SetBlacklist(cRequestPacket &r); void CreateChannelGroups(bool automatic); bool processTIMER_GetCount(cRequestPacket &r); bool processTIMER_Get(cRequestPacket &r); bool processTIMER_GetList(cRequestPacket &r); bool processTIMER_Add(cRequestPacket &r); bool processTIMER_Delete(cRequestPacket &r); bool processTIMER_Update(cRequestPacket &r); bool processTIMER_GetTypes(cRequestPacket &r); bool processRECORDINGS_GetDiskSpace(cRequestPacket &r); bool processRECORDINGS_GetCount(cRequestPacket &r); bool processRECORDINGS_GetList(cRequestPacket &r); bool processRECORDINGS_GetInfo(cRequestPacket &r); bool processRECORDINGS_Rename(cRequestPacket &r); bool processRECORDINGS_Delete(cRequestPacket &r); bool processRECORDINGS_Move(cRequestPacket &r); bool processRECORDINGS_GetEdl(cRequestPacket &r); bool processRECORDINGS_DELETED_Supported(cRequestPacket &r); bool processRECORDINGS_DELETED_GetCount(cRequestPacket &r); bool processRECORDINGS_DELETED_GetList(cRequestPacket &r); bool processRECORDINGS_DELETED_Delete(cRequestPacket &r); bool processRECORDINGS_DELETED_Undelete(cRequestPacket &r); bool processRECORDINGS_DELETED_DeleteAll(cRequestPacket &r); bool processEPG_GetForChannel(cRequestPacket &r); bool processSCAN_ScanSupported(cRequestPacket &r); bool processSCAN_GetSupportedTypes(cRequestPacket &r); bool processSCAN_GetCountries(cRequestPacket &r); bool processSCAN_GetSatellites(cRequestPacket &r); bool processSCAN_Start(cRequestPacket &r); bool processSCAN_Stop(cRequestPacket &r); bool Undelete(cRecording* recording); bool processOSD_Connect(cRequestPacket &req); bool processOSD_Disconnect(); bool processOSD_Hitkey(cRequestPacket &req); cString CreatePiconRef(const cChannel* channel); private: /** Static callback functions to interact with wirbelscan plugin over the plugin service interface */ friend class CScanControl; void processSCAN_SetPercentage(int percent); void processSCAN_SetSignalStrength(int strength, bool locked); void processSCAN_SetDeviceInfo(const char *Info); void processSCAN_SetTransponder(const char *Info); void processSCAN_NewChannel(const char *Name, bool isRadio, bool isEncrypted, bool isHD); void processSCAN_IsFinished(); void processSCAN_SetStatus(int status); }; vdr-plugin-vnsiserver-1.5.2/vnsicommand.h000066400000000000000000000153051303643374600205160ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_COMMAND_H #define VNSI_COMMAND_H /** Current VNSI Protocol Version number */ #define VNSI_PROTOCOLVERSION 10 /** Start of RDS support protocol Version */ #define VNSI_RDS_PROTOCOLVERSION 8 /** Minimum VNSI Protocol Version number */ #define VNSI_MIN_PROTOCOLVERSION 5 /** Packet types */ #define VNSI_CHANNEL_REQUEST_RESPONSE 1 #define VNSI_CHANNEL_STREAM 2 #define VNSI_CHANNEL_KEEPALIVE 3 #define VNSI_CHANNEL_NETLOG 4 #define VNSI_CHANNEL_STATUS 5 #define VNSI_CHANNEL_SCAN 6 #define VNSI_CHANNEL_OSD 7 /** Response packets operation codes */ #define CONFNAME_PMTTIMEOUT "PmtTimeout" #define CONFNAME_TIMESHIFT "Timeshift" #define CONFNAME_TIMESHIFTBUFFERSIZE "TimeshiftBufferSize" #define CONFNAME_TIMESHIFTBUFFERFILESIZE "TimeshiftBufferFileSize" #define CONFNAME_TIMESHIFTBUFFERDIR "TimeshiftBufferDir" #define CONFNAME_PLAYRECORDING "PlayRecording" #define CONFNAME_AVOIDEPGSCAN "AvoidEPGScan" #define CONFNAME_DISABLESCRAMBLETIMEOUT "DisableScrambleTimeout" #define CONFNAME_DISABLECAMBLACKLIST "DisableCamBlacklist" /* OPCODE 1 - 19: VNSI network functions for general purpose */ #define VNSI_LOGIN 1 #define VNSI_GETTIME 2 #define VNSI_ENABLESTATUSINTERFACE 3 #define VNSI_PING 7 #define VNSI_GETSETUP 8 #define VNSI_STORESETUP 9 /* OPCODE 20 - 39: VNSI network functions for live streaming */ #define VNSI_CHANNELSTREAM_OPEN 20 #define VNSI_CHANNELSTREAM_CLOSE 21 #define VNSI_CHANNELSTREAM_SEEK 22 /* OPCODE 40 - 59: VNSI network functions for recording streaming */ #define VNSI_RECSTREAM_OPEN 40 #define VNSI_RECSTREAM_CLOSE 41 #define VNSI_RECSTREAM_GETBLOCK 42 #define VNSI_RECSTREAM_POSTOFRAME 43 #define VNSI_RECSTREAM_FRAMETOPOS 44 #define VNSI_RECSTREAM_GETIFRAME 45 #define VNSI_RECSTREAM_GETLENGTH 46 /* OPCODE 60 - 79: VNSI network functions for channel access */ #define VNSI_CHANNELS_GETCOUNT 61 #define VNSI_CHANNELS_GETCHANNELS 63 #define VNSI_CHANNELGROUP_GETCOUNT 65 #define VNSI_CHANNELGROUP_LIST 66 #define VNSI_CHANNELGROUP_MEMBERS 67 #define VNSI_CHANNELS_GETCAIDS 68 #define VNSI_CHANNELS_GETWHITELIST 69 #define VNSI_CHANNELS_GETBLACKLIST 70 #define VNSI_CHANNELS_SETWHITELIST 71 #define VNSI_CHANNELS_SETBLACKLIST 72 /* OPCODE 80 - 99: VNSI network functions for timer access */ #define VNSI_TIMER_GETCOUNT 80 #define VNSI_TIMER_GET 81 #define VNSI_TIMER_GETLIST 82 #define VNSI_TIMER_ADD 83 #define VNSI_TIMER_DELETE 84 #define VNSI_TIMER_UPDATE 85 #define VNSI_TIMER_GETTYPES 86 /* OPCODE 100 - 119: VNSI network functions for recording access */ #define VNSI_RECORDINGS_DISKSIZE 100 #define VNSI_RECORDINGS_GETCOUNT 101 #define VNSI_RECORDINGS_GETLIST 102 #define VNSI_RECORDINGS_RENAME 103 #define VNSI_RECORDINGS_DELETE 104 #define VNSI_RECORDINGS_GETEDL 105 /* OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ #define VNSI_EPG_GETFORCHANNEL 120 /* OPCODE 140 - 159: VNSI network functions for channel scanning */ #define VNSI_SCAN_SUPPORTED 140 #define VNSI_SCAN_GETCOUNTRIES 141 #define VNSI_SCAN_GETSATELLITES 142 #define VNSI_SCAN_START 143 #define VNSI_SCAN_STOP 144 #define VNSI_SCAN_SUPPORTED_TYPES 145 /* OPCODE 160 - 179: VNSI network functions for channel scanning */ #define VNSI_OSD_CONNECT 160 #define VNSI_OSD_DISCONNECT 161 #define VNSI_OSD_HITKEY 162 /* OPCODE 180 - 189: VNSI network functions for deleted recording access */ #define VNSI_RECORDINGS_DELETED_ACCESS_SUPPORTED 180 #define VNSI_RECORDINGS_DELETED_GETCOUNT 181 #define VNSI_RECORDINGS_DELETED_GETLIST 182 #define VNSI_RECORDINGS_DELETED_DELETE 183 #define VNSI_RECORDINGS_DELETED_UNDELETE 184 #define VNSI_RECORDINGS_DELETED_DELETE_ALL 185 /** Stream packet types (server -> client) */ #define VNSI_STREAM_CHANGE 1 #define VNSI_STREAM_STATUS 2 #define VNSI_STREAM_QUEUESTATUS 3 #define VNSI_STREAM_MUXPKT 4 #define VNSI_STREAM_SIGNALINFO 5 #define VNSI_STREAM_CONTENTINFO 6 #define VNSI_STREAM_BUFFERSTATS 7 #define VNSI_STREAM_REFTIME 8 /** Scan packet types (server -> client) */ #define VNSI_SCANNER_PERCENTAGE 1 #define VNSI_SCANNER_SIGNAL 2 #define VNSI_SCANNER_DEVICE 3 #define VNSI_SCANNER_TRANSPONDER 4 #define VNSI_SCANNER_NEWCHANNEL 5 #define VNSI_SCANNER_FINISHED 6 #define VNSI_SCANNER_STATUS 7 /** OSD packet types (server -> client) */ #define VNSI_OSD_MOVEWINDOW 1 #define VNSI_OSD_CLEAR 2 #define VNSI_OSD_OPEN 3 #define VNSI_OSD_CLOSE 4 #define VNSI_OSD_SETPALETTE 5 #define VNSI_OSD_SETBLOCK 6 /** Status packet types (server -> client) */ #define VNSI_STATUS_TIMERCHANGE 1 #define VNSI_STATUS_RECORDING 2 #define VNSI_STATUS_MESSAGE 3 #define VNSI_STATUS_CHANNELCHANGE 4 #define VNSI_STATUS_RECORDINGSCHANGE 5 #define VNSI_STATUS_EPGCHANGE 6 /** Channel search signal supported flags */ #define VNSI_SCAN_SUPPORT_DVB_T 0x01 #define VNSI_SCAN_SUPPORT_DVB_C 0x02 #define VNSI_SCAN_SUPPORT_DVB_S 0x04 #define VNSI_SCAN_SUPPORT_ANALOG_TV 0x08 #define VNSI_SCAN_SUPPORT_ANALOG_RADIO 0x10 #define VNSI_SCAN_SUPPORT_ATSC 0x20 /** Timer */ #define VNSI_TIMER_TYPE_MAN 1 #define VNSI_TIMER_TYPE_MAN_REPEAT 2 #define VNSI_TIMER_TYPE_EPG 3 #define VNSI_TIMER_TYPE_VPS 4 #define VNSI_TIMER_TYPE_EPG_SEARCH 5 /** Packet return codes */ #define VNSI_RET_OK 0 #define VNSI_RET_RECRUNNING 1 #define VNSI_RET_NOTSUPPORTED 995 #define VNSI_RET_DATAUNKNOWN 996 #define VNSI_RET_DATALOCKED 997 #define VNSI_RET_DATAINVALID 998 #define VNSI_RET_ERROR 999 #endif // VNSI_COMMAND_H vdr-plugin-vnsiserver-1.5.2/vnsiosd.c000066400000000000000000000225031303643374600176560ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "vnsiosd.h" #include "config.h" #include "vnsicommand.h" #include "responsepacket.h" #include "vnsi.h" #include #include #include #include #include #include "cxsocket.h" #define ACTION_NONE 0 #define ACTION_MOVE_LEFT 1 #define ACTION_MOVE_RIGHT 2 #define ACTION_MOVE_UP 3 #define ACTION_MOVE_DOWN 4 #define ACTION_SELECT_ITEM 7 #define ACTION_PREVIOUS_MENU 10 #define REMOTE_0 58 // remote keys 0-9. are used by multiple windows #define REMOTE_1 59 // for example in videoFullScreen.xml window id=2005 you can #define REMOTE_2 60 // enter time (mmss) to jump to particular point in the movie #define REMOTE_3 61 #define REMOTE_4 62 // with spincontrols you can enter 3digit number to quickly set #define REMOTE_5 63 // spincontrol to desired value #define REMOTE_6 64 #define REMOTE_7 65 #define REMOTE_8 66 #define REMOTE_9 67 #define ACTION_NAV_BACK 92 #define ACTION_TELETEXT_RED 215 // Teletext Color buttons to control TopText #define ACTION_TELETEXT_GREEN 216 // " " " " " " #define ACTION_TELETEXT_YELLOW 217 // " " " " " " #define ACTION_TELETEXT_BLUE 218 // " " " " " " // --- cVnsiOsd ----------------------------------------------------------- #define MAXNUMWINDOWS 16 // OSD windows are counted 0...15 #define MAXOSDMEMORY 1024000 // number of bytes available to the OSD class cVnsiOsd : public cOsd { private: int osdMem; bool shown; void Cmd(int cmd, int wnd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, const void *data = NULL, int size = 0); protected: virtual void SetActive(bool On); public: cVnsiOsd(int Left, int Top, uint Level); virtual ~cVnsiOsd(); virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas); virtual eOsdError SetAreas(const tArea *Areas, int NumAreas); virtual void Flush(void); }; cVnsiOsd::cVnsiOsd(int Left, int Top, uint Level) :cOsd(Left, Top, Level) { shown = false; osdMem = MAXOSDMEMORY; DEBUGLOG("osd created. left: %d, top: %d", Left, Top); } cVnsiOsd::~cVnsiOsd() { SetActive(false); } void cVnsiOsd::SetActive(bool On) { if (On != Active()) { cOsd::SetActive(On); if (On) { // must clear all windows here to avoid flashing effects - doesn't work if done // in Flush() only for the windows that are actually used... for (int i = 0; i < MAXNUMWINDOWS; i++) { Cmd(VNSI_OSD_CLEAR, i); } if (GetBitmap(0)) // only flush here if there are already bitmaps Flush(); } else if (shown) { for (int i = 0; GetBitmap(i); i++) { Cmd(VNSI_OSD_CLOSE, i); } shown = false; } } } eOsdError cVnsiOsd::CanHandleAreas(const tArea *Areas, int NumAreas) { eOsdError Result = cOsd::CanHandleAreas(Areas, NumAreas); if (Result == oeOk) { if (NumAreas > MAXNUMWINDOWS) return oeTooManyAreas; int TotalMemory = 0; for (int i = 0; i < NumAreas; i++) { if (Areas[i].bpp != 1 && Areas[i].bpp != 2 && Areas[i].bpp != 4 && Areas[i].bpp != 8) return oeBppNotSupported; if ((Areas[i].Width() & (8 / Areas[i].bpp - 1)) != 0) return oeWrongAlignment; if (Areas[i].Width() < 1 || Areas[i].Height() < 1 || Areas[i].Width() > 720 || Areas[i].Height() > 576) return oeWrongAreaSize; TotalMemory += Areas[i].Width() * Areas[i].Height() / (8 / Areas[i].bpp); } if (TotalMemory > osdMem) return oeOutOfMemory; } return Result; } eOsdError cVnsiOsd::SetAreas(const tArea *Areas, int NumAreas) { if (shown) { for (int i = 0; GetBitmap(i); i++) { Cmd(VNSI_OSD_CLOSE, i); } shown = false; } return cOsd::SetAreas(Areas, NumAreas); } void cVnsiOsd::Cmd(int cmd, int wnd, int color, int x0, int y0, int x1, int y1, const void *data, int size) { DEBUGLOG("OSD: cmd: %d, wnd: %d, color: %d, x0: %d, y0: %d, x1: %d, y1: %d", cmd, wnd, color, x0, y0, x1, y1); cVnsiOsdProvider::SendOsdPacket(cmd, wnd, color, x0, y0, x1, y1, data, size); } void cVnsiOsd::Flush(void) { if (!Active()) return; cBitmap *Bitmap; bool full = cVnsiOsdProvider::IsRequestFull(); for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { uint8_t reset = !shown || full; Cmd(VNSI_OSD_OPEN, i, Bitmap->Bpp(), Left() + Bitmap->X0(), Top() + Bitmap->Y0(), Left() + Bitmap->X0() + Bitmap->Width() - 1, Top() + Bitmap->Y0() + Bitmap->Height() - 1, (void *)&reset, 1); int x1 = 0, y1 = 0, x2 = 0, y2 = 0; if (!shown || Bitmap->Dirty(x1, y1, x2, y2) || full) { if (!shown || full) { x1 = y1 = 0; x2 = Bitmap->Width() - 1; y2 = Bitmap->Height() - 1; } // commit colors: int NumColors; const tColor *Colors = Bitmap->Colors(NumColors); if (Colors) { Cmd(VNSI_OSD_SETPALETTE, i, 0, NumColors, 0, 0, 0, Colors, NumColors*sizeof(tColor)); } // commit modified data: int size = (y2-y1) * Bitmap->Width() + (x2-x1+1); Cmd(VNSI_OSD_SETBLOCK, i, Bitmap->Width(), x1, y1, x2, y2, Bitmap->Data(x1, y1), size); } Bitmap->Clean(); } if (!shown) { // Showing the windows in a separate loop to avoid seeing them come up one after another for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { Cmd(VNSI_OSD_MOVEWINDOW, i, 0, Left() + Bitmap->X0(), Top() + Bitmap->Y0()); } shown = true; } } // --- cVnsiOsdProvider ------------------------------------------------------- cxSocket *cVnsiOsdProvider::m_Socket; cMutex cVnsiOsdProvider::m_Mutex; bool cVnsiOsdProvider::m_RequestFull; cVnsiOsdProvider::cVnsiOsdProvider(cxSocket *socket) { cMutexLock lock(&m_Mutex); INFOLOG("new osd provider"); m_Socket = socket; m_RequestFull = true; if (VNSIServerConfig.pDevice) ((cDvbVnsiDevice*)VNSIServerConfig.pDevice)->ActivateDecoder(true); } cVnsiOsdProvider::~cVnsiOsdProvider() { cMutexLock lock(&m_Mutex); m_Socket = NULL; if (VNSIServerConfig.pDevice) ((cDvbVnsiDevice*)VNSIServerConfig.pDevice)->ActivateDecoder(false); } cOsd *cVnsiOsdProvider::CreateOsd(int Left, int Top, uint Level) { cMutexLock lock(&m_Mutex); return new cVnsiOsd(Left, Top, Level); } void cVnsiOsdProvider::SendOsdPacket(int cmd, int wnd, int color, int x0, int y0, int x1, int y1, const void *data, int size) { cMutexLock lock(&m_Mutex); if (!m_Socket) return; cResponsePacket m_OsdPacket; m_OsdPacket.initOsd(cmd, wnd, color, x0, y0, x1, y1); m_OsdPacket.setLen(m_OsdPacket.getOSDHeaderLength() + size); m_OsdPacket.finaliseOSD(); m_Socket->LockWrite(); m_Socket->write(m_OsdPacket.getPtr(), m_OsdPacket.getOSDHeaderLength(), -1, (size > 0) ? true: false); if (size) m_Socket->write(data, size); m_Socket->UnlockWrite(); } bool cVnsiOsdProvider::IsRequestFull() { cMutexLock lock(&m_Mutex); bool ret = m_RequestFull; m_RequestFull = false; return ret; } void cVnsiOsdProvider::SendKey(unsigned int key) { if (!cOsd::IsOpen()) { cRemote::Put(kMenu); return; } switch (key) { case ACTION_MOVE_UP: cRemote::Put(kUp); break; case ACTION_MOVE_DOWN: cRemote::Put(kDown); break; case ACTION_MOVE_LEFT: cRemote::Put(kLeft); break; case ACTION_MOVE_RIGHT: cRemote::Put(kRight); break; case ACTION_PREVIOUS_MENU: cRemote::Put(kMenu); break; case ACTION_NAV_BACK: cRemote::Put(kBack); break; case ACTION_SELECT_ITEM: cRemote::Put(kOk); break; case ACTION_TELETEXT_RED: cRemote::Put(kRed); break; case ACTION_TELETEXT_BLUE: cRemote::Put(kBlue); break; case ACTION_TELETEXT_YELLOW: cRemote::Put(kYellow); break; case ACTION_TELETEXT_GREEN: cRemote::Put(kGreen); break; case REMOTE_0: cRemote::Put(k0); break; case REMOTE_1: cRemote::Put(k1); break; case REMOTE_2: cRemote::Put(k2); break; case REMOTE_3: cRemote::Put(k3); break; case REMOTE_4: cRemote::Put(k4); break; case REMOTE_5: cRemote::Put(k5); break; case REMOTE_6: cRemote::Put(k6); break; case REMOTE_7: cRemote::Put(k7); break; case REMOTE_8: cRemote::Put(k8); break; case REMOTE_9: cRemote::Put(k9); break; default: break; } } vdr-plugin-vnsiserver-1.5.2/vnsiosd.h000066400000000000000000000025331303643374600176640ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2012 Team XBMC * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include class cxSocket; class cVnsiOsdProvider : public cOsdProvider { public: cVnsiOsdProvider(cxSocket *socket); virtual ~cVnsiOsdProvider(); virtual cOsd *CreateOsd(int Left, int Top, uint Level); static void SendOsdPacket(int cmd, int wnd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, const void *data = NULL, int size = 0); static bool IsRequestFull(); static void SendKey(unsigned int key); private: static cxSocket *m_Socket; static cMutex m_Mutex; static bool m_RequestFull; }; vdr-plugin-vnsiserver-1.5.2/vnsiserver.c000066400000000000000000000121721303643374600204000ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "vnsiserver.h" #include "vnsiclient.h" #include "vnsi.h" #include "channelfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include unsigned int cVNSIServer::m_IdCnt = 0; class cAllowedHosts : public cSVDRPhosts { public: cAllowedHosts(const cString& AllowedHostsFile) { if (!Load(AllowedHostsFile, true, true)) { ERRORLOG("Invalid or missing '%s'. falling back to 'svdrphosts.conf'.", *AllowedHostsFile); cString Base = cString::sprintf("%s/../svdrphosts.conf", *VNSIServerConfig.ConfigDirectory); if (!Load(Base, true, true)) { ERRORLOG("Invalid or missing %s. Adding 127.0.0.1 to list of allowed hosts.", *Base); cSVDRPhost *localhost = new cSVDRPhost; if (localhost->Parse("127.0.0.1")) Add(localhost); else delete localhost; } } } }; cVNSIServer::cVNSIServer(int listenPort) : cThread("VNSI Server") { m_ServerPort = listenPort; Start(); INFOLOG("VNSI Server started"); INFOLOG("Channel streaming timeout: %i seconds", VNSIServerConfig.stream_timeout); return; } cVNSIServer::~cVNSIServer() { Cancel(); m_Status.Shutdown(); m_timers.Shutdown(); INFOLOG("VNSI Server stopped"); } void cVNSIServer::NewClientConnected(int fd) { char buf[64]; struct sockaddr_in sin; socklen_t len = sizeof(sin); if (getpeername(fd, (struct sockaddr *)&sin, &len)) { ERRORLOG("getpeername() failed, dropping new incoming connection %d", m_IdCnt); close(fd); return; } cAllowedHosts AllowedHosts(m_AllowedHostsFile); if (!AllowedHosts.Acceptable(sin.sin_addr.s_addr)) { ERRORLOG("Address not allowed to connect (%s)", *m_AllowedHostsFile); close(fd); return; } if (fcntl(fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK) == -1) { ERRORLOG("Error setting control socket to nonblocking mode"); close(fd); return; } int val = 1; setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); #ifdef SOL_TCP val = 30; setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &val, sizeof(val)); val = 15; setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &val, sizeof(val)); val = 5; setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &val, sizeof(val)); val = 1; setsockopt(fd, SOL_TCP, TCP_NODELAY, &val, sizeof(val)); #endif INFOLOG("Client with ID %d connected: %s", m_IdCnt, cxSocket::ip2txt(sin.sin_addr.s_addr, sin.sin_port, buf)); m_Status.AddClient(fd, m_IdCnt, cxSocket::ip2txt(sin.sin_addr.s_addr, sin.sin_port, buf), m_timers); m_IdCnt++; } void cVNSIServer::Action(void) { fd_set fds; struct timeval tv; if(*VNSIServerConfig.ConfigDirectory) { m_AllowedHostsFile = cString::sprintf("%s/" ALLOWED_HOSTS_FILE, *VNSIServerConfig.ConfigDirectory); } else { ERRORLOG("cVNSIServer: missing ConfigDirectory!"); m_AllowedHostsFile = cString::sprintf("/video/" ALLOWED_HOSTS_FILE); } VNSIChannelFilter.Load(); VNSIChannelFilter.SortChannels(); m_Status.Init(&m_timers); m_timers.Load(); m_timers.Start(); m_ServerFD = socket(AF_INET, SOCK_STREAM, 0); if(m_ServerFD == -1) return; fcntl(m_ServerFD, F_SETFD, fcntl(m_ServerFD, F_GETFD) | FD_CLOEXEC); int one = 1; setsockopt(m_ServerFD, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); struct sockaddr_in s; memset(&s, 0, sizeof(s)); s.sin_family = AF_INET; s.sin_port = htons(m_ServerPort); int x = bind(m_ServerFD, (struct sockaddr *)&s, sizeof(s)); if (x < 0) { close(m_ServerFD); INFOLOG("Unable to start VNSI Server, port already in use ?"); m_ServerFD = -1; return; } listen(m_ServerFD, 10); while (Running()) { FD_ZERO(&fds); FD_SET(m_ServerFD, &fds); tv.tv_sec = 0; tv.tv_usec = 250*1000; int r = select(m_ServerFD + 1, &fds, NULL, NULL, &tv); if (r == -1) { ERRORLOG("failed during select"); continue; } if (r == 0) { continue; } int fd = accept(m_ServerFD, 0, 0); if (fd >= 0) { NewClientConnected(fd); } else { ERRORLOG("accept failed"); } } return; } vdr-plugin-vnsiserver-1.5.2/vnsiserver.h000066400000000000000000000025721303643374600204100ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2010 Alwin Esch (Team XBMC) * Copyright (C) 2010, 2011 Alexander Pipelka * Copyright (C) 2015 Team KODI * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #ifndef VNSI_SERVER_H #define VNSI_SERVER_H #include #include "config.h" #include "status.h" #include "vnsitimer.h" class cVNSIClient; class cVNSIServer : public cThread { protected: virtual void Action(void); void NewClientConnected(int fd); int m_ServerPort; int m_ServerFD; cString m_AllowedHostsFile; CVNSITimers m_timers; cVNSIStatus m_Status; static unsigned int m_IdCnt; public: cVNSIServer(int listenPort); virtual ~cVNSIServer(); }; #endif // VNSI_SERVER_H vdr-plugin-vnsiserver-1.5.2/vnsiserver/000077500000000000000000000000001303643374600202315ustar00rootroot00000000000000vdr-plugin-vnsiserver-1.5.2/vnsiserver/allowed_hosts.conf000066400000000000000000000007251303643374600237530ustar00rootroot00000000000000# # allowed_hosts.conf This file describes a number of host addresses that # are allowed to connect to the streamdev server running # with the Video Disk Recorder (VDR) on this system. # Syntax: # # IP-Address[/Netmask] # 127.0.0.1 # always accept localhost 192.168.0.0/24 # any host on the local net #204.152.189.113 # a specific host #0.0.0.0/0 # any host on any net (USE THIS WITH CARE!) vdr-plugin-vnsiserver-1.5.2/vnsitimer.c000066400000000000000000000317611303643374600202170ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2016 Team Kodi * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #include "config.h" #include "hash.h" #include "time.h" #include #include #include #include #include #include #include #include #include "vnsitimer.h" CVNSITimers::CVNSITimers() : cThread("VNSITimers") { m_doScan = false; m_state = 0; } void CVNSITimers::Load() { cString filename; std::string line; std::ifstream rfile; CVNSITimer timer; cMutexLock lock(&m_timerLock); filename = cString::sprintf("%s/timers.vnsi", *VNSIServerConfig.ConfigDirectory); rfile.open(filename); m_timers.clear(); if (rfile.is_open()) { while(getline(rfile,line)) { // timer name size_t pos = line.find(";"); if(pos == line.npos) { continue; } timer.m_name = line.substr(0, pos); // channelUID line = line.substr(pos+1); pos = line.find(";"); if(pos == line.npos) { continue; } char *pend; std::string channeluid = line.substr(0, pos); timer.m_channelUID = strtol(channeluid.c_str(), &pend, 10); const cChannel *channel = FindChannelByUID(timer.m_channelUID); if (!channel) { continue; } timer.m_channelID = channel->GetChannelID(); // enabled line = line.substr(pos+1); pos = line.find(";"); if(pos == line.npos) { continue; } std::string enabled = line.substr(0, pos); timer.m_enabled = strtol(enabled.c_str(), &pend, 10); // priority line = line.substr(pos+1); pos = line.find(";"); if(pos == line.npos) { continue; } std::string priority = line.substr(0, pos); timer.m_priority = strtol(priority.c_str(), &pend, 10); // lifetime line = line.substr(pos+1); pos = line.find(";"); if(pos == line.npos) { continue; } std::string lifetime = line.substr(0, pos); timer.m_lifetime = strtol(lifetime.c_str(), &pend, 10); // searchstring line = line.substr(pos+1); pos = line.find(";"); if (pos == line.npos) { timer.m_search = line.substr(pos+1); } else { timer.m_search = line.substr(0, pos); } // created timers if (pos != line.npos) { line = line.substr(pos+1); while ((pos = line.find(",")) != line.npos) { std::string tmp = line.substr(0, pos); time_t starttime = strtol(tmp.c_str(), &pend, 10); timer.m_timersCreated.push_back(starttime); line = line.substr(pos+1); } } timer.m_id = ++m_nextId; m_timers.emplace_back(std::move(timer)); } rfile.close(); } } void CVNSITimers::Save() { cString filename; std::ofstream wfile; filename = cString::sprintf("%s/timers.vnsi", *VNSIServerConfig.ConfigDirectory); cMutexLock lock(&m_timerLock); wfile.open(filename); if(wfile.is_open()) { for (auto &timer : m_timers) { wfile << timer.m_name << ';' << timer.m_channelUID << ';' << timer.m_enabled << ';' << timer.m_priority << ';' << timer.m_lifetime << ';' << timer.m_search << ';'; for (auto &starttime : timer.m_timersCreated) { wfile << starttime << ','; } wfile << '\n'; } wfile.close(); } } void CVNSITimers::Add(CVNSITimer &&timer) { const cChannel *channel = FindChannelByUID(timer.m_channelUID); if (!channel) return; timer.m_channelID = channel->GetChannelID(); timer.m_id = ++m_nextId; cMutexLock lock(&m_timerLock); m_timers.emplace_back(std::move(timer)); m_state++; Save(); } void CVNSITimers::Scan() { m_doScan = true; } size_t CVNSITimers::GetTimersCount() { cMutexLock lock(&m_timerLock); return m_timers.size(); } std::vector CVNSITimers::GetTimers() { cMutexLock lock(&m_timerLock); return m_timers; } bool CVNSITimers::GetTimer(int id, CVNSITimer &timer) { cMutexLock lock(&m_timerLock); id &= ~VNSITIMER_MASK; for (auto &searchtimer : m_timers) { if (searchtimer.m_id == id) { timer = searchtimer; return true; } } return false; } bool CVNSITimers::UpdateTimer(int id, CVNSITimer &timer) { cMutexLock lock(&m_timerLock); id &= ~VNSITIMER_MASK; for (auto &searchtimer : m_timers) { if (searchtimer.m_id == id) { const cChannel *channel = FindChannelByUID(timer.m_channelUID); if (!channel) return false; if (timer.m_channelUID != searchtimer.m_channelUID || timer.m_search != searchtimer.m_search) { DeleteChildren(searchtimer); } timer.m_id = id; timer.m_channelID = channel->GetChannelID(); timer.m_timersCreated = searchtimer.m_timersCreated; searchtimer = timer; m_state++; Save(); return true; } } return false; } bool CVNSITimers::DeleteTimer(int id) { cMutexLock lock(&m_timerLock); id &= ~VNSITIMER_MASK; std::vector::iterator it; for (it = m_timers.begin(); it != m_timers.end(); ++it) { if (it->m_id == id) { DeleteChildren(*it); m_timers.erase(it); m_state++; Save(); return true; } } return false; } void CVNSITimers::DeleteChildren(CVNSITimer &vnsitimer) { #if VDRVERSNUM >= 20301 cStateKey timerState; timerState.Reset(); bool modified = false; cTimers *Timers = cTimers::GetTimersWrite(timerState); if (Timers) { Timers->SetExplicitModify(); cTimer *timer = Timers->First(); while (timer) { if (!timer->Channel()) continue; timer->Matches(); cTimer* nextTimer = Timers->Next(timer); for (auto &starttime : vnsitimer.m_timersCreated) { if (vnsitimer.m_channelID == timer->Channel()->GetChannelID() && timer->StartTime() == starttime && !timer->Recording()) { Timers->Del(timer); Timers->SetModified(); modified = true; break; } } timer = nextTimer; } timerState.Remove(modified); vnsitimer.m_timersCreated.clear(); } #endif } int CVNSITimers::GetParent(const cTimer *timer) { if (!timer->Channel()) return 0; timer->Matches(); cMutexLock lock(&m_timerLock); for (auto &searchTimer : m_timers) { if (searchTimer.m_channelID == timer->Channel()->GetChannelID()) { for (auto &starttime : searchTimer.m_timersCreated) { if (timer->StartTime() == starttime) { return searchTimer.m_id | VNSITIMER_MASK; } } } } return 0; } bool CVNSITimers::IsChild(int id, time_t starttime) { cMutexLock lock(&m_timerLock); for (auto &timer : m_timers) { if (timer.m_id != id) continue; for (auto &time : timer.m_timersCreated) { if (time == starttime) return true; } } return false; } bool CVNSITimers::StateChange(int &state) { if (state != m_state) { state = m_state; return true; } return false; } std::string CVNSITimers::Convert(std::string search) { std::string regex; size_t pos = search.find("*"); while (pos != search.npos) { regex += search.substr(0, pos); regex += ".*"; search = search.substr(pos + 1); pos = search.find("*"); } regex += search; return regex; } bool CVNSITimers::IsDuplicateEvent(cTimers *timers, const cEvent *event) { for (const cTimer *timer = timers->First(); timer; timer = timers->Next(timer)) { const cEvent *timerEvent = timer->Event(); if (timerEvent == nullptr) continue; if (timer->HasFlags(tfActive) && strcmp(timerEvent->Title(), event->Title()) == 0) { if (timerEvent->ShortText() != nullptr && event->ShortText() != nullptr && strcmp(timerEvent->ShortText(), event->ShortText()) == 0) return true; if (abs(difftime(timerEvent->StartTime(), event->StartTime())) < 300) return true; } } return false; } void CVNSITimers::Action() { #if VDRVERSNUM >= 20301 bool modified; cStateKey timerState; // set thread priority (nice level) SetPriority(1); while (Running()) { if (!m_doScan) { usleep(1000*1000); continue; } m_doScan = false; std::vector timers; { cMutexLock lock(&m_timerLock); timers = m_timers; } cTimers *Timers = cTimers::GetTimersWrite(timerState); if (!Timers) continue; Timers->SetExplicitModify(); modified = false; cStateKey SchedulesStateKey(true); const cSchedules *schedules = cSchedules::GetSchedulesRead(SchedulesStateKey); if (schedules) { for (const cSchedule *schedule = schedules->First(); schedule; schedule = schedules->Next(schedule)) { for (auto &searchTimer : timers) { if (!searchTimer.m_enabled) continue; if (!(searchTimer.m_channelID == schedule->ChannelID())) continue; for (const cEvent *event = schedule->Events()->First(); event; event = schedule->Events()->Next(event)) { if (event->EndTime() < time(nullptr)) continue; std::string title(event->Title()); std::smatch m; std::regex e(Convert(searchTimer.m_search)); if (std::regex_search(title, m, e, std::regex_constants::match_not_null)) { bool duplicate = false; LOCK_RECORDINGS_READ; for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording)) { if (recording->Info() != nullptr) { if (strcmp(recording->Info()->Title(), event->Title()) == 0) { if (recording->Info()->ShortText() != nullptr && event->ShortText() != nullptr && strcmp(recording->Info()->ShortText(), event->ShortText()) == 0) { duplicate = true; break; } } } if (abs(difftime(event->StartTime(), recording->Start())) < 300) { duplicate = true; break; } } if (duplicate) continue; if (IsDuplicateEvent(Timers, event)) continue; cTimer *newTimer = new cTimer(event); if (!Setup.MarginStart) { unsigned int start = newTimer->Start(); if (start < searchTimer.m_marginStart) { newTimer->SetDay(cTimer::IncDay(newTimer->Day(), -1)); start = 24*3600 - (searchTimer.m_marginStart - start); } else start -= searchTimer.m_marginStart; newTimer->SetStart(start); } if (!Setup.MarginStop) { unsigned int stop = newTimer->Stop(); if (stop + searchTimer.m_marginEnd >= 24*3600) { newTimer->SetDay(cTimer::IncDay(newTimer->Day(), 1)); stop = stop + searchTimer.m_marginEnd - 24*3600; } else stop += searchTimer.m_marginEnd; newTimer->SetStop(stop); } if (IsChild(searchTimer.m_id, newTimer->StartTime())) { delete newTimer; continue; } Timers->Add(newTimer); modified = true; { cMutexLock lock(&m_timerLock); for (auto &origTimer : m_timers) { if (origTimer.m_id == searchTimer.m_id) { origTimer.m_timersCreated.push_back(newTimer->StartTime()); Save(); break; } } } } } } } } if (modified) Timers->SetModified(); timerState.Remove(modified); SchedulesStateKey.Remove(modified); } #endif } void CVNSITimers::Shutdown() { Cancel(5); m_timers.clear(); } vdr-plugin-vnsiserver-1.5.2/vnsitimer.h000066400000000000000000000041161303643374600202160ustar00rootroot00000000000000/* * vdr-plugin-vnsi - KODI server plugin for VDR * * Copyright (C) 2005-2016 Team XBMC * * http://kodi.tv * * 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 KODI; see the file COPYING. If not, see * . * */ #pragma once #include "stdint.h" #include #include #include #include #include class cEvent; class cTimers; class cTimer; class CVNSITimer { public: int m_id; std::string m_name; uint32_t m_channelUID; int32_t m_enabled; int32_t m_priority; int32_t m_lifetime; uint32_t m_marginStart; uint32_t m_marginEnd; std::string m_search; tChannelID m_channelID; std::vector m_timersCreated; }; class CVNSITimers : public cThread { public: CVNSITimers(); void Load(); void Save(); void Shutdown(); void Add(CVNSITimer &&timer); void Scan(); size_t GetTimersCount(); bool StateChange(int &state); std::vector GetTimers(); bool GetTimer(int id, CVNSITimer &timer); bool UpdateTimer(int id, CVNSITimer &timer); bool DeleteTimer(int id); int GetParent(const cTimer *timer); static constexpr uint32_t VNSITIMER_MASK = 0xF0000000; protected: virtual void Action(void) override; std::string Convert(std::string search); bool IsDuplicateEvent(cTimers *timers, const cEvent *event); void DeleteChildren(CVNSITimer &vnsitimer); bool IsChild(int id, time_t starttime); std::vector m_timers; std::atomic_bool m_doScan; std::atomic_int m_state; cMutex m_timerLock; int m_nextId = 0; }; vdr-plugin-vnsiserver-1.5.2/wirbelscan_services.h000066400000000000000000000313461303643374600222370ustar00rootroot00000000000000/* * wirbelscan_services.h * * Copyright (C) 2010 Winfried Koehler * * 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 * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * The author can be reached at: handygewinnspiel AT gmx DOT de * * The project's page is http://wirbel.htpc-forum.de/wirbelscan/index2.html */ #ifndef __WIRBELSCAN_SERVICES_H__ #define __WIRBELSCAN_SERVICES_H__ /******************************************************************** * * wirbelscans plugin service interface * * see http://wirbel.htpc-forum.de/wirbelscan/vdr-servdemo-0.0.1.tgz * for example on usage. * *******************************************************************/ namespace WIRBELSCAN_SERVICE { /* begin of namespace. to use this header file: * #include "../wirbelscan/wirbelscan_services.h" * using namespace WIRBELSCAN_SERVICE; */ /* --- service(s) version ---------------------------------------------------- */ #define SPlugin "wirbelscan_" // prefix #define SInfo "GetVersion" // plugin version and service api #define SCommand "DoCmd#0001" // command api 0001 #define SStatus "Status#0002" // query status #define SSetup "Setup#0001" // get/set setup, GetSetup#XXXX/SetSetup#XXXX #define SCountry "Country#0001" // get list of country IDs and Names #define SSat "Sat#0001" // get list of satellite IDs and Names #define SUser "User#0002" // get/set single user transponder, GetUser#XXXX/SetUser#XXXX /* --- wirbelscan_GetVersion ------------------------------------------------- * Query wirbelscans versions, will fail only if plugin version doesnt support service at all. */ typedef struct { const char * PluginVersion; // plugin version const char * CommandVersion; // commands service version const char * StatusVersion; // status service version const char * SetupVersion; // get/put setup service version const char * CountryVersion; // country ID list version const char * SatVersion; // satellite ID list version const char * UserVersion; // user transponder api version, 0.0.5-pre12b or higher. const char * reserved2; // reserved, dont use. const char * reserved3; // reserved, dont use. } cWirbelscanInfo; /* --- wirbelscan_DoCmd ------------------------------------------------------ * Execute commands. */ typedef enum { CmdStartScan = 0, // start scanning CmdStopScan = 1, // stop scanning CmdStore = 2, // store current setup } s_cmd; typedef struct { s_cmd cmd; // see above. bool replycode; // false, if unsuccessful. } cWirbelscanCmd; /* --- wirbelscan_Status ----------------------------------------------------- * Query Status. Use this to build up your osd information displayed to user. */ typedef enum { StatusUnknown = 0, // no status information available, try again later. StatusScanning = 1, // scan in progress. StatusStopped = 2, // no scan in progress (not started, finished or stopped). StatusBusy = 3, // plugin is busy, try again later. } cStatus; typedef struct { cStatus status; // see above. char curr_device[256]; // name of current device. meaningless, if (status != StatusScanning) uint16_t progress; // progress by means of "percent of predefined transponders". NOTE: will differ in terms of time. meaningless, if (status != StatusScanning) uint16_t strength; // current signal strength as reported from device. NOTE: updated only after switching to new transponder. meaningless, if (status != StatusScanning) char transponder[256]; // current transponder. meaningless, if (status != StatusScanning) uint16_t numChannels; // current number of (all) channels, including those which are new. meaningless, if (status != StatusScanning) uint16_t newChannels; // number of channels found during this scan. meaningless, if (status != StatusScanning) uint16_t nextTransponders; // number of transponders still to be scanned from NIT on this transponder. meaningless, if (status != StatusScanning) uint16_t reserved2; // reserved, dont use. uint16_t reserved3; // reserved, dont use. } cWirbelscanStatus; /* --- wirbelscan_GetSetup, wirbelscan_SetSetup ------------------------------ * Get/Set Setup. Use this to build up your setup osd displayed to user. */ typedef struct { uint16_t verbosity; // 0 (errors only) .. 5 (extended debug); default = 3 (messages) uint16_t logFile; // 0 = off, 1 = stdout, 2 = syslog uint16_t DVB_Type; // DVB-T = 0, DVB-C = 1, DVB-S/S2 = 2, PVRINPUT = 3, PVRINPUT(FM Radio) = 4, ATSC = 5, TRANSPONDER = 999 uint16_t DVBT_Inversion; // AUTO/OFF = 0, AUTO/ON = 1 uint16_t DVBC_Inversion; // AUTO/OFF = 0, AUTO/ON = 1 uint16_t DVBC_Symbolrate; // careful here - may change. AUTO = 0, 6900 = 1, 6875 = 2 (...) 14 = 5483, 15 = ALL uint16_t DVBC_QAM; // AUTO = 0,QAM64 = 1, QAM128 = 2, QAM256 = 3, ALL = 4 uint16_t CountryId; // the id according to country, found in country list, see wirbelscan_GetCountry uint16_t SatId; // the id according to satellite, found in list, see wirbelscan_GetSat uint32_t scanflags; // bitwise flag of wanted channels: TV = (1 << 0), RADIO = (1 << 1), FTA = (1 << 2), SCRAMBLED = (1 << 4), HDTV = (1 << 5) uint16_t ATSC_type; // VSB = 0, QAM = 1, both VSB+QAM = 2 uint16_t stuffing[6]; // dont use. } cWirbelscanScanSetup; /* --- wirbelscan_GetCountry, wirbelscan_GetSat ------------------------------ * Use this to build up your setup OSD - user needs to choose satellite and * country by name and assign correct IDs to setup, see * * cWirbelscanScanSetup::CountryId * * cWirbelscanScanSetup::SatId. * * 1) Query needed buffer size with cPreAllocBuffer::size == 0 * 2) allocate memory in your plugin, to be at least: cPreAllocBuffer::size * sizeof(SListItem) * 3) second call fills buffer with count * sizeof(SListItem) bytes. * 4) access to items as SListItem* */ typedef struct { int id; char short_name[8]; char full_name[64]; } SListItem; typedef struct { uint32_t size; uint32_t count; SListItem * buffer; } cPreAllocBuffer; /* --- wirbelscan_GetUser, wirbelscan_SetUser -------------------------------- * Scan a user defined Transponder. Service() expects a pointer to uint32_t Data[3]; * Data should be initialized and read using class cUserTransponder. * * --------------------------------------------- * id : 9 country-id/sat-id * frequency : 21 DVB-T: 177500..858000; ATSC/DVB-C: 73000..858000, DVB-S/S2: 2000..15000 * polarisation : 2 0=h, 1=v, 2=l, 3=r * type : 3 DVB_Type * symbolrate : 17 i.e. 27500, 6900 * fec_hi : 4 DVB-S/S2: 1=1/2, 2=2/3, 3=3/4, 4=4/5, 5=5/6, 6=6/7, 7=7/8, 8=8/9, 9=forbidden, 10=3/5, 11=9/10 * DVB-T 1=1/2, 2=2/3, 3=3/4, 4=forbidden, 5=5/6, 6=forbidden, 7=7/8, 8..15=forbidden * fec_lo : 4 DVB-T 1=1/2, 2=2/3, 3=3/4, 4=forbidden, 5=5/6, 6=forbidden, 7=7/8, 8..15=forbidden * modulation : 4 DVB-C: 0=forbidden, 1=QAM16, 2=QAM32, 3=QAM64, 4=QAM128, 5=QAM256, 6..15: forbidden * DVB-T: 0=QPSK, 1=QAM16, 2=forbidden, 3=QAM64, 4..15: forbidden * DVB-S: 0=QPSK, 1=QAM16, 2..8: forbidden, 9=PSK8, 10..15:forbidden * ATSC: 0..2: forbidden, 3=QAM64, 4=forbidden, 5=QAM256, 6=QAM_AUTO, 7=VSB8, 8=VSB16, 9..15=forbidden * orbit : 12 i.e. 192 for S19E2 * we_flag : 1 0=west, 1=east * rolloff : 2 0=0,35, 1=0,25, 2=0,20 * 2nd_gen_sys : 1 0=DVB-S/T, 1=DVB-S2/T2 * bw : 2 0=8MHz, 1=7MHz, 2=6MHz, 3=5MHz * priority : 1 0=LP, 1=HP * hierarchy : 4 0=OFF, 1=alpha1, 2=alpha2, 3=alpha4 * guardinterval : 2 0=1/32, 1=1/16, 2=1/8, 3=1/4 * transmission : 2 0=2k, 1=8k, 2=4k * inversion : 1 0=OFF, 1=ON * use_nit : 1 0=OFF, 1=ON * reserved : 3 always 0 * --------------------------------------------- */ #if ! defined(uint32_t) || ! defined(uint8_t) #include #endif #define P(v,b,p) ((v & ((1 << b) -1)) << p) #define G(v,b,p) ((v >> p) & ((1 << b) -1)) class cUserTransponder { private: uint32_t data[3]; public: ~cUserTransponder() { }; cUserTransponder(uint32_t * Data) { data[0] = *(Data + 0); data[1] = *(Data + 1); data[2] = *(Data + 2); }; // DVB-T cUserTransponder(uint8_t id, uint32_t frequency, uint8_t modulation, uint8_t fec_hp, uint8_t fec_lp, uint8_t bw, uint8_t priority, uint8_t hierarchy, uint8_t guard, uint8_t tm, uint8_t inversion, uint8_t use_nit) { data[0] = P(id,9,23) | P(frequency,21,2); data[1] = P(fec_hp,4,8) | P(fec_lp,4,4) | P(modulation,4,0); data[2] = P(bw,2,14) | P(priority,1,13) | P(hierarchy,4,9) | P(guard,2,7) | P(tm,2,5) | P(inversion,1,4) | P(use_nit,1,3); }; // DVB-C cUserTransponder(uint8_t id, uint32_t frequency, uint32_t symbolrate, uint8_t modulation, uint8_t inversion, uint8_t use_nit) { data[0] = P(id,9,23) | P(frequency,21,2); data[1] = P(1,3,29) | P(symbolrate,17,12) | P(modulation,4,0); data[2] = P(inversion,1,4) | P(use_nit,1,3); }; // DVB-S cUserTransponder(uint8_t id, uint8_t system, uint32_t frequency, uint8_t polarisation, uint32_t symbolrate, uint8_t modulation, uint8_t fec, uint16_t orbit, uint8_t we_flag, uint8_t rolloff, uint8_t use_nit) { data[0] = P(id,9,23) | P(frequency,21,2) | P(polarisation,2,0); data[1] = P(2,3,29) | P(symbolrate,17,12) | P(fec,4,8) | P(modulation,4,0); data[2] = P(orbit,12,20) | P(we_flag,1,19) | P(rolloff,2,17) | P(system,1,16) | P(use_nit,1,3); }; // ATSC cUserTransponder(uint8_t id, uint32_t frequency, uint8_t modulation, uint8_t use_nit) { data[0] = P(id,9,23) | P(frequency,21,2); data[1] = P(5,3,29) | P(modulation,4,0); data[2] = P(use_nit,1,3); }; const uint32_t * Data(void) { return data; }; int Id(void) { return G(data[0],9,23); }; int Frequency(void) { return G(data[0],21,2); }; int Polarisation(void){ return G(data[0],2,0); }; int Type(void) { return G(data[1],3,29); }; int Symbolrate(void) { return G(data[1],17,12); }; int FecHP(void) { return G(data[1],4,8); }; int FecLP(void) { return G(data[1],4,4); }; int Modulation(void) { return G(data[1],4,0); }; int Orbit(void) { return G(data[2],12,20); }; int EastFlag(void) { return G(data[2],1,19); }; int Rolloff(void) { return G(data[2],2,17); }; int Satsystem(void) { return G(data[2],1,16) + 5; }; /*deprecated. use System() instead.*/ int System(void) { return G(data[2],1,16) + 5; }; int Bandwidth(void) { return (8 - G(data[2],2,14)) * (int) 1E6; }; int Priority(void) { return G(data[2],1,13); }; int Hierarchy(void) { return G(data[2],4,9); }; int Guard(void) { return G(data[2],2,7); }; int Transmission(void){ return G(data[2],2,5); }; int Inversion(void) { return G(data[2],1,4); }; int UseNit(void) { return G(data[2],1,3); }; bool IsTerr(void) { return IsType(0); }; bool IsCable(void) { return IsType(1); }; bool IsSat(void) { return IsType(2); }; bool IsAtsc(void) { return IsType(5); }; bool IsType(int type) { return type == Type(); }; }; } /* end of namespace, dont touch */ #endif