├── .gitignore ├── .gitmodules ├── COPYING ├── ChangeLog ├── Makefile ├── README ├── TODO ├── config.c ├── config.h ├── data.c ├── data.h ├── inidict.c ├── inidict.h ├── iniparser.c ├── iniparser.h ├── input.c ├── input.h ├── mptsd.c ├── mptsd.conf ├── mptsd_channels.conf ├── mptsd_epg.conf ├── mptsd_nit.conf ├── mptsd_valgrind ├── network.c ├── network.h ├── output.h ├── output_mix.c ├── output_psi.c ├── output_write.c ├── pidref.c ├── pidref.h ├── rc.mptsd ├── rc.mptsd.conf ├── sleep.c ├── sleep.h ├── web_pages.c ├── web_pages.h ├── web_server.c └── web_server.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | mptsd 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libfuncs"] 2 | path = libfuncs 3 | url = git://github.com/gfto/libfuncs.git 4 | [submodule "libtsfuncs"] 5 | path = libtsfuncs 6 | url = git://github.com/gfto/libtsfuncs.git 7 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2012-01-26 : Version 1.1 2 | * Added support for RTP input. 3 | 4 | 2011-09-16 : Version 1.0 5 | * Initial public release. 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC? = $(CROSS)$(TARGET)gcc 2 | STRIP = $(CROSS)$(TARGET)strip 3 | VERSION="v1.1" 4 | ifndef REPRODUCIBLE_BUILD 5 | BUILD_ID = $(shell date +%F_%R) 6 | GIT_VER = $(shell git describe --tags --dirty --always 2>/dev/null) 7 | endif 8 | CFLAGS ?= -ggdb -O2 9 | CFLAGS += -Wall -Wextra -Wshadow -Wformat-security -Wno-strict-aliasing -D_GNU_SOURCE -DBUILD_ID=\"$(BUILD_ID)\" 10 | ifneq "$(GIT_VER)" "" 11 | CFLAGS += -DGIT_VER=\"$(GIT_VER)\" 12 | else 13 | CFLAGS += -DGIT_VER=\"$(VERSION)\" 14 | endif 15 | 16 | RM = /bin/rm -f 17 | Q = @ 18 | 19 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 20 | ifeq ($(uname_S),Darwin) 21 | LIBS = -lpthread -lm 22 | else 23 | LIBS = -lpthread -lm -lrt 24 | endif 25 | 26 | FUNCS_DIR = libfuncs 27 | FUNCS_LIB = $(FUNCS_DIR)/libfuncs.a 28 | 29 | TSFUNCS_DIR = libtsfuncs 30 | TSFUNCS_LIB = $(TSFUNCS_DIR)/libtsfuncs.a 31 | 32 | mptsd_OBJS = $(FUNCS_LIB) $(TSFUNCS_LIB) \ 33 | iniparser.o inidict.o pidref.o data.o config.o \ 34 | sleep.o network.o \ 35 | input.o \ 36 | output_psi.o output_mix.o output_write.o \ 37 | web_pages.o web_server.o \ 38 | mptsd.o 39 | 40 | PROGS = mptsd 41 | CLEAN_OBJS = $(PROGS) $(mptsd_OBJS) *~ 42 | 43 | all: $(PROGS) 44 | 45 | $(FUNCS_LIB): 46 | $(Q)echo " MAKE $(FUNCS_LIB)" 47 | $(Q)$(MAKE) -s -C $(FUNCS_DIR) 48 | 49 | $(TSFUNCS_LIB): 50 | $(Q)echo " MAKE $(TSFUNCS_LIB)" 51 | $(Q)$(MAKE) -s -C $(TSFUNCS_DIR) 52 | 53 | mptsd: $(mptsd_OBJS) 54 | $(Q)echo " LINK mptsd" 55 | $(Q)$(CC) $(CFLAGS) $(mptsd_OBJS) $(LIBS) -o mptsd 56 | 57 | %.o: %.c data.h 58 | $(Q)echo " CC mptsd $<" 59 | $(Q)$(CC) $(CFLAGS) -c $< 60 | 61 | strip: 62 | $(Q)echo " STRIP $(PROGS)" 63 | $(Q)$(STRIP) $(PROGS) 64 | 65 | clean: 66 | $(Q)echo " RM $(CLEAN_OBJS)" 67 | $(Q)$(RM) $(CLEAN_OBJS) 68 | 69 | distclean: clean 70 | $(Q)$(MAKE) -s -C $(TSFUNCS_DIR) clean 71 | $(Q)$(MAKE) -s -C $(FUNCS_DIR) clean 72 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | mptsd 2 | ===== 3 | mptsd receives mpegts streams from multicast (udp/rtp) or http and combines 4 | them into one multiple program stream that is suitable for outputing to DVB-C 5 | modulator. It is tested with Dektec DTE-3114 Quad QAM Modulator and it 6 | is used in production in couple of small DVB-C networks. 7 | 8 | Installation 9 | ============ 10 | mptsd do not depend on any external libraries. There are two source code 11 | dependancies that come with mptsd - libfuncs and libtsfuncs. 12 | 13 | Make sure your kernel has CONFIG_HIGH_RES_TIMERS enabled. Otherwise sleep 14 | timeout probably won't be able to calibrate itself and mptsd will not work. 15 | 16 | Documentation 17 | ============= 18 | mptsd is controlled using command line parameters. Run mptsd to see all of 19 | the parameters and explanation what every one of them does. Also there 20 | are 4 configuration files. In the distribution there are example files that 21 | you can change to suit your needs. 22 | 23 | mptsd.conf - This file configures the network_id in outputed 24 | NIT table and also MPEG PSI tables playout timeouts. 25 | 26 | mptsd_nit.conf - Configures NIT table output. 27 | 28 | mptsd_channels.conf - Configure inputs, SDT provider name and transport_stream_id 29 | 30 | mptsd_epg.conf - Configures EIT current/next tables. This file is auto 31 | reloaded when it is changed. This allows EPG in output 32 | to work. 33 | 34 | One mptsd instance should be used for each transponder. 35 | 36 | Output Stream 37 | ============= 38 | mptsd can achieve perfect PCR restamping to output using "-m 3". 39 | This is useful for hardware modulators that require correct PCR values 40 | (most cheap modulators do, so always use "-m 3" with them). 41 | mptsd was tested and found to be working ok with Dektec DTE-3114 & HiDes UT-100C. 42 | 43 | To enable RTP output instead of plain UDP for network streams, 44 | specify the SSRC identifier via the -s flag (must be != 0). 45 | 46 | Development 47 | =========== 48 | The development is tracked using git. The repository is hosted at github 49 | to get it, run the following command: 50 | 51 | git clone git://github.com/gfto/mptsd.git 52 | git submodule init 53 | git submodule update 54 | OR 55 | git submodule update --recursive --remote // if you like to checkout HEAD submodules. 56 | 57 | Compiling 58 | ========= 59 | After cloning the git repository as described in Development section 60 | just run `make`. 61 | 62 | Releases 63 | ======== 64 | Official releases can be downloaded from tsdecrypt home page which is: 65 | 66 | http://georgi.unixsol.org/programs/mptsd/ 67 | 68 | Contact 69 | ======= 70 | For patches, bug reports, complaints and so on send e-mail to: 71 | 72 | Georgi Chorbadzhiyski 73 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | mptsd todo 2 | - Add web access to stats 3 | - Support rewritting of incomming EIT tables 4 | - Add live reconfiguration 5 | - Adding removing channels 6 | - Changing of output address and bitrate 7 | - Reloading of nit/eit 8 | - Add support for schedule eit 9 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd configuration 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include // for uint32_t 36 | 37 | #include "libfuncs/log.h" 38 | #include "libfuncs/list.h" 39 | #include "libfuncs/server.h" 40 | 41 | #include "libtsfuncs/tsfuncs.h" 42 | 43 | #include "data.h" 44 | #include "sleep.h" 45 | #include "config.h" 46 | #include "inidict.h" 47 | #include "iniparser.h" 48 | 49 | extern char *server_sig; 50 | extern char *server_ver; 51 | extern char *copyright; 52 | 53 | int is_valid_url(char *url) { 54 | regex_t re; 55 | regmatch_t res[5]; 56 | int ret; 57 | regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED); 58 | ret = regexec(&re,url,5,res,0); 59 | regfree(&re); 60 | return ret == 0; 61 | } 62 | 63 | CONFIG *config_alloc() { 64 | CONFIG *conf = calloc(1, sizeof(CONFIG)); 65 | conf->inputs = list_new("input"); 66 | conf->nit = list_new("nit"); 67 | conf->output = output_new(); 68 | return conf; 69 | } 70 | 71 | void config_free(CONFIG **pconf) { 72 | CONFIG *conf = *pconf; 73 | if (conf) { 74 | conf->output->dienow = 1; 75 | 76 | list_free(&conf->nit, NULL, (void (*)(void **))nit_free); 77 | list_free(&conf->inputs, NULL, (void (*)(void **))input_free); 78 | list_free(&conf->channels, NULL, (void (*)(void **))channel_free); 79 | output_free(&conf->output); 80 | 81 | FREE(conf->ident); 82 | FREE(conf->pidfile); 83 | FREE(conf->server_addr); 84 | FREE(conf->loghost); 85 | FREE(conf->logident); 86 | FREE(conf->global_conf); 87 | FREE(conf->channels_conf); 88 | FREE(conf->nit_conf); 89 | FREE(conf->epg_conf); 90 | FREE(conf->network_name); 91 | FREE(conf->provider_name); 92 | FREE(conf->output_filename); 93 | FREE(*pconf); 94 | } 95 | } 96 | 97 | 98 | int config_load_channels(CONFIG *conf) { 99 | int num_channels = 0, i, j; 100 | 101 | LOGf("CONFIG: Loading channels config %s\n", conf->channels_conf); 102 | dictionary *ini = iniparser_load(conf->channels_conf); 103 | if (!ini) { 104 | LOGf("CONFIG: Error loading channels config (%s)\n", conf->channels_conf); 105 | return 0; 106 | } 107 | 108 | //iniparser_dump(ini, stdout); 109 | LIST *channels = list_new("channels"); 110 | 111 | // Parse channels file 112 | conf->provider_name = ini_get_string_copy(ini, NULL, "Global:provider_name"); 113 | conf->transport_stream_id = ini_get_int(ini, 0, "Global:transport_stream_id"); 114 | for (i=1;i<32;i++) { 115 | CHANNEL *channel = NULL; 116 | int service_id = ini_get_int(ini, 0, "Channel%d:service_id", i); 117 | if (!service_id) 118 | continue; 119 | 120 | int is_radio = ini_get_bool(ini, 0, "Channel%d:radio", i); 121 | int lcn = ini_get_int(ini, 0, "Channel%d:lcn", i); 122 | int is_lcn_visible = ini_get_bool(ini, 0, "Channel%d:lcn_visible", i); 123 | 124 | char *id = ini_get_string(ini, NULL, "Channel%d:id", i); 125 | if (!id) { 126 | LOGf("CONFIG: Channel%d have no defined id\n", i); 127 | continue; 128 | } 129 | 130 | char *name = ini_get_string(ini, NULL, "Channel%d:name", i); 131 | if (!name) { 132 | LOGf("CONFIG: Channel%d have no defined name\n", i); 133 | continue; 134 | } 135 | 136 | int eit_mode = ini_get_int(ini, 0, "Channel%d:eit_mode", i); 137 | 138 | for (j=1;j<8;j++) { 139 | char *source = ini_get_string(ini, NULL, "Channel%d:source%d", i, j); 140 | if (j == 1 && !source) { 141 | source = ini_get_string(ini, NULL, "Channel%d:source", i); 142 | } 143 | if (source) { 144 | if (!is_valid_url(source)) { 145 | LOGf("CONFIG: Invalid url: %s\n", source); 146 | continue; 147 | } 148 | // Init channel 149 | if (channel == NULL) { 150 | channel = channel_new(service_id, is_radio, id, name, eit_mode, source, i, lcn, is_lcn_visible); 151 | } else { 152 | chansrc_add(channel, source); 153 | } 154 | } 155 | } 156 | 157 | char *worktime = ini_get_string(ini, NULL, "Channel%d:worktime", i); 158 | int sh=0,sm=0,eh=0,em=0; 159 | if (worktime && sscanf(worktime, "%02d:%02d-%02d:%02d", &sh, &sm, &eh, &em) == 4) { 160 | channel->worktime_start = sh * 3600 + sm * 60; 161 | channel->worktime_end = eh * 3600 + em * 60; 162 | } 163 | 164 | // Channel is initialized, add it to channels list 165 | if (channel) { 166 | num_channels++; 167 | list_add(channels, channel); 168 | } 169 | } 170 | // iniparser_dump(ini, stderr); 171 | iniparser_freedict(&ini); 172 | 173 | // Check for channel changes 174 | struct timeval tv; 175 | gettimeofday(&tv, NULL); 176 | unsigned int randstate = tv.tv_usec; 177 | int cookie = rand_r(&randstate); 178 | 179 | /* Save current channels config */ 180 | LIST *old_channels = conf->channels; 181 | 182 | /* Switch new channels config */ 183 | conf->channels = channels; 184 | 185 | /* Rewrite restreamer channels */ 186 | LNODE *lc, *lr, *lctmp, *lrtmp; 187 | CHANNEL *chan; 188 | list_lock(conf->inputs); // Unlocked after second list_for_each(restreamer) 189 | list_lock(conf->channels); 190 | list_for_each(conf->channels, lc, lctmp) { 191 | chan = lc->data; 192 | list_for_each(conf->inputs, lr, lrtmp) { 193 | if (strcmp(chan->name, ((INPUT *)lr->data)->name)==0) { 194 | INPUT *restr = lr->data; 195 | /* Mark the restreamer as valid */ 196 | restr->cookie = cookie; 197 | /* Check if current source exists in new channel configuration */ 198 | int src_found = -1; 199 | char *old_source = restr->channel->source; 200 | for (i=0; inum_src; i++) { 201 | if (strcmp(old_source, chan->sources[i]) == 0) { 202 | src_found = i; 203 | } 204 | } 205 | if (src_found > -1) { 206 | /* New configuration contains existing source, just update the reference */ 207 | chansrc_set(chan, src_found); 208 | restr->channel = chan; 209 | } else { 210 | /* New configuration *DO NOT* contain existing source. Force reconnect */ 211 | LOGf("INPUT: Source changed | Channel: %s srv_fd: %d Old:%s New:%s\n", chan->name, restr->sock, restr->channel->source, chan->source); 212 | /* The order is important! */ 213 | chansrc_set(chan, chan->num_src-1); /* Set source to last one. On reconnect next source will be used. */ 214 | restr->channel = chan; 215 | restr->reconnect = 1; 216 | } 217 | break; 218 | } 219 | } 220 | } 221 | list_unlock(conf->channels); 222 | 223 | /* Kill restreamers that serve channels that no longer exist */ 224 | list_for_each(conf->inputs, lr, lrtmp) { 225 | INPUT *r = lr->data; 226 | /* This restreamer should no longer serve clients */ 227 | if (r->cookie != cookie) { 228 | proxy_log(r, "Remove"); 229 | /* Replace channel reference with real object and instruct free_restreamer to free it */ 230 | r->channel = channel_new(r->channel->service_id, r->channel->radio, r->channel->id, r->channel->name, r->channel->eit_mode, r->channel->source, r->channel->index, r->channel->lcn, r->channel->lcn_visible); 231 | r->freechannel = 1; 232 | r->dienow = 1; 233 | } 234 | } 235 | list_unlock(conf->inputs); 236 | 237 | /* Free old_channels */ 238 | list_free(&old_channels, NULL, (void (*)(void **))channel_free); 239 | 240 | LOGf("CONFIG: %d channels loaded\n", num_channels); 241 | return num_channels; 242 | } 243 | 244 | int config_load_global(CONFIG *conf) { 245 | LOGf("CONFIG: Loading global config %s\n", conf->global_conf); 246 | dictionary *ini = iniparser_load(conf->global_conf); 247 | if (!ini) { 248 | LOGf("CONFIG: Error loading global config (%s)\n", conf->global_conf); 249 | return 0; 250 | } 251 | conf->network_id = ini_get_int(ini, 0, "Global:network_id"); 252 | conf->timeouts.pat = ini_get_int(ini, 100, "Timeouts:pat"); 253 | conf->timeouts.pmt = ini_get_int(ini, 200, "Timeouts:pmt"); 254 | conf->timeouts.sdt = ini_get_int(ini, 500, "Timeouts:sdt"); 255 | conf->timeouts.nit = ini_get_int(ini, 2000, "Timeouts:nit"); 256 | conf->timeouts.eit = ini_get_int(ini, 1000, "Timeouts:eit"); 257 | conf->timeouts.tdt = ini_get_int(ini, 7500, "Timeouts:tdt"); 258 | conf->timeouts.tot = ini_get_int(ini, 1500, "Timeouts:tot"); 259 | conf->timeouts.stats = ini_get_int(ini, 0, "Timeouts:stats"); 260 | //iniparser_dump(ini, stderr); 261 | iniparser_freedict(&ini); 262 | return 1; 263 | } 264 | 265 | int config_load_nit(CONFIG *conf) { 266 | LOGf("CONFIG: Loading nit config %s\n", conf->nit_conf); 267 | dictionary *ini = iniparser_load(conf->nit_conf); 268 | if (!ini) { 269 | LOGf("CONFIG: Error loading nit config (%s)\n", conf->nit_conf); 270 | return 0; 271 | } 272 | conf->network_name = ini_get_string_copy(ini, NULL, "Global:network_name"); 273 | int i; 274 | for (i=1;i<32;i++) { 275 | uint16_t ts_id = ini_get_int (ini, 0, "Transponder%d:transport_stream_id", i); 276 | char *freq = ini_get_string(ini, NULL, "Transponder%d:frequency", i); 277 | char *mod = ini_get_string(ini, NULL, "Transponder%d:modulation", i); 278 | char *sr = ini_get_string(ini, NULL, "Transponder%d:symbol_rate", i); 279 | if (ts_id && freq && mod && sr) { 280 | if (strlen(freq) == 9 && strlen(sr) == 8) { 281 | list_add(conf->nit, nit_new(ts_id, freq, mod, sr)); 282 | } 283 | } 284 | } 285 | //iniparser_dump(ini, stderr); 286 | iniparser_freedict(&ini); 287 | return 1; 288 | } 289 | 290 | static void config_reset_channels_epg(CONFIG *conf) { 291 | LNODE *lc, *lctmp; 292 | list_lock(conf->channels); 293 | list_for_each(conf->channels, lc, lctmp) { 294 | CHANNEL *c = lc->data; 295 | channel_free_epg(c); 296 | } 297 | list_unlock(conf->channels); 298 | conf->epg_conf_mtime = 0; 299 | } 300 | 301 | static EPG_ENTRY *config_load_epg_entry(dictionary *ini, char *entry, char *channel) { 302 | EPG_ENTRY *e = NULL; 303 | time_t start = ini_get_int(ini, 0, "%s-%s:start", channel, entry); 304 | int duration = ini_get_int(ini, 0, "%s-%s:duration", channel, entry); 305 | char *event = ini_get_string(ini, NULL, "%s-%s:event", channel, entry); 306 | char *sdesc = ini_get_string(ini, NULL, "%s-%s:sdescr", channel, entry); 307 | char *ldesc = ini_get_string(ini, NULL, "%s-%s:descr", channel, entry); 308 | char *enc = ini_get_string(ini, NULL, "%s-%s:encoding", channel, entry); 309 | if (start && duration && event) { 310 | e = epg_new(start, duration, enc, event, sdesc, ldesc); 311 | } 312 | return e; 313 | } 314 | 315 | static void config_channel_init_epg(CONFIG *conf, CHANNEL *c, EPG_ENTRY *now, EPG_ENTRY *next) { 316 | int updated = 0; 317 | 318 | if (epg_changed(now, c->epg_now)) { 319 | if(!conf->quiet) 320 | LOGf("EPG : %s | Now data changed\n", c->id); 321 | updated++; 322 | } 323 | 324 | if (epg_changed(next, c->epg_next)) { 325 | if(!conf->quiet) 326 | LOGf("EPG : %s | Next data changed\n", c->id); 327 | updated++; 328 | } 329 | 330 | if (!updated) 331 | return; 332 | 333 | // LOGf("EPG : %s | Version %d\n", c->epg_version); 334 | c->epg_version++; 335 | 336 | struct ts_eit *eit_now = ts_eit_alloc_init_pf(c->service_id, conf->transport_stream_id, conf->network_id, 0, 1); 337 | eit_now->section_header->version_number = c->epg_version; 338 | if (now) { 339 | ts_eit_add_short_event_descriptor(eit_now, now->event_id, 1, now->start, now->duration, now->event, now->short_desc); 340 | ts_eit_add_extended_event_descriptor(eit_now, now->event_id, 1, now->start, now->duration, now->long_desc); 341 | } 342 | ts_eit_regenerate_packets(eit_now); 343 | 344 | struct ts_eit *eit_next = ts_eit_alloc_init_pf(c->service_id, conf->transport_stream_id, conf->network_id, 1, 1); 345 | eit_next->section_header->version_number = c->epg_version; 346 | if (next) { 347 | ts_eit_add_short_event_descriptor(eit_next, next->event_id, 1, next->start, next->duration, next->event, next->short_desc); 348 | ts_eit_add_extended_event_descriptor(eit_next, next->event_id, 1, next->start, next->duration, next->long_desc); 349 | } 350 | ts_eit_regenerate_packets(eit_next); 351 | 352 | channel_free_epg(c); 353 | 354 | c->epg_now = now; 355 | c->epg_next = next; 356 | 357 | if (now || next) { 358 | c->eit_now = eit_now; 359 | c->eit_next = eit_next; 360 | //LOGf(" ***** NOW ******\n"); 361 | //ts_eit_dump(eit_now); 362 | //LOGf(" ***** NEXT *****\n"); 363 | //ts_eit_dump(eit_next); 364 | } else { 365 | ts_eit_free(&eit_now); 366 | ts_eit_free(&eit_next); 367 | } 368 | } 369 | 370 | 371 | int config_load_epg(CONFIG *conf) { 372 | struct stat st; 373 | if (stat(conf->epg_conf, &st) == 0) { 374 | if (st.st_mtime > conf->epg_conf_mtime) { 375 | // Set config last change time 376 | conf->epg_conf_mtime = st.st_mtime; 377 | } else { 378 | // LOGf("CONFIG: EPG config not changed since last check.\n"); 379 | return 0; // The config has not changed! 380 | } 381 | } else { 382 | // Config file not found! 383 | LOGf("CONFIG: EPG config file is not found (%s)\n", conf->epg_conf); 384 | config_reset_channels_epg(conf); 385 | return 0; 386 | } 387 | 388 | // LOGf("CONFIG: Loading EPG config %s\n", conf->epg_conf); 389 | dictionary *ini = iniparser_load(conf->epg_conf); 390 | if (!ini) { 391 | LOGf("CONFIG: Error parsing EPG config (%s)\n", conf->epg_conf); 392 | config_reset_channels_epg(conf); 393 | return 0; 394 | } 395 | 396 | LNODE *lc, *lctmp; 397 | list_lock(conf->channels); 398 | list_for_each(conf->channels, lc, lctmp) { 399 | CHANNEL *c = lc->data; 400 | EPG_ENTRY *enow = config_load_epg_entry(ini, "now", c->id); 401 | EPG_ENTRY *enext = config_load_epg_entry(ini, "next", c->id); 402 | config_channel_init_epg(conf, c, enow, enext); 403 | } 404 | list_unlock(conf->channels); 405 | //iniparser_dump(ini, stderr); 406 | iniparser_freedict(&ini); 407 | 408 | return 1; 409 | } 410 | 411 | extern char *program_id; 412 | 413 | static void show_usage(void) { 414 | printf("%s\n", program_id); 415 | puts(copyright); 416 | puts(""); 417 | puts("Identification:"); 418 | puts("\t-i ident\tServer ident. (default: ux/mptsd)"); 419 | puts(""); 420 | puts("Server settings:"); 421 | puts("\t-b addr\t\tLocal IP address to bind. (default: 0.0.0.0)"); 422 | puts("\t-p port\t\tPort to listen. (default: 0)"); 423 | puts("\t-N disable network"); 424 | puts("\t-d pidfile\tDaemonize with pidfile"); 425 | puts("\t-l host\t\tSyslog host (default: disabled)"); 426 | puts("\t-L port\t\tSyslog port (default: 514)"); 427 | puts(""); 428 | puts("Configuration files:"); 429 | puts("\t-g file\t\tGlobal configuration file (default: mptsd.conf)"); 430 | puts("\t-c file\t\tChannels configuration file (default: mptsd_channels.conf)"); 431 | puts("\t-e file\t\tEPG configuration file (default: mptsd_epg.conf)"); 432 | puts("\t-n file\t\tNIT configuration file (default: mptsd_nit.conf)"); 433 | puts(""); 434 | puts("Output settings:"); 435 | puts("\t-O ip\t\tOutput udp address"); 436 | puts("\t-P ip\t\tOutput udp port (default: 5000)"); 437 | puts("\t-o ip\t\tOutput interface address (default: 0.0.0.0)"); 438 | puts("\t-t ttl\t\tSet multicast ttl (default: 1)"); 439 | puts("\t-s SSRC\t\tEnables RTP (default: disabled)"); 440 | puts(""); 441 | puts("\t-B Mbps\t\tOutput bitrate in Mbps (default: 38.00)"); 442 | puts("\t-m mode\t\tPCR mode (modes list bellow)"); 443 | puts("\t\t\t- Mode 0: do not touch PCRs (default)"); 444 | puts("\t\t\t- Mode 1: move PCRs to their calculated place"); 445 | puts("\t\t\t- Mode 2: rewrite PCRs using output bitrate"); 446 | puts("\t\t\t- Mode 3: move PCRs and rewrite them"); 447 | puts(""); 448 | puts("Other settings:"); 449 | puts("\t-q\t\tQuiet"); 450 | puts("\t-D\t\tDebug"); 451 | puts(""); 452 | puts("\t-W\t\tWrite output file (recommended to use with -N)"); 453 | puts("\t-f\t\tThe output filename (default: mptsd-output.ts) (use - for stdout)"); 454 | puts("\t-E\t\tWrite input file"); 455 | puts("\t-9\t\tEnable LCN support (default: disabled)"); 456 | puts(""); 457 | } 458 | 459 | void config_load(CONFIG *conf, int argc, char **argv) { 460 | int j, ttl; 461 | 462 | conf->multicast_ttl = 1; 463 | conf->output->out_port = 5000; 464 | conf->output->rtp_sequence_number = 1; 465 | conf->output->rtp_ssrc = 0; 466 | conf->output_bitrate = 38; 467 | conf->logport = 514; 468 | conf->server_port = 0; 469 | conf->server_socket = -1; 470 | conf->write_output_network = 1; 471 | 472 | while ((j = getopt(argc, argv, "i:b:p:g:c:n:e:d:t:o:O:P:l:L:B:m:s:f:qDHhEWN9")) != -1) { 473 | switch (j) { 474 | case 'i': 475 | conf->ident = strdup(optarg); 476 | conf->logident = strdup(optarg); 477 | char *c = conf->logident; 478 | while (*c) { 479 | if (*c=='/') 480 | *c='-'; 481 | c++; 482 | } 483 | break; 484 | case 'b': 485 | conf->server_addr = strdup(optarg); 486 | break; 487 | case 'p': 488 | conf->server_port = atoi(optarg); 489 | break; 490 | case 'd': 491 | conf->pidfile = strdup(optarg); 492 | break; 493 | case 'g': 494 | conf->global_conf = strdup(optarg); 495 | break; 496 | case 'c': 497 | conf->channels_conf = strdup(optarg); 498 | break; 499 | case 'n': 500 | conf->nit_conf = strdup(optarg); 501 | break; 502 | case 'e': 503 | conf->epg_conf = strdup(optarg); 504 | break; 505 | case 'o': 506 | if (inet_aton(optarg, &conf->output_intf) == 0) { 507 | fprintf(stderr, "Invalid interface address: %s\n", optarg); 508 | exit(1); 509 | } 510 | break; 511 | case 'O': 512 | if (inet_aton(optarg, &(conf->output->out_host)) == 0) { 513 | fprintf(stderr, "Invalid output address: %s\n", optarg); 514 | exit(1); 515 | } 516 | break; 517 | case 'P': 518 | conf->output->out_port = atoi(optarg); 519 | if (!conf->output->out_port || conf->output->out_port < 1024) { 520 | fprintf(stderr, "Invalid output port: %s\n", optarg); 521 | exit(1); 522 | } 523 | break; 524 | case 's': 525 | conf->output->rtp_ssrc = strtoul(optarg, NULL, 10); 526 | break; 527 | case 'm': 528 | conf->pcr_mode = atoi(optarg); 529 | if (conf->pcr_mode < 0 || conf->pcr_mode > 4) 530 | conf->pcr_mode = 0; 531 | break; 532 | case 't': 533 | ttl = atoi(optarg); 534 | conf->multicast_ttl = (ttl && ttl < 127) ? ttl : 1; 535 | break; 536 | case 'l': 537 | conf->loghost = strdup(optarg); 538 | conf->syslog_active = 1; 539 | break; 540 | case 'L': 541 | conf->logport = atoi(optarg); 542 | break; 543 | case 'B': 544 | conf->output_bitrate = atof(optarg); 545 | if (conf->output_bitrate < 2 || conf->output_bitrate > 75) { 546 | fprintf(stderr, "Invalid output bitrate: %.2f (valid 2-75)\n", conf->output_bitrate); 547 | exit(1); 548 | } 549 | break; 550 | case 'N': 551 | conf->write_output_network = 0; 552 | break; 553 | case 'W': 554 | conf->write_output_file = 1; 555 | break; 556 | case 'E': 557 | conf->write_input_file = 1; 558 | break; 559 | case 'f': 560 | conf->output_filename = strdup(optarg); 561 | break; 562 | case '9': 563 | conf->use_lcn = 1; 564 | break; 565 | case 'D': 566 | conf->debug = 1; 567 | break; 568 | case 'q': 569 | conf->quiet = 1; 570 | break; 571 | case 'H': 572 | case 'h': 573 | show_usage(); 574 | exit(0); 575 | break; 576 | } 577 | } 578 | if (conf->write_output_network && !conf->output->out_host.s_addr) { 579 | fprintf(stderr, "ERROR: Output address is not set (use -O x.x.x.x)\n\n"); 580 | show_usage(); 581 | goto ERR; 582 | } 583 | // Set defaults 584 | if (!conf->ident) { 585 | conf->ident = strdup("ux/mptsd"); 586 | conf->logident = strdup("ux-mptsd"); 587 | } 588 | if (!conf->global_conf) { 589 | conf->global_conf = strdup("mptsd.conf"); 590 | } 591 | if (!conf->channels_conf) { 592 | conf->channels_conf = strdup("mptsd_channels.conf"); 593 | } 594 | if (!conf->nit_conf) { 595 | conf->nit_conf = strdup("mptsd_nit.conf"); 596 | } 597 | if (!conf->epg_conf) { 598 | conf->epg_conf = strdup("mptsd_epg.conf"); 599 | } 600 | if (!conf->output_filename) { 601 | conf->output_filename = strdup("mptsd-output.ts"); 602 | } 603 | 604 | // Align bitrate to 1 packet (1316 bytes) 605 | conf->output_bitrate *= 1000000; // In bytes 606 | conf->output_packets_per_sec = ceil(conf->output_bitrate / (1316 * 8)); 607 | conf->output_bitrate = conf->output_packets_per_sec * (1316 * 8); 608 | conf->output_tmout = 1000000 / conf->output_packets_per_sec; 609 | 610 | // Open the filename if we want to write to a file 611 | if(conf->write_output_file) { 612 | output_open_file(conf->output_filename, conf->output); 613 | } 614 | 615 | if (conf->server_port) 616 | init_server_socket(conf->server_addr, conf->server_port, &conf->server, &conf->server_socket); 617 | 618 | if (!conf->quiet) { 619 | LOGf("Configuration:\n"); 620 | LOGf("\tServer ident : %s\n", conf->ident); 621 | LOGf("\tGlobal config : %s\n", conf->global_conf); 622 | LOGf("\tChannels config : %s\n", conf->channels_conf); 623 | LOGf("\tNIT config : %s\n", conf->nit_conf); 624 | if (conf->write_output_network) 625 | LOGf("\tOutput addr : %s://%s:%d\n", (conf->output->rtp_ssrc != 0 ? "rtp" : "udp"), inet_ntoa(conf->output->out_host), conf->output->out_port); 626 | else 627 | LOGf("\tOutput addr : disabled\n"); 628 | if (conf->output_intf.s_addr) 629 | LOGf("\tOutput iface addr : %s\n", inet_ntoa(conf->output_intf)); 630 | LOGf("\tMulticast ttl : %d\n", conf->multicast_ttl); 631 | LOGf("\tRTP SSRC : %u\n", conf->output->rtp_ssrc); 632 | if (conf->syslog_active) { 633 | LOGf("\tSyslog host : %s\n", conf->loghost); 634 | LOGf("\tSyslog port : %d\n", conf->logport); 635 | } else { 636 | LOGf("\tSyslog : disabled\n"); 637 | } 638 | LOGf("\tOutput bitrate : %.0f bps, %.2f Kbps, %.2f Mbps\n", conf->output_bitrate, conf->output_bitrate / 1000, conf->output_bitrate / 1000000); 639 | LOGf("\tOutput pkt tmout : %ld us\n", conf->output_tmout); 640 | LOGf("\tPackets per second: %ld\n", conf->output_packets_per_sec); 641 | LOGf("\tPCR mode : %s\n", 642 | conf->pcr_mode == 0 ? "Do not touch PCRs" : 643 | conf->pcr_mode == 1 ? "Move PCRs to their calculated place" : 644 | conf->pcr_mode == 2 ? "Rewrite PCRs using output bitrate" : 645 | conf->pcr_mode == 3 ? "Move PCRs and rewrite them" : "???" 646 | ); 647 | LOGf("\tLCN : %s\n", (conf->use_lcn)? "enabled" : "disabled"); 648 | if (conf->write_output_file) 649 | LOGf("\tWrite output file : %s\n", conf->output_filename); 650 | if (conf->write_input_file) 651 | LOGf("\tWrite input file(s)\n"); 652 | } else 653 | LOGf("Quiet mode enabled.\n"); 654 | 655 | pthread_t sleepthread; 656 | if (pthread_create(&sleepthread, NULL, &calibrate_sleep, conf) == 0) { 657 | pthread_join(sleepthread, NULL); 658 | } else { 659 | perror("calibrate_thread"); 660 | exit(1); 661 | } 662 | if (!conf->output_tmout) 663 | exit(1); 664 | 665 | output_buffer_alloc(conf->output, conf->output_bitrate); 666 | 667 | if (!config_load_global(conf)) 668 | goto ERR; 669 | if (!config_load_nit(conf)) 670 | goto ERR; 671 | if (!config_load_channels(conf)) 672 | goto ERR; 673 | 674 | config_load_epg(conf); 675 | 676 | return; 677 | 678 | ERR: 679 | config_free(&conf); 680 | exit(1); 681 | } 682 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd configuration header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef CONFIG_H 19 | #define CONFIG_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libfuncs/list.h" 26 | 27 | #include "data.h" 28 | 29 | typedef struct { 30 | char *ident; 31 | char *pidfile; 32 | 33 | int syslog_active; 34 | char *logident; 35 | char *loghost; 36 | int logport; 37 | 38 | struct sockaddr_in server; 39 | char *server_addr; 40 | int server_port; 41 | int server_socket; 42 | pthread_t server_thread; 43 | 44 | int multicast_ttl; 45 | struct in_addr output_intf; 46 | 47 | char *global_conf; 48 | char *channels_conf; 49 | char *nit_conf; 50 | char *epg_conf; 51 | 52 | int debug; 53 | int quiet; 54 | 55 | int write_input_file; 56 | int write_output_network; 57 | int write_output_file; // 0 - do not write an output file 58 | // 1 - write output files 59 | char *output_filename; // filename to write the output 60 | int pcr_mode; // 0 - do not touch PCRs 61 | // 1 - move PCRs to their calculated place 62 | // 2 - rewrite PCRs using output bitrate 63 | // 3 - move PCRs and rewrite them 64 | int use_lcn; 65 | 66 | uint16_t network_id; // For NIT && SDT 67 | uint16_t transport_stream_id;// For NIT 68 | char *network_name; // For NIT 69 | char *provider_name; // For SDT 70 | 71 | double output_bitrate; // Output bitrate (bps) 72 | long output_tmout; // Smooth interval miliseconds 73 | long usleep_overhead; // How much more usecs uslep(1) takes 74 | long output_packets_per_sec; // How much packets should be sent in one second 75 | 76 | time_t epg_conf_mtime; // Last change time of epg config 77 | 78 | LIST * channels; // List of channels 79 | LIST * inputs; // Input threads 80 | LIST * nit; 81 | OUTPUT * output; // Output 82 | 83 | struct { 84 | unsigned int pat; // DVB section id 0x00 (program_association_section) 85 | unsigned int pmt; // DVB section id 0x02 (program_map_section) 86 | unsigned int nit; // DVB section id 0x40 (network_information_section - actual_network) 87 | unsigned int sdt; // DVB section id 0x42 (service_description_section - actual_transport_stream) 88 | unsigned int eit; // DVB section id 0x4e (event_information_section - actual_transport_stream, present/following) 89 | unsigned int tdt; // DVB section id 0x70 (time_date_section) 90 | unsigned int tot; // DVB section id 0x73 (time_offset_section) 91 | unsigned int stats; // Local 92 | } timeouts; 93 | } CONFIG; 94 | 95 | 96 | CONFIG * config_alloc (); 97 | void config_free (CONFIG **conf); 98 | void config_load (CONFIG *conf, int argc, char **argv); 99 | 100 | int config_load_global (CONFIG *conf); 101 | int config_load_channels (CONFIG *conf); 102 | int config_load_nit (CONFIG *conf); 103 | int config_load_epg (CONFIG *conf); 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /data.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd data 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "libfuncs/io.h" 30 | #include "libfuncs/log.h" 31 | #include "libfuncs/list.h" 32 | #include "libfuncs/asyncdns.h" 33 | 34 | #include "libtsfuncs/tsfuncs.h" 35 | 36 | #include "data.h" 37 | #include "config.h" 38 | #include "output.h" 39 | 40 | extern CONFIG *config; 41 | 42 | channel_source get_sproto(char *url) { 43 | return strncmp(url, "http", 4)==0 ? tcp_sock : udp_sock; 44 | } 45 | 46 | int is_rtp(char *url) { 47 | return strncmp(url, "rtp", 3) == 0; 48 | } 49 | 50 | CHANSRC *chansrc_init(char *url) { 51 | if (!url) 52 | return NULL; 53 | regex_t re; 54 | regmatch_t res[5]; 55 | regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED); 56 | if (regexec(&re,url,5,res,0)==0) { 57 | char *data = strdup(url); 58 | char *proto, *host, *port, *path; 59 | int iport; 60 | proto= data+res[1].rm_so; data[res[1].rm_eo]=0; 61 | host = data+res[2].rm_so; data[res[2].rm_eo]=0; 62 | port = data+res[3].rm_so; data[res[3].rm_eo]=0; 63 | path = data+res[4].rm_so; data[res[4].rm_eo]=0; 64 | iport = atoi(port); 65 | /* Setup */ 66 | CHANSRC *src = calloc(1, sizeof(CHANSRC)); 67 | src->proto = strdup(proto); 68 | src->sproto= get_sproto(url); 69 | src->host = strdup(host); 70 | src->port = iport ? iport : 80; 71 | src->path = strdup(path); 72 | src->rtp = strcmp(proto, "rtp") == 0; 73 | FREE(data); 74 | regfree(&re); 75 | return src; 76 | } 77 | regfree(&re); 78 | return NULL; 79 | } 80 | 81 | void chansrc_free(CHANSRC **purl) { 82 | CHANSRC *url = *purl; 83 | if (url) { 84 | FREE(url->proto); 85 | FREE(url->host); 86 | FREE(url->path); 87 | FREE(*purl); 88 | } 89 | }; 90 | 91 | void chansrc_add(CHANNEL *c, const char *src) { 92 | if (c->num_src >= MAX_CHANNEL_SOURCES-1) 93 | return; 94 | c->sources[c->num_src] = strdup(src); 95 | if (c->num_src == 0) /* Set default source to first one */ 96 | c->source = c->sources[c->num_src]; 97 | c->num_src++; 98 | } 99 | 100 | void chansrc_next(CHANNEL *c) { 101 | if (c->num_src <= 1) 102 | return; 103 | // uint8_t old_src = c->curr_src; 104 | c->curr_src++; 105 | if (c->curr_src >= MAX_CHANNEL_SOURCES-1 || c->sources[c->curr_src] == NULL) 106 | c->curr_src = 0; 107 | c->source = c->sources[c->curr_src]; 108 | // LOGf("CHAN : Switch source | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source); 109 | } 110 | 111 | void chansrc_set(CHANNEL *c, uint8_t src_id) { 112 | if (src_id >= MAX_CHANNEL_SOURCES-1 || c->sources[src_id] == NULL) 113 | return; 114 | // uint8_t old_src = c->curr_src; 115 | c->curr_src = src_id; 116 | c->source = c->sources[c->curr_src]; 117 | // LOGf("CHAN : Set source | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source); 118 | } 119 | 120 | 121 | 122 | 123 | 124 | CHANNEL *channel_new(int service_id, int is_radio, const char *id, const char *name, int eit_mode, const char *source, int channel_index, int lcn, int is_lcn_visible){ 125 | 126 | if (channel_index<=0 || channel_index>=256) 127 | { 128 | 129 | LOGf("CONFIG: Error channel_new invalid index %d\n", channel_index); 130 | return NULL; 131 | } 132 | //LOGf("CONFIG: ------------------channel_new() serviceid %d id %s name %s source %s index %d\n", service_id, id, name , source , channel_index); 133 | 134 | CHANNEL *c = calloc(1, sizeof(CHANNEL)); 135 | c->service_id = service_id; 136 | c->radio = is_radio; 137 | c->index = channel_index; 138 | c->lcn = lcn; 139 | c->lcn_visible = is_lcn_visible; 140 | c->base_pid = c->index * 32; // The first pid is saved for PMT , channel_index must > 0 141 | c->pmt_pid = c->base_pid; // The first pid is saved for PMT 142 | c->id = strdup(id); 143 | c->name = strdup(name); 144 | c->eit_mode = eit_mode; 145 | chansrc_add(c, source); 146 | 147 | 148 | return c; 149 | } 150 | 151 | void channel_free_epg(CHANNEL *c) { 152 | epg_free(&c->epg_now); 153 | epg_free(&c->epg_next); 154 | 155 | ts_eit_free(&c->eit_now); 156 | ts_eit_free(&c->eit_next); 157 | } 158 | 159 | void channel_free(CHANNEL **pc) { 160 | CHANNEL *c = *pc; 161 | if (c) { 162 | channel_free_epg(c); 163 | FREE(c->id); 164 | FREE(c->name); 165 | int i; 166 | for (i=c->num_src-1; i>=0; i--) { 167 | FREE(c->sources[i]); 168 | } 169 | c->source = NULL; 170 | FREE(*pc); 171 | } 172 | } 173 | 174 | 175 | EPG_ENTRY *epg_new(time_t start, int duration, char *encoding, char *event, char *short_desc, char *long_desc) { 176 | EPG_ENTRY *e; 177 | if (!event) 178 | return NULL; 179 | e = calloc(1, sizeof(EPG_ENTRY)); 180 | e->event_id = (start / 60) &~ 0xffff0000; 181 | e->start = start; 182 | e->duration = duration; 183 | if (encoding && strcmp(encoding, "iso-8859-5")==0) { 184 | e->event = init_dvb_string_iso_8859_5(event); 185 | e->short_desc = init_dvb_string_iso_8859_5(short_desc); 186 | e->long_desc = init_dvb_string_iso_8859_5(long_desc); 187 | } else { // Default is utf-8 188 | e->event = init_dvb_string_utf8(event); 189 | e->short_desc = init_dvb_string_utf8(short_desc); 190 | e->long_desc = init_dvb_string_utf8(long_desc); 191 | } 192 | return e; 193 | } 194 | 195 | void epg_free(EPG_ENTRY **pe) { 196 | EPG_ENTRY *e = *pe; 197 | if (e) { 198 | FREE(e->event); 199 | FREE(e->short_desc); 200 | FREE(e->long_desc); 201 | FREE(*pe); 202 | } 203 | } 204 | 205 | 206 | // Return 1 if they are different 207 | // Return 0 if they are the same 208 | int epg_changed(EPG_ENTRY *a, EPG_ENTRY *b) { 209 | if (!a && b) return 1; 210 | if (!b && a) return 1; 211 | if (!a && !b) return 0; 212 | if (a->event_id != b->event_id) return 1; 213 | if (a->start != b->start) return 1; 214 | if (a->duration != b->duration) return 1; 215 | if (xstrcmp(a->event, b->event) != 0) return 1; 216 | if (xstrcmp(a->short_desc, b->short_desc) != 0) return 1; 217 | if (xstrcmp(a->long_desc, b->long_desc) != 0) return 1; 218 | return 0; 219 | } 220 | 221 | void input_stream_alloc(INPUT *input) { 222 | input->stream.pidref = pidref_init(64, input->channel->base_pid); 223 | input->stream.pat = ts_pat_alloc(); 224 | input->stream.pmt = ts_pmt_alloc(); 225 | input->stream.eit = ts_eit_alloc(); 226 | input->stream.last_pat = ts_pat_alloc(); 227 | input->stream.last_pmt = ts_pmt_alloc(); 228 | } 229 | 230 | void input_stream_free(INPUT *input) { 231 | ts_pmt_free(&input->stream.pmt); 232 | ts_pmt_free(&input->stream.pmt_rewritten); 233 | ts_pmt_free(&input->stream.last_pmt); 234 | ts_pat_free(&input->stream.pat); 235 | ts_pat_free(&input->stream.pat_rewritten); 236 | ts_pat_free(&input->stream.last_pat); 237 | ts_eit_free(&input->stream.eit); 238 | ts_eit_free(&input->stream.eit_rewritten); 239 | pidref_free(&input->stream.pidref); 240 | input->stream.nit_pid = 0; 241 | input->stream.pmt_pid = 0; 242 | input->stream.pcr_pid = 0; 243 | input->stream.input_pcr = 0; 244 | } 245 | 246 | void input_stream_reset(INPUT *input) { 247 | input_stream_free(input); 248 | input_stream_alloc(input); 249 | } 250 | 251 | INPUT * input_new(const char *name, CHANNEL *channel) { 252 | char *tmp; 253 | INPUT *r = calloc(1, sizeof(INPUT)); 254 | 255 | r->name = strdup(name); 256 | r->sock = -1; 257 | r->channel = channel; 258 | 259 | if (config->write_input_file) { 260 | if (asprintf(&tmp, "mptsd-input-%s.ts", channel->id) > 0) 261 | r->ifd = open(tmp, O_CREAT | O_WRONLY | O_TRUNC, 0644); 262 | FREE(tmp); 263 | } 264 | 265 | r->buf = cbuf_init(1428 * 1316 * 4, channel->id); // ~ 40000 x 188 266 | 267 | input_stream_alloc(r); 268 | 269 | return r; 270 | } 271 | 272 | void input_free(INPUT **pinput) { 273 | INPUT *r = *pinput; 274 | if (!r) 275 | return; 276 | if (r->sock > -1) 277 | shutdown_fd(&(r->sock)); 278 | if (r->freechannel) 279 | channel_free(&r->channel); 280 | if (r->ifd) 281 | close(r->ifd); 282 | 283 | input_stream_free(r); 284 | 285 | cbuf_free(&r->buf); 286 | 287 | FREE(r->name); 288 | FREE(*pinput); 289 | } 290 | 291 | 292 | 293 | OUTPUT *output_new() { 294 | OUTPUT *o = calloc(1, sizeof(OUTPUT)); 295 | o->obuf_ms = 100; 296 | 297 | o->psibuf = cbuf_init(50 * 1316, "psi"); 298 | if (!o->psibuf) { 299 | LOGf("ERROR: Can't allocate PSI input buffer\n"); 300 | exit(1); 301 | } 302 | cbuf_poison(o->psibuf, 'Y'); 303 | 304 | return o; 305 | } 306 | 307 | void output_open_file(char *filename, OUTPUT *o) { 308 | int fd = -1; 309 | if (strcmp(filename, "-") == 0) { 310 | fd = STDOUT_FILENO; 311 | } else { 312 | fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0660); 313 | } 314 | if (fd == -1) { 315 | perror("Cannot open output file\n"); 316 | exit(1); 317 | } 318 | o->ofd = fd; 319 | } 320 | 321 | void obuf_reset(OBUF *ob) { 322 | int i; 323 | memset(ob->buf, 0xff, ob->size); 324 | for (i=0; isize; i+=188) { 325 | ob->buf[i+0] = 0x47; 326 | ob->buf[i+1] = 0x1f; 327 | ob->buf[i+2] = 0xff; 328 | ob->buf[i+3] = 0x00; 329 | } 330 | ob->written = 0; 331 | ob->status = obuf_empty; 332 | } 333 | 334 | void output_buffer_alloc(OUTPUT *o, double output_bitrate) { 335 | if (!output_bitrate) { 336 | LOGf("No output bitrate, can't determine buffer!\n"); 337 | exit(1); 338 | } 339 | 340 | o->output_bitrate = output_bitrate; 341 | 342 | long pps = ceil((double)output_bitrate / (FRAME_PACKET_SIZE * 8)); // Packets per second 343 | long ppms = ceil((double)pps / ((double)1000 / o->obuf_ms)); // Packets per o->buffer_ms miliseconds 344 | long obuf_size = ppms * 1316; 345 | 346 | o->obuf[0].size = obuf_size; 347 | o->obuf[0].status = obuf_empty; 348 | o->obuf[0].buf = malloc(o->obuf[0].size); 349 | obuf_reset(&o->obuf[0]); 350 | 351 | o->obuf[1].size = obuf_size; 352 | o->obuf[1].status = obuf_empty; 353 | o->obuf[1].buf = malloc(o->obuf[0].size); 354 | obuf_reset(&o->obuf[1]); 355 | 356 | if (!config->quiet) { 357 | LOGf("\tOutput buf size : %ld * 2 = %ld\n", obuf_size, obuf_size * 2); 358 | LOGf("\tOutput buf packets: %ld (188 bytes)\n", obuf_size / 188); 359 | LOGf("\tOutput buf frames : %ld (1316 bytes)\n", obuf_size / 1316); 360 | LOGf("\tOutput buf ms : %u ms\n", o->obuf_ms); 361 | } 362 | } 363 | 364 | void output_free(OUTPUT **po) { 365 | OUTPUT *o = *po; 366 | if (!o) 367 | return; 368 | if (o->out_sock > -1) 369 | shutdown_fd(&(o->out_sock)); 370 | if (o->ofd) 371 | close(o->ofd); 372 | cbuf_free(&o->psibuf); 373 | FREE(o->obuf[0].buf); 374 | FREE(o->obuf[1].buf); 375 | output_psi_free(o); 376 | FREE(*po); 377 | } 378 | 379 | 380 | NIT *nit_new(uint16_t ts_id, char *freq, char *mod, char *symbol_rate) { 381 | char tmp[9]; 382 | unsigned i, pos; 383 | 384 | if (strlen(freq) != 9 || strlen(symbol_rate) != 8) 385 | return NULL; 386 | NIT *n = calloc(1, sizeof(NIT)); 387 | n->freq = strdup(freq); 388 | n->modulation = strdup(mod); 389 | n->symbol_rate = strdup(symbol_rate); 390 | n->ts_id = ts_id; 391 | 392 | n->_modulation = 393 | strcmp(mod, "16-QAM") == 0 ? 0x01 : 394 | strcmp(mod, "32-QAM") == 0 ? 0x02 : 395 | strcmp(mod, "64-QAM") == 0 ? 0x03 : 396 | strcmp(mod, "128-QAM") == 0 ? 0x04 : 397 | strcmp(mod, "256-QAM") == 0 ? 0x05 : 0x00; 398 | 399 | memset(tmp, 0, sizeof(tmp)); 400 | pos = 0; 401 | for (i=0;i_freq = strtol(tmp, NULL, 16); 408 | 409 | memset(tmp, 0, sizeof(tmp)); 410 | pos = 0; 411 | for (i=0;i_symbol_rate = strtol(tmp, NULL, 16); 418 | 419 | return n; 420 | } 421 | 422 | void nit_free(NIT **pn) { 423 | NIT *n = *pn; 424 | if (n) { 425 | FREE(n->freq); 426 | FREE(n->modulation); 427 | FREE(n->symbol_rate); 428 | FREE(*pn); 429 | } 430 | } 431 | 432 | void proxy_log(INPUT *r, char *msg) { 433 | if (!config->quiet) 434 | LOGf("INPUT : [%-12s] %s fd: %d src: %s\n", r->channel->id, msg, r->sock, r->channel->source); 435 | } 436 | 437 | void proxy_close(LIST *inputs, INPUT **input) { 438 | proxy_log(*input, "Stop"); 439 | // If there are no clients left, no "Timeout" messages will be logged 440 | list_del_entry(inputs, *input); 441 | input_free(input); 442 | } 443 | -------------------------------------------------------------------------------- /data.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd data header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef DATA_H 19 | #define DATA_H 20 | 21 | /* How much to wait for connection to be established with channel source (miliseconds) */ 22 | #define PROXY_CONNECT_TIMEOUT 1000 23 | 24 | /* Seconds to sleep between retries (miliseconds) */ 25 | #define PROXY_RETRY_TIMEOUT 1000 26 | 27 | /* 7 * 188 */ 28 | #define FRAME_PACKET_SIZE 1316 29 | 30 | #define RTP_HEADER_SIZE 12 31 | 32 | #include "libfuncs/libfuncs.h" 33 | 34 | #include "libtsfuncs/tsdata.h" 35 | 36 | #include "pidref.h" 37 | 38 | typedef enum { udp_sock, tcp_sock } channel_source; 39 | 40 | typedef struct { 41 | channel_source sproto; 42 | char *proto; 43 | char *host; 44 | char *path; 45 | unsigned int port; 46 | unsigned int rtp; 47 | } CHANSRC; 48 | 49 | #define MAX_CHANNEL_SOURCES 8 50 | 51 | typedef struct { 52 | uint16_t event_id; 53 | time_t start; 54 | int duration; 55 | char * event; 56 | char * short_desc; 57 | char * long_desc; 58 | } EPG_ENTRY; 59 | 60 | typedef struct { 61 | /* Config */ 62 | int index; 63 | int base_pid; 64 | int service_id; 65 | int pmt_pid; 66 | int radio; 67 | int lcn; 68 | int lcn_visible; 69 | char * id; 70 | char * name; 71 | int eit_mode; /* 0 = ignore EIT data from input 72 | 1 = forward EIT data for the configured service, ignore any other EIT data 73 | */ 74 | /* Sources */ 75 | char * source; /* Full source url */ 76 | char * sources[MAX_CHANNEL_SOURCES]; 77 | uint8_t num_src; 78 | uint8_t curr_src; 79 | int worktime_start; 80 | int worktime_end; 81 | 82 | /* EPG */ 83 | uint8_t epg_version:5; 84 | 85 | EPG_ENTRY * epg_now; 86 | EPG_ENTRY * epg_next; 87 | 88 | struct ts_eit * eit_now; 89 | struct ts_eit * eit_next; 90 | } CHANNEL; 91 | 92 | typedef struct { 93 | uint64_t pcr; 94 | uint64_t last_pcr; 95 | int bytes; 96 | int ts_packets_per_output_bitrate; 97 | } PCR; 98 | 99 | typedef struct { 100 | PIDREF *pidref; /* Rewritten pids list */ 101 | 102 | uint16_t nit_pid; /* Pid of the NIT, default 0x10 */ 103 | uint16_t pmt_pid; /* Pid of the original PMT, used to replace PMT */ 104 | uint16_t pcr_pid; /* PCR pid */ 105 | 106 | struct ts_pat *pat; /* The PAT */ 107 | struct ts_pmt *pmt; /* The PMT */ 108 | struct ts_eit *eit; /* The EIT */ 109 | 110 | uint64_t input_pcr; // Latest PCR entered into input buffer 111 | 112 | uint8_t pid_pat_cont:4; 113 | uint8_t pid_pmt_cont:4; 114 | uint8_t pid_eit_cont : 4; 115 | struct ts_pat *pat_rewritten; /* The rewritten PAT */ 116 | struct ts_pmt *pmt_rewritten; /* The rewritten PMT */ 117 | struct ts_eit *eit_rewritten; /* The rewritten EIT */ 118 | 119 | struct ts_pat *last_pat; /* The last incoming PAT */ 120 | struct ts_pmt *last_pmt; /* The last incoming PMT */ 121 | } INPUT_STREAM; 122 | 123 | typedef struct { 124 | char *name; 125 | CHANNEL *channel; 126 | int sock; /* Server socket */ 127 | struct sockaddr_in src_sockname; 128 | int reconnect:1, /* Set to 1 to force proxy reconnect */ 129 | connected:1, /* It's set to 1 when proxy is connected and serving clients */ 130 | insert_eit:1, /* When set to 1 input adds EIT table into stream (if there is info) */ 131 | working:1, /* Set to 1 if the input is in worktime */ 132 | dienow:1, /* Stop serving clients and exit now */ 133 | freechannel:1; /* Free channel data on object free (this is used in chanconf) */ 134 | int cookie; /* Used in chanconf to determine if the restreamer is alrady checked */ 135 | int ifd; 136 | 137 | pthread_t thread; 138 | 139 | uint16_t output_pcr_pid; 140 | uint64_t output_last_pcr; // The PCR before latest outputed 141 | uint64_t output_pcr; // Latest outputed PCR 142 | int output_pcr_packets_needed; // Based on selected output rate how much packets should be between output_pcr and output_last_pcr 143 | int outputed_packets; // How much packets have been sent. This is reset on every PCR and incremented on every sent packet (every input and output padding) 144 | 145 | int disabled; /* Input is disabled, no data is fed to output buffers */ 146 | int input_ready; /* Set to 1 from INPUT thread when input is ready to be mixed in output */ 147 | 148 | CBUF *buf; // Input buffer */ 149 | 150 | INPUT_STREAM stream; 151 | } INPUT; 152 | 153 | typedef enum { 154 | obuf_empty = 0, // Buffer is empty and can be used by mix thread 155 | obuf_filling = 1, // Buffer is being filled by mix thread 156 | obuf_full = 2, // Buffer is filled and can be used by write thread 157 | obuf_emptying = 3, // Buffer is being emptyed by write thread 158 | } OBUF_STATUS; 159 | 160 | typedef struct { 161 | uint8_t * buf; 162 | int size; // Output buffer size (must be size % 1316 == 0) 163 | int written; 164 | OBUF_STATUS status; 165 | } OBUF; 166 | 167 | typedef struct { 168 | struct in_addr out_host; 169 | int out_port; 170 | int out_sock; /* The udp socket */ 171 | int ofd; 172 | int dienow; /* Instruct output to die */ 173 | 174 | pthread_t psi_thread; 175 | pthread_t mix_thread; 176 | pthread_t write_thread; 177 | 178 | CBUF *psibuf; // Input buffer */ 179 | 180 | unsigned int obuf_ms; // How much miliseconds of data output buffer holds 181 | OBUF obuf[2]; // Output buffers 182 | double output_bitrate; // Output bitrate (bps) 183 | 184 | uint64_t traffic; 185 | 186 | uint64_t traffic_period; 187 | uint64_t padding_period; 188 | 189 | uint8_t pid_pat_cont:4; 190 | uint8_t pid_nit_cont:4; 191 | uint8_t pid_sdt_cont:4; 192 | uint8_t pid_eit_cont:4; 193 | uint8_t pid_tdt_cont:4; 194 | 195 | struct ts_pat *pat; 196 | struct timeval pat_ts; 197 | 198 | struct ts_sdt *sdt; 199 | struct timeval sdt_ts; 200 | 201 | struct ts_nit *nit; 202 | struct timeval nit_ts; 203 | 204 | struct ts_tdt *tdt; 205 | struct timeval tdt_ts; 206 | 207 | struct ts_tdt *tot; 208 | struct timeval tot_ts; 209 | 210 | struct timeval eit_ts; 211 | 212 | uint64_t last_org_pcr[8193]; // Last PCR value indexed by PID x 213 | uint64_t last_pcr[8193]; // Last PCR value indexed by PID x 214 | uint64_t last_traffic[8193]; // Last traffic when PCR with PID x was seen 215 | 216 | uint16_t rtp_sequence_number; // RTP sequence number 217 | uint32_t rtp_ssrc; // use RTP is != 0 218 | } OUTPUT; 219 | 220 | typedef struct { 221 | char *freq; 222 | char *modulation; 223 | char *symbol_rate; 224 | uint16_t ts_id; 225 | uint32_t _freq; 226 | uint8_t _modulation; 227 | uint32_t _symbol_rate; 228 | } NIT; 229 | 230 | EPG_ENTRY * epg_new (time_t start, int duration, char *encoding, char *event, char *short_desc, char *long_desc); 231 | void epg_free (EPG_ENTRY **e); 232 | int epg_changed (EPG_ENTRY *a, EPG_ENTRY *b); 233 | 234 | CHANNEL * channel_new (int service_id, int is_radio, const char *id, const char *name, int eit_mode, const char *source, int channel_index, int lcn, int is_lcn_visible); 235 | void channel_free (CHANNEL **c); 236 | void channel_free_epg(CHANNEL *c); 237 | 238 | channel_source get_sproto(char *url); 239 | int is_rtp(char *url); 240 | 241 | CHANSRC * chansrc_init (char *url); 242 | void chansrc_free (CHANSRC **url); 243 | void chansrc_add (CHANNEL *c, const char *src); 244 | void chansrc_next (CHANNEL *c); 245 | void chansrc_set (CHANNEL *c, uint8_t src_id); 246 | 247 | INPUT * input_new (const char *name, CHANNEL *channel); 248 | void input_free (INPUT **input); 249 | 250 | void input_stream_reset (INPUT *input); 251 | 252 | OUTPUT * output_new (); 253 | void output_free (OUTPUT **output); 254 | void output_open_file (char *filename, OUTPUT *o); 255 | void output_buffer_alloc (OUTPUT *o, double output_bitrate); 256 | void obuf_reset (OBUF *ob); 257 | 258 | NIT * nit_new (uint16_t ts_id, char *freq, char *modulation, char *symbol_rate); 259 | void nit_free (NIT **nit); 260 | 261 | void proxy_log (INPUT *r, char *msg); 262 | void proxy_close (LIST *inputs, INPUT **input); 263 | 264 | #endif 265 | -------------------------------------------------------------------------------- /inidict.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2000-2007 by Nicolas Devillard. 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /*-------------------------------------------------------------------------*/ 25 | /** 26 | @file dictionary.c 27 | @author N. Devillard 28 | @date Sep 2007 29 | @version Revision: 1.27 30 | @brief Implements a dictionary for string variables. 31 | 32 | This module implements a simple dictionary object, i.e. a list 33 | of string/string associations. This object is useful to store e.g. 34 | informations retrieved from a configuration file (ini files). 35 | */ 36 | /*--------------------------------------------------------------------------*/ 37 | 38 | /* 39 | Id: inidict.c,v 1.2 2010/04/26 09:35:52 gf Exp 40 | Revision: 1.2 41 | */ 42 | /*--------------------------------------------------------------------------- 43 | Includes 44 | ---------------------------------------------------------------------------*/ 45 | #include "inidict.h" 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | /** Maximum value size for integers and doubles. */ 53 | #define MAXVALSZ 1024 54 | 55 | /** Minimal allocated number of entries in a dictionary */ 56 | #define DICTMINSZ 128 57 | 58 | /** Invalid key token */ 59 | #define DICT_INVALID_KEY ((char*)-1) 60 | 61 | /*--------------------------------------------------------------------------- 62 | Private functions 63 | ---------------------------------------------------------------------------*/ 64 | 65 | /* Doubles the allocated size associated to a pointer */ 66 | /* 'size' is the current allocated size. */ 67 | static void * mem_double(void * ptr, int size) 68 | { 69 | void * newptr ; 70 | 71 | newptr = calloc(2*size, 1); 72 | if (newptr==NULL) { 73 | return NULL ; 74 | } 75 | memcpy(newptr, ptr, size); 76 | free(ptr); 77 | return newptr ; 78 | } 79 | 80 | /*-------------------------------------------------------------------------*/ 81 | /** 82 | @brief Duplicate a string 83 | @param s String to duplicate 84 | @return Pointer to a newly allocated string, to be freed with free() 85 | 86 | This is a replacement for strdup(). This implementation is provided 87 | for systems that do not have it. 88 | */ 89 | /*--------------------------------------------------------------------------*/ 90 | static char * xstrdup(char * s) 91 | { 92 | char * t ; 93 | if (!s) 94 | return NULL ; 95 | t = malloc(strlen(s)+1) ; 96 | if (t) { 97 | strcpy(t,s); 98 | } 99 | return t ; 100 | } 101 | 102 | /*--------------------------------------------------------------------------- 103 | Function codes 104 | ---------------------------------------------------------------------------*/ 105 | /*-------------------------------------------------------------------------*/ 106 | /** 107 | @brief Compute the hash key for a string. 108 | @param key Character string to use for key. 109 | @return 1 unsigned int on at least 32 bits. 110 | 111 | This hash function has been taken from an Article in Dr Dobbs Journal. 112 | This is normally a collision-free function, distributing keys evenly. 113 | The key is stored anyway in the struct so that collision can be avoided 114 | by comparing the key itself in last resort. 115 | */ 116 | /*--------------------------------------------------------------------------*/ 117 | unsigned dictionary_hash(char * key) 118 | { 119 | int len ; 120 | unsigned hash ; 121 | int i ; 122 | 123 | len = strlen(key); 124 | for (hash=0, i=0 ; i>6) ; 128 | } 129 | hash += (hash <<3); 130 | hash ^= (hash >>11); 131 | hash += (hash <<15); 132 | return hash ; 133 | } 134 | 135 | /*-------------------------------------------------------------------------*/ 136 | /** 137 | @brief Create a new dictionary object. 138 | @param size Optional initial size of the dictionary. 139 | @return 1 newly allocated dictionary objet. 140 | 141 | This function allocates a new dictionary object of given size and returns 142 | it. If you do not know in advance (roughly) the number of entries in the 143 | dictionary, give size=0. 144 | */ 145 | /*--------------------------------------------------------------------------*/ 146 | dictionary * dictionary_new(int size) 147 | { 148 | dictionary * d ; 149 | 150 | /* If no size was specified, allocate space for DICTMINSZ */ 151 | if (sizesize = size ; 157 | d->val = (char **)calloc(size, sizeof(char*)); 158 | d->key = (char **)calloc(size, sizeof(char*)); 159 | d->hash = (unsigned int *)calloc(size, sizeof(unsigned)); 160 | return d ; 161 | } 162 | 163 | /*-------------------------------------------------------------------------*/ 164 | /** 165 | @brief Delete a dictionary object 166 | @param d dictionary object to deallocate. 167 | @return void 168 | 169 | Deallocate a dictionary object and all memory associated to it. 170 | */ 171 | /*--------------------------------------------------------------------------*/ 172 | void dictionary_del(dictionary * d) 173 | { 174 | int i ; 175 | 176 | if (d==NULL) return ; 177 | for (i=0 ; isize ; i++) { 178 | if (d->key[i]!=NULL) 179 | free(d->key[i]); 180 | if (d->val[i]!=NULL) 181 | free(d->val[i]); 182 | } 183 | free(d->val); 184 | free(d->key); 185 | free(d->hash); 186 | free(d); 187 | return ; 188 | } 189 | 190 | /*-------------------------------------------------------------------------*/ 191 | /** 192 | @brief Get a value from a dictionary. 193 | @param d dictionary object to search. 194 | @param key Key to look for in the dictionary. 195 | @param def Default value to return if key not found. 196 | @return 1 pointer to internally allocated character string. 197 | 198 | This function locates a key in a dictionary and returns a pointer to its 199 | value, or the passed 'def' pointer if no such key can be found in 200 | dictionary. The returned character pointer points to data internal to the 201 | dictionary object, you should not try to free it or modify it. 202 | */ 203 | /*--------------------------------------------------------------------------*/ 204 | char * dictionary_get(dictionary * d, char * key, char * def) 205 | { 206 | unsigned hash ; 207 | int i ; 208 | 209 | hash = dictionary_hash(key); 210 | for (i=0 ; isize ; i++) { 211 | if (d->key[i]==NULL) 212 | continue ; 213 | /* Compare hash */ 214 | if (hash==d->hash[i]) { 215 | /* Compare string, to avoid hash collisions */ 216 | if (!strcmp(key, d->key[i])) { 217 | return d->val[i] ; 218 | } 219 | } 220 | } 221 | return def ; 222 | } 223 | 224 | /*-------------------------------------------------------------------------*/ 225 | /** 226 | @brief Set a value in a dictionary. 227 | @param d dictionary object to modify. 228 | @param key Key to modify or add. 229 | @param val Value to add. 230 | @return int 0 if Ok, anything else otherwise 231 | 232 | If the given key is found in the dictionary, the associated value is 233 | replaced by the provided one. If the key cannot be found in the 234 | dictionary, it is added to it. 235 | 236 | It is Ok to provide a NULL value for val, but NULL values for the dictionary 237 | or the key are considered as errors: the function will return immediately 238 | in such a case. 239 | 240 | Notice that if you dictionary_set a variable to NULL, a call to 241 | dictionary_get will return a NULL value: the variable will be found, and 242 | its value (NULL) is returned. In other words, setting the variable 243 | content to NULL is equivalent to deleting the variable from the 244 | dictionary. It is not possible (in this implementation) to have a key in 245 | the dictionary without value. 246 | 247 | This function returns non-zero in case of failure. 248 | */ 249 | /*--------------------------------------------------------------------------*/ 250 | int dictionary_set(dictionary * d, char * key, char * val) 251 | { 252 | int i ; 253 | unsigned hash ; 254 | 255 | if (d==NULL || key==NULL) return -1 ; 256 | 257 | /* Compute hash for this key */ 258 | hash = dictionary_hash(key) ; 259 | /* Find if value is already in dictionary */ 260 | if (d->n>0) { 261 | for (i=0 ; isize ; i++) { 262 | if (d->key[i]==NULL) 263 | continue ; 264 | if (hash==d->hash[i]) { /* Same hash value */ 265 | if (!strcmp(key, d->key[i])) { /* Same key */ 266 | /* Found a value: modify and return */ 267 | if (d->val[i]!=NULL) 268 | free(d->val[i]); 269 | d->val[i] = val ? xstrdup(val) : NULL ; 270 | /* Value has been modified: return */ 271 | return 0 ; 272 | } 273 | } 274 | } 275 | } 276 | /* Add a new value */ 277 | /* See if dictionary needs to grow */ 278 | if (d->n==d->size) { 279 | 280 | /* Reached maximum size: reallocate dictionary */ 281 | d->val = (char **)mem_double(d->val, d->size * sizeof(char*)) ; 282 | d->key = (char **)mem_double(d->key, d->size * sizeof(char*)) ; 283 | d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ; 284 | if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) { 285 | /* Cannot grow dictionary */ 286 | return -1 ; 287 | } 288 | /* Double size */ 289 | d->size *= 2 ; 290 | } 291 | 292 | /* Insert key in the first empty slot */ 293 | for (i=0 ; isize ; i++) { 294 | if (d->key[i]==NULL) { 295 | /* Add key here */ 296 | break ; 297 | } 298 | } 299 | /* Copy key */ 300 | d->key[i] = xstrdup(key); 301 | d->val[i] = val ? xstrdup(val) : NULL ; 302 | d->hash[i] = hash; 303 | d->n ++ ; 304 | return 0 ; 305 | } 306 | 307 | /*-------------------------------------------------------------------------*/ 308 | /** 309 | @brief Delete a key in a dictionary 310 | @param d dictionary object to modify. 311 | @param key Key to remove. 312 | @return void 313 | 314 | This function deletes a key in a dictionary. Nothing is done if the 315 | key cannot be found. 316 | */ 317 | /*--------------------------------------------------------------------------*/ 318 | void dictionary_unset(dictionary * d, char * key) 319 | { 320 | unsigned hash ; 321 | int i ; 322 | 323 | if (key == NULL) { 324 | return; 325 | } 326 | 327 | hash = dictionary_hash(key); 328 | for (i=0 ; isize ; i++) { 329 | if (d->key[i]==NULL) 330 | continue ; 331 | /* Compare hash */ 332 | if (hash==d->hash[i]) { 333 | /* Compare string, to avoid hash collisions */ 334 | if (!strcmp(key, d->key[i])) { 335 | /* Found key */ 336 | break ; 337 | } 338 | } 339 | } 340 | if (i>=d->size) 341 | /* Key not found */ 342 | return ; 343 | 344 | free(d->key[i]); 345 | d->key[i] = NULL ; 346 | if (d->val[i]!=NULL) { 347 | free(d->val[i]); 348 | d->val[i] = NULL ; 349 | } 350 | d->hash[i] = 0 ; 351 | d->n -- ; 352 | return ; 353 | } 354 | 355 | /*-------------------------------------------------------------------------*/ 356 | /** 357 | @brief Dump a dictionary to an opened file pointer. 358 | @param d Dictionary to dump 359 | @param f Opened file pointer. 360 | @return void 361 | 362 | Dumps a dictionary onto an opened file pointer. Key pairs are printed out 363 | as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as 364 | output file pointers. 365 | */ 366 | /*--------------------------------------------------------------------------*/ 367 | void dictionary_dump(dictionary * d, FILE * out) 368 | { 369 | int i ; 370 | 371 | if (d==NULL || out==NULL) return ; 372 | if (d->n<1) { 373 | fprintf(out, "empty dictionary\n"); 374 | return ; 375 | } 376 | for (i=0 ; isize ; i++) { 377 | if (d->key[i]) { 378 | fprintf(out, "%20s\t[%s]\n", 379 | d->key[i], 380 | d->val[i] ? d->val[i] : "UNDEF"); 381 | } 382 | } 383 | return ; 384 | } 385 | 386 | 387 | /* Test code */ 388 | #ifdef TESTDIC 389 | #define NVALS 20000 390 | int main(int argc, char *argv[]) 391 | { 392 | dictionary * d ; 393 | char * val ; 394 | int i ; 395 | char cval[90] ; 396 | 397 | /* Allocate dictionary */ 398 | printf("allocating...\n"); 399 | d = dictionary_new(0); 400 | 401 | /* Set values in dictionary */ 402 | printf("setting %d values...\n", NVALS); 403 | for (i=0 ; in != 0) { 421 | printf("error deleting values\n"); 422 | } 423 | printf("deallocating...\n"); 424 | dictionary_del(d); 425 | return 0 ; 426 | } 427 | #endif 428 | /* vim: set ts=4 et sw=4 tw=75 */ 429 | -------------------------------------------------------------------------------- /inidict.h: -------------------------------------------------------------------------------- 1 | 2 | /*-------------------------------------------------------------------------*/ 3 | /** 4 | @file dictionary.h 5 | @author N. Devillard 6 | @date Sep 2007 7 | @version Revision: 1.12 8 | @brief Implements a dictionary for string variables. 9 | 10 | This module implements a simple dictionary object, i.e. a list 11 | of string/string associations. This object is useful to store e.g. 12 | informations retrieved from a configuration file (ini files). 13 | */ 14 | /*--------------------------------------------------------------------------*/ 15 | 16 | /* 17 | Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp 18 | Author: ndevilla 19 | Date: 2007-11-23 21:37:00 20 | Revision: 1.12 21 | */ 22 | 23 | #ifndef _DICTIONARY_H_ 24 | #define _DICTIONARY_H_ 25 | 26 | /*--------------------------------------------------------------------------- 27 | Includes 28 | ---------------------------------------------------------------------------*/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | /*--------------------------------------------------------------------------- 36 | New types 37 | ---------------------------------------------------------------------------*/ 38 | 39 | 40 | /*-------------------------------------------------------------------------*/ 41 | /** 42 | @brief Dictionary object 43 | 44 | This object contains a list of string/string associations. Each 45 | association is identified by a unique string key. Looking up values 46 | in the dictionary is speeded up by the use of a (hopefully collision-free) 47 | hash function. 48 | */ 49 | /*-------------------------------------------------------------------------*/ 50 | typedef struct _dictionary_ { 51 | int n ; /** Number of entries in dictionary */ 52 | int size ; /** Storage size */ 53 | char ** val ; /** List of string values */ 54 | char ** key ; /** List of string keys */ 55 | unsigned * hash ; /** List of hash values for keys */ 56 | } dictionary ; 57 | 58 | 59 | /*--------------------------------------------------------------------------- 60 | Function prototypes 61 | ---------------------------------------------------------------------------*/ 62 | 63 | /*-------------------------------------------------------------------------*/ 64 | /** 65 | @brief Compute the hash key for a string. 66 | @param key Character string to use for key. 67 | @return 1 unsigned int on at least 32 bits. 68 | 69 | This hash function has been taken from an Article in Dr Dobbs Journal. 70 | This is normally a collision-free function, distributing keys evenly. 71 | The key is stored anyway in the struct so that collision can be avoided 72 | by comparing the key itself in last resort. 73 | */ 74 | /*--------------------------------------------------------------------------*/ 75 | unsigned dictionary_hash(char * key); 76 | 77 | /*-------------------------------------------------------------------------*/ 78 | /** 79 | @brief Create a new dictionary object. 80 | @param size Optional initial size of the dictionary. 81 | @return 1 newly allocated dictionary objet. 82 | 83 | This function allocates a new dictionary object of given size and returns 84 | it. If you do not know in advance (roughly) the number of entries in the 85 | dictionary, give size=0. 86 | */ 87 | /*--------------------------------------------------------------------------*/ 88 | dictionary * dictionary_new(int size); 89 | 90 | /*-------------------------------------------------------------------------*/ 91 | /** 92 | @brief Delete a dictionary object 93 | @param d dictionary object to deallocate. 94 | @return void 95 | 96 | Deallocate a dictionary object and all memory associated to it. 97 | */ 98 | /*--------------------------------------------------------------------------*/ 99 | void dictionary_del(dictionary * vd); 100 | 101 | /*-------------------------------------------------------------------------*/ 102 | /** 103 | @brief Get a value from a dictionary. 104 | @param d dictionary object to search. 105 | @param key Key to look for in the dictionary. 106 | @param def Default value to return if key not found. 107 | @return 1 pointer to internally allocated character string. 108 | 109 | This function locates a key in a dictionary and returns a pointer to its 110 | value, or the passed 'def' pointer if no such key can be found in 111 | dictionary. The returned character pointer points to data internal to the 112 | dictionary object, you should not try to free it or modify it. 113 | */ 114 | /*--------------------------------------------------------------------------*/ 115 | char * dictionary_get(dictionary * d, char * key, char * def); 116 | 117 | 118 | /*-------------------------------------------------------------------------*/ 119 | /** 120 | @brief Set a value in a dictionary. 121 | @param d dictionary object to modify. 122 | @param key Key to modify or add. 123 | @param val Value to add. 124 | @return int 0 if Ok, anything else otherwise 125 | 126 | If the given key is found in the dictionary, the associated value is 127 | replaced by the provided one. If the key cannot be found in the 128 | dictionary, it is added to it. 129 | 130 | It is Ok to provide a NULL value for val, but NULL values for the dictionary 131 | or the key are considered as errors: the function will return immediately 132 | in such a case. 133 | 134 | Notice that if you dictionary_set a variable to NULL, a call to 135 | dictionary_get will return a NULL value: the variable will be found, and 136 | its value (NULL) is returned. In other words, setting the variable 137 | content to NULL is equivalent to deleting the variable from the 138 | dictionary. It is not possible (in this implementation) to have a key in 139 | the dictionary without value. 140 | 141 | This function returns non-zero in case of failure. 142 | */ 143 | /*--------------------------------------------------------------------------*/ 144 | int dictionary_set(dictionary * vd, char * key, char * val); 145 | 146 | /*-------------------------------------------------------------------------*/ 147 | /** 148 | @brief Delete a key in a dictionary 149 | @param d dictionary object to modify. 150 | @param key Key to remove. 151 | @return void 152 | 153 | This function deletes a key in a dictionary. Nothing is done if the 154 | key cannot be found. 155 | */ 156 | /*--------------------------------------------------------------------------*/ 157 | void dictionary_unset(dictionary * d, char * key); 158 | 159 | 160 | /*-------------------------------------------------------------------------*/ 161 | /** 162 | @brief Dump a dictionary to an opened file pointer. 163 | @param d Dictionary to dump 164 | @param f Opened file pointer. 165 | @return void 166 | 167 | Dumps a dictionary onto an opened file pointer. Key pairs are printed out 168 | as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as 169 | output file pointers. 170 | */ 171 | /*--------------------------------------------------------------------------*/ 172 | void dictionary_dump(dictionary * d, FILE * out); 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /iniparser.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2000-2007 by Nicolas Devillard. 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /*-------------------------------------------------------------------------*/ 25 | /** 26 | @file iniparser.c 27 | @author N. Devillard 28 | @date Sep 2007 29 | @version 3.0 30 | @brief Parser for ini files. 31 | */ 32 | /*--------------------------------------------------------------------------*/ 33 | /* 34 | Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp 35 | Revision: 2.18 36 | Date: 2008-01-03 18:35:39 37 | */ 38 | /*---------------------------- Includes ------------------------------------*/ 39 | #include 40 | #include "iniparser.h" 41 | 42 | /*---------------------------- Defines -------------------------------------*/ 43 | #define ASCIILINESZ (1024) 44 | #define INI_INVALID_KEY ((char*)-1) 45 | #define MSG_PREFIX "confparser: " 46 | 47 | /*--------------------------------------------------------------------------- 48 | Private to this module 49 | ---------------------------------------------------------------------------*/ 50 | /** 51 | * This enum stores the status for each parsed line (internal use only). 52 | */ 53 | typedef enum _line_status_ { 54 | LINE_UNPROCESSED, 55 | LINE_ERROR, 56 | LINE_EMPTY, 57 | LINE_COMMENT, 58 | LINE_SECTION, 59 | LINE_VALUE 60 | } line_status ; 61 | 62 | /*-------------------------------------------------------------------------*/ 63 | /** 64 | @brief Convert a string to lowercase. 65 | @param s String to convert. 66 | @return ptr to statically allocated string. 67 | 68 | This function returns a pointer to a statically allocated string 69 | containing a lowercased version of the input string. Do not free 70 | or modify the returned string! Since the returned string is statically 71 | allocated, it will be modified at each function call (not re-entrant). 72 | */ 73 | /*--------------------------------------------------------------------------*/ 74 | static char * strlwc(const char * s) 75 | { 76 | static char l[ASCIILINESZ+1]; 77 | int i ; 78 | 79 | if (s==NULL) return NULL ; 80 | memset(l, 0, ASCIILINESZ+1); 81 | i=0 ; 82 | while (s[i] && i l) { 116 | if (!isspace((int)*(last-1))) 117 | break ; 118 | last -- ; 119 | } 120 | *last = (char)0; 121 | return (char*)l ; 122 | } 123 | 124 | /*-------------------------------------------------------------------------*/ 125 | /** 126 | @brief Get number of sections in a dictionary 127 | @param d Dictionary to examine 128 | @return int Number of sections found in dictionary 129 | 130 | This function returns the number of sections found in a dictionary. 131 | The test to recognize sections is done on the string stored in the 132 | dictionary: a section name is given as "section" whereas a key is 133 | stored as "section:key", thus the test looks for entries that do not 134 | contain a colon. 135 | 136 | This clearly fails in the case a section name contains a colon, but 137 | this should simply be avoided. 138 | 139 | This function returns -1 in case of error. 140 | */ 141 | /*--------------------------------------------------------------------------*/ 142 | int iniparser_getnsec(dictionary * d) 143 | { 144 | int i ; 145 | int nsec ; 146 | 147 | if (d==NULL) return -1 ; 148 | nsec=0 ; 149 | for (i=0 ; isize ; i++) { 150 | if (d->key[i]==NULL) 151 | continue ; 152 | if (strchr(d->key[i], ':')==NULL) { 153 | nsec ++ ; 154 | } 155 | } 156 | return nsec ; 157 | } 158 | 159 | /*-------------------------------------------------------------------------*/ 160 | /** 161 | @brief Get name for section n in a dictionary. 162 | @param d Dictionary to examine 163 | @param n Section number (from 0 to nsec-1). 164 | @return Pointer to char string 165 | 166 | This function locates the n-th section in a dictionary and returns 167 | its name as a pointer to a string statically allocated inside the 168 | dictionary. Do not free or modify the returned string! 169 | 170 | This function returns NULL in case of error. 171 | */ 172 | /*--------------------------------------------------------------------------*/ 173 | char * iniparser_getsecname(dictionary * d, int n) 174 | { 175 | int i ; 176 | int foundsec ; 177 | 178 | if (d==NULL || n<0) return NULL ; 179 | foundsec=0 ; 180 | for (i=0 ; isize ; i++) { 181 | if (d->key[i]==NULL) 182 | continue ; 183 | if (strchr(d->key[i], ':')==NULL) { 184 | foundsec++ ; 185 | if (foundsec>n) 186 | break ; 187 | } 188 | } 189 | if (foundsec<=n) { 190 | return NULL ; 191 | } 192 | return d->key[i] ; 193 | } 194 | 195 | /*-------------------------------------------------------------------------*/ 196 | /** 197 | @brief Dump a dictionary to an opened file pointer. 198 | @param d Dictionary to dump. 199 | @param f Opened file pointer to dump to. 200 | @return void 201 | 202 | This function prints out the contents of a dictionary, one element by 203 | line, onto the provided file pointer. It is OK to specify @c stderr 204 | or @c stdout as output files. This function is meant for debugging 205 | purposes mostly. 206 | */ 207 | /*--------------------------------------------------------------------------*/ 208 | void iniparser_dump(dictionary * d, FILE * f) 209 | { 210 | int i ; 211 | 212 | if (d==NULL || f==NULL) return ; 213 | for (i=0 ; isize ; i++) { 214 | if (d->key[i]==NULL) 215 | continue ; 216 | if (d->val[i]!=NULL) { 217 | fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); 218 | } else { 219 | fprintf(f, "[%s]=UNDEF\n", d->key[i]); 220 | } 221 | } 222 | return ; 223 | } 224 | 225 | /*-------------------------------------------------------------------------*/ 226 | /** 227 | @brief Save a dictionary to a loadable ini file 228 | @param d Dictionary to dump 229 | @param f Opened file pointer to dump to 230 | @return void 231 | 232 | This function dumps a given dictionary into a loadable ini file. 233 | It is Ok to specify @c stderr or @c stdout as output files. 234 | */ 235 | /*--------------------------------------------------------------------------*/ 236 | void iniparser_dump_ini(dictionary * d, FILE * f) 237 | { 238 | int i, j ; 239 | char keym[ASCIILINESZ+1]; 240 | int nsec ; 241 | char * secname ; 242 | int seclen ; 243 | 244 | if (d==NULL || f==NULL) return ; 245 | 246 | nsec = iniparser_getnsec(d); 247 | if (nsec<1) { 248 | /* No section in file: dump all keys as they are */ 249 | for (i=0 ; isize ; i++) { 250 | if (d->key[i]==NULL) 251 | continue ; 252 | fprintf(f, "%s = %s\n", d->key[i], d->val[i]); 253 | } 254 | return ; 255 | } 256 | for (i=0 ; isize ; j++) { 262 | if (d->key[j]==NULL) 263 | continue ; 264 | if (!strncmp(d->key[j], keym, seclen+1)) { 265 | fprintf(f, 266 | "%-30s = %s\n", 267 | d->key[j]+seclen+1, 268 | d->val[j] ? d->val[j] : ""); 269 | } 270 | } 271 | } 272 | fprintf(f, "\n"); 273 | return ; 274 | } 275 | 276 | /*-------------------------------------------------------------------------*/ 277 | /** 278 | @brief Get the string associated to a key 279 | @param d Dictionary to search 280 | @param key Key string to look for 281 | @param def Default value to return if key not found. 282 | @return pointer to statically allocated character string 283 | 284 | This function queries a dictionary for a key. A key as read from an 285 | ini file is given as "section:key". If the key cannot be found, 286 | the pointer passed as 'def' is returned. 287 | The returned char pointer is pointing to a string allocated in 288 | the dictionary, do not free or modify it. 289 | */ 290 | /*--------------------------------------------------------------------------*/ 291 | char * iniparser_getstring(dictionary * d, const char * key, char * def) 292 | { 293 | char * lc_key ; 294 | char * sval ; 295 | 296 | if (d==NULL || key==NULL) 297 | return def ; 298 | 299 | lc_key = strlwc(key); 300 | sval = dictionary_get(d, lc_key, def); 301 | return sval ; 302 | } 303 | 304 | /*-------------------------------------------------------------------------*/ 305 | /** 306 | @brief Get the string associated to a key, convert to an int 307 | @param d Dictionary to search 308 | @param key Key string to look for 309 | @param notfound Value to return in case of error 310 | @return integer 311 | 312 | This function queries a dictionary for a key. A key as read from an 313 | ini file is given as "section:key". If the key cannot be found, 314 | the notfound value is returned. 315 | 316 | Supported values for integers include the usual C notation 317 | so decimal, octal (starting with 0) and hexadecimal (starting with 0x) 318 | are supported. Examples: 319 | 320 | "42" -> 42 321 | "042" -> 34 (octal -> decimal) 322 | "0x42" -> 66 (hexa -> decimal) 323 | 324 | Warning: the conversion may overflow in various ways. Conversion is 325 | totally outsourced to strtol(), see the associated man page for overflow 326 | handling. 327 | 328 | Credits: Thanks to A. Becker for suggesting strtol() 329 | */ 330 | /*--------------------------------------------------------------------------*/ 331 | int iniparser_getint(dictionary * d, const char * key, int notfound) 332 | { 333 | char * str ; 334 | 335 | str = iniparser_getstring(d, key, INI_INVALID_KEY); 336 | if (str==INI_INVALID_KEY) return notfound ; 337 | return (int)strtol(str, NULL, 0); 338 | } 339 | 340 | /*-------------------------------------------------------------------------*/ 341 | /** 342 | @brief Get the string associated to a key, convert to a double 343 | @param d Dictionary to search 344 | @param key Key string to look for 345 | @param notfound Value to return in case of error 346 | @return double 347 | 348 | This function queries a dictionary for a key. A key as read from an 349 | ini file is given as "section:key". If the key cannot be found, 350 | the notfound value is returned. 351 | */ 352 | /*--------------------------------------------------------------------------*/ 353 | double iniparser_getdouble(dictionary * d, char * key, double notfound) 354 | { 355 | char * str ; 356 | 357 | str = iniparser_getstring(d, key, INI_INVALID_KEY); 358 | if (str==INI_INVALID_KEY) return notfound ; 359 | return atof(str); 360 | } 361 | 362 | /*-------------------------------------------------------------------------*/ 363 | /** 364 | @brief Get the string associated to a key, convert to a boolean 365 | @param d Dictionary to search 366 | @param key Key string to look for 367 | @param notfound Value to return in case of error 368 | @return integer 369 | 370 | This function queries a dictionary for a key. A key as read from an 371 | ini file is given as "section:key". If the key cannot be found, 372 | the notfound value is returned. 373 | 374 | A true boolean is found if one of the following is matched: 375 | 376 | - A string starting with 'y' 377 | - A string starting with 'Y' 378 | - A string starting with 't' 379 | - A string starting with 'T' 380 | - A string starting with '1' 381 | 382 | A false boolean is found if one of the following is matched: 383 | 384 | - A string starting with 'n' 385 | - A string starting with 'N' 386 | - A string starting with 'f' 387 | - A string starting with 'F' 388 | - A string starting with '0' 389 | 390 | The notfound value returned if no boolean is identified, does not 391 | necessarily have to be 0 or 1. 392 | */ 393 | /*--------------------------------------------------------------------------*/ 394 | int iniparser_getboolean(dictionary * d, const char * key, int notfound) 395 | { 396 | char * c ; 397 | int ret ; 398 | 399 | c = iniparser_getstring(d, key, INI_INVALID_KEY); 400 | if (c==INI_INVALID_KEY) return notfound ; 401 | if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { 402 | ret = 1 ; 403 | } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { 404 | ret = 0 ; 405 | } else { 406 | ret = notfound ; 407 | } 408 | return ret; 409 | } 410 | 411 | /*-------------------------------------------------------------------------*/ 412 | /** 413 | @brief Finds out if a given entry exists in a dictionary 414 | @param ini Dictionary to search 415 | @param entry Name of the entry to look for 416 | @return integer 1 if entry exists, 0 otherwise 417 | 418 | Finds out if a given entry exists in the dictionary. Since sections 419 | are stored as keys with NULL associated values, this is the only way 420 | of querying for the presence of sections in a dictionary. 421 | */ 422 | /*--------------------------------------------------------------------------*/ 423 | int iniparser_find_entry( 424 | dictionary * ini, 425 | char * entry 426 | ) 427 | { 428 | int found=0 ; 429 | if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { 430 | found = 1 ; 431 | } 432 | return found ; 433 | } 434 | 435 | /*-------------------------------------------------------------------------*/ 436 | /** 437 | @brief Set an entry in a dictionary. 438 | @param ini Dictionary to modify. 439 | @param entry Entry to modify (entry name) 440 | @param val New value to associate to the entry. 441 | @return int 0 if Ok, -1 otherwise. 442 | 443 | If the given entry can be found in the dictionary, it is modified to 444 | contain the provided value. If it cannot be found, -1 is returned. 445 | It is Ok to set val to NULL. 446 | */ 447 | /*--------------------------------------------------------------------------*/ 448 | int iniparser_set(dictionary * ini, char * entry, char * val) 449 | { 450 | return dictionary_set(ini, strlwc(entry), val) ; 451 | } 452 | 453 | /*-------------------------------------------------------------------------*/ 454 | /** 455 | @brief Delete an entry in a dictionary 456 | @param ini Dictionary to modify 457 | @param entry Entry to delete (entry name) 458 | @return void 459 | 460 | If the given entry can be found, it is deleted from the dictionary. 461 | */ 462 | /*--------------------------------------------------------------------------*/ 463 | void iniparser_unset(dictionary * ini, char * entry) 464 | { 465 | dictionary_unset(ini, strlwc(entry)); 466 | } 467 | 468 | /*-------------------------------------------------------------------------*/ 469 | /** 470 | @brief Load a single line from an INI file 471 | @param input_line Input line, may be concatenated multi-line input 472 | @param section Output space to store section 473 | @param key Output space to store key 474 | @param value Output space to store value 475 | @return line_status value 476 | */ 477 | /*--------------------------------------------------------------------------*/ 478 | static line_status iniparser_line( 479 | char * input_line, 480 | char * section, 481 | char * key, 482 | char * value) 483 | { 484 | line_status sta ; 485 | char line[ASCIILINESZ+1]; 486 | int len ; 487 | 488 | strcpy(line, strstrip(input_line)); 489 | len = (int)strlen(line); 490 | 491 | if (len<1) { 492 | /* Empty line */ 493 | sta = LINE_EMPTY ; 494 | } else if (line[0]=='#') { 495 | /* Comment line */ 496 | sta = LINE_COMMENT ; 497 | } else if (line[0]=='[' && line[len-1]==']') { 498 | /* Section name */ 499 | sscanf(line, "[%[^]]", section); 500 | strcpy(section, strstrip(section)); 501 | strcpy(section, strlwc(section)); 502 | sta = LINE_SECTION ; 503 | } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 504 | || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 505 | || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { 506 | /* Usual key=value, with or without comments */ 507 | strcpy(key, strstrip(key)); 508 | strcpy(key, strlwc(key)); 509 | strcpy(value, strstrip(value)); 510 | /* 511 | * sscanf cannot handle '' or "" as empty values 512 | * this is done here 513 | */ 514 | if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { 515 | value[0]=0 ; 516 | } 517 | sta = LINE_VALUE ; 518 | } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 519 | || sscanf(line, "%[^=] %[=]", key, value) == 2) { 520 | /* 521 | * Special cases: 522 | * key= 523 | * key=; 524 | * key=# 525 | */ 526 | char *skey = strstrip(key); 527 | if (skey) 528 | strcpy(key, skey); 529 | strcpy(key, strlwc(key)); 530 | value[0]=0 ; 531 | sta = LINE_VALUE ; 532 | } else { 533 | /* Generate syntax error */ 534 | sta = LINE_ERROR ; 535 | } 536 | return sta ; 537 | } 538 | 539 | /*-------------------------------------------------------------------------*/ 540 | /** 541 | @brief Parse an ini file and return an allocated dictionary object 542 | @param ininame Name of the ini file to read. 543 | @return Pointer to newly allocated dictionary 544 | 545 | This is the parser for ini files. This function is called, providing 546 | the name of the file to be read. It returns a dictionary object that 547 | should not be accessed directly, but through accessor functions 548 | instead. 549 | 550 | The returned dictionary must be freed using iniparser_freedict(). 551 | */ 552 | /*--------------------------------------------------------------------------*/ 553 | dictionary * iniparser_load(const char * ininame) 554 | { 555 | FILE * in ; 556 | 557 | char line [ASCIILINESZ+1] ; 558 | char section [ASCIILINESZ+1] ; 559 | char key [ASCIILINESZ+1] ; 560 | char tmp [ASCIILINESZ+1] ; 561 | char val [ASCIILINESZ+1] ; 562 | 563 | int last=0 ; 564 | int len ; 565 | int lineno=0 ; 566 | int errs=0; 567 | 568 | dictionary * dict ; 569 | 570 | if ((in=fopen(ininame, "r"))==NULL) { 571 | fprintf(stderr, MSG_PREFIX "cannot open %s\n", ininame); 572 | return NULL ; 573 | } 574 | 575 | dict = dictionary_new(0) ; 576 | if (!dict) { 577 | fclose(in); 578 | return NULL ; 579 | } 580 | 581 | memset(line, 0, ASCIILINESZ); 582 | memset(section, 0, ASCIILINESZ); 583 | memset(key, 0, ASCIILINESZ); 584 | memset(val, 0, ASCIILINESZ); 585 | last=0 ; 586 | 587 | while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { 588 | lineno++ ; 589 | len = (int)strlen(line)-1; 590 | /* Safety check against buffer overflows */ 591 | if (line[len]!='\n') { 592 | fprintf(stderr, 593 | MSG_PREFIX "input line too long in %s (%d)\n", 594 | ininame, 595 | lineno); 596 | dictionary_del(dict); 597 | fclose(in); 598 | return NULL ; 599 | } 600 | /* Get rid of \n and spaces at end of line */ 601 | while ((len>=0) && 602 | ((line[len]=='\n') || (isspace(line[len])))) { 603 | line[len]=0 ; 604 | len-- ; 605 | } 606 | /* Detect multi-line */ 607 | if (line[len]=='\\') { 608 | /* Multi-line value */ 609 | last=len ; 610 | continue ; 611 | } 612 | switch (iniparser_line(line, section, key, val)) { 613 | case LINE_EMPTY: 614 | case LINE_COMMENT: 615 | break ; 616 | 617 | case LINE_SECTION: 618 | errs = dictionary_set(dict, section, NULL); 619 | break ; 620 | 621 | case LINE_VALUE: 622 | sprintf(tmp, "%s:%s", section, key); 623 | errs = dictionary_set(dict, tmp, val) ; 624 | break ; 625 | 626 | case LINE_ERROR: 627 | fprintf(stderr, MSG_PREFIX "syntax error in %s (%d):\n", 628 | ininame, 629 | lineno); 630 | fprintf(stderr, "-> %s\n", line); 631 | errs++ ; 632 | break; 633 | 634 | default: 635 | break ; 636 | } 637 | memset(line, 0, ASCIILINESZ); 638 | last=0; 639 | if (errs<0) { 640 | fprintf(stderr, MSG_PREFIX "memory allocation failure\n"); 641 | break ; 642 | } 643 | } 644 | if (errs) { 645 | dictionary_del(dict); 646 | dict = NULL ; 647 | } 648 | fclose(in); 649 | return dict ; 650 | } 651 | 652 | /*-------------------------------------------------------------------------*/ 653 | /** 654 | @brief Free all memory associated to an ini dictionary 655 | @param d Dictionary to free 656 | @return void 657 | 658 | Free all memory associated to an ini dictionary. 659 | It is mandatory to call this function before the dictionary object 660 | gets out of the current context. 661 | */ 662 | /*--------------------------------------------------------------------------*/ 663 | void iniparser_freedict(dictionary **d) 664 | { 665 | if (*d) { 666 | dictionary_del(*d); 667 | *d = NULL; 668 | } 669 | } 670 | 671 | /* vim: set ts=4 et sw=4 tw=75 */ 672 | #include 673 | 674 | #define __handle_format \ 675 | char key[128]; \ 676 | { \ 677 | va_list args; \ 678 | va_start(args, keyfmt); \ 679 | vsnprintf(key, sizeof(key)-1, keyfmt, args); \ 680 | va_end(args); \ 681 | } 682 | 683 | char *ini_get_string(dictionary *d, char *def, const char *keyfmt, ...) { 684 | __handle_format; 685 | return iniparser_getstring(d, key, def); 686 | } 687 | 688 | char *ini_get_string_copy(dictionary *d, char *def , const char *keyfmt, ...) { 689 | __handle_format; 690 | char *ret = iniparser_getstring(d, key, def); 691 | if (ret) 692 | ret = strdup(ret); 693 | return ret; 694 | } 695 | 696 | int ini_get_int(dictionary *d, int def, const char *keyfmt, ...) { 697 | __handle_format; 698 | return iniparser_getint(d, key, def); 699 | } 700 | 701 | double ini_get_double(dictionary *d, double def, const char *keyfmt, ...) { 702 | __handle_format; 703 | return iniparser_getdouble(d, key, def); 704 | } 705 | 706 | int ini_get_bool(dictionary *d, int def , const char *keyfmt, ...) { 707 | __handle_format; 708 | return iniparser_getboolean(d, key, def); 709 | } 710 | 711 | #undef __handle_format 712 | -------------------------------------------------------------------------------- /iniparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2000-2007 by Nicolas Devillard. 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /*-------------------------------------------------------------------------*/ 25 | /** 26 | @file iniparser.h 27 | @author N. Devillard 28 | @date Sep 2007 29 | @version 3.0 30 | @brief Parser for ini files. 31 | */ 32 | /*--------------------------------------------------------------------------*/ 33 | 34 | /* 35 | Id: iniparser.h,v 1.24 2007-11-23 21:38:19 ndevilla Exp 36 | Revision: 1.24 37 | */ 38 | 39 | #ifndef _INIPARSER_H_ 40 | #define _INIPARSER_H_ 41 | 42 | /*--------------------------------------------------------------------------- 43 | Includes 44 | ---------------------------------------------------------------------------*/ 45 | 46 | #include 47 | #include 48 | #include 49 | 50 | /* 51 | * The following #include is necessary on many Unixes but not Linux. 52 | * It is not needed for Windows platforms. 53 | * Uncomment it if needed. 54 | */ 55 | #include 56 | 57 | #include "inidict.h" 58 | 59 | /* Local extras */ 60 | char * ini_get_string (dictionary *d, char *def , const char *keyfmt, ...); 61 | char * ini_get_string_copy (dictionary *d, char *def , const char *keyfmt, ...); 62 | int ini_get_int (dictionary *d, int def , const char *keyfmt, ...); 63 | double ini_get_double (dictionary *d, double def, const char *keyfmt, ...); 64 | int ini_get_bool (dictionary *d, int def , const char *keyfmt, ...); 65 | 66 | /*--------------------------------------------------------------------------- 67 | Macros 68 | ---------------------------------------------------------------------------*/ 69 | /** For backwards compatibility only */ 70 | #define iniparser_getstr(d, k) iniparser_getstring(d, k, NULL) 71 | #define iniparser_setstr iniparser_setstring 72 | 73 | /*-------------------------------------------------------------------------*/ 74 | /** 75 | @brief Get number of sections in a dictionary 76 | @param d Dictionary to examine 77 | @return int Number of sections found in dictionary 78 | 79 | This function returns the number of sections found in a dictionary. 80 | The test to recognize sections is done on the string stored in the 81 | dictionary: a section name is given as "section" whereas a key is 82 | stored as "section:key", thus the test looks for entries that do not 83 | contain a colon. 84 | 85 | This clearly fails in the case a section name contains a colon, but 86 | this should simply be avoided. 87 | 88 | This function returns -1 in case of error. 89 | */ 90 | /*--------------------------------------------------------------------------*/ 91 | 92 | int iniparser_getnsec(dictionary * d); 93 | 94 | 95 | /*-------------------------------------------------------------------------*/ 96 | /** 97 | @brief Get name for section n in a dictionary. 98 | @param d Dictionary to examine 99 | @param n Section number (from 0 to nsec-1). 100 | @return Pointer to char string 101 | 102 | This function locates the n-th section in a dictionary and returns 103 | its name as a pointer to a string statically allocated inside the 104 | dictionary. Do not free or modify the returned string! 105 | 106 | This function returns NULL in case of error. 107 | */ 108 | /*--------------------------------------------------------------------------*/ 109 | 110 | char * iniparser_getsecname(dictionary * d, int n); 111 | 112 | 113 | /*-------------------------------------------------------------------------*/ 114 | /** 115 | @brief Save a dictionary to a loadable ini file 116 | @param d Dictionary to dump 117 | @param f Opened file pointer to dump to 118 | @return void 119 | 120 | This function dumps a given dictionary into a loadable ini file. 121 | It is Ok to specify @c stderr or @c stdout as output files. 122 | */ 123 | /*--------------------------------------------------------------------------*/ 124 | 125 | void iniparser_dump_ini(dictionary * d, FILE * f); 126 | 127 | /*-------------------------------------------------------------------------*/ 128 | /** 129 | @brief Dump a dictionary to an opened file pointer. 130 | @param d Dictionary to dump. 131 | @param f Opened file pointer to dump to. 132 | @return void 133 | 134 | This function prints out the contents of a dictionary, one element by 135 | line, onto the provided file pointer. It is OK to specify @c stderr 136 | or @c stdout as output files. This function is meant for debugging 137 | purposes mostly. 138 | */ 139 | /*--------------------------------------------------------------------------*/ 140 | void iniparser_dump(dictionary * d, FILE * f); 141 | 142 | /*-------------------------------------------------------------------------*/ 143 | /** 144 | @brief Get the string associated to a key 145 | @param d Dictionary to search 146 | @param key Key string to look for 147 | @param def Default value to return if key not found. 148 | @return pointer to statically allocated character string 149 | 150 | This function queries a dictionary for a key. A key as read from an 151 | ini file is given as "section:key". If the key cannot be found, 152 | the pointer passed as 'def' is returned. 153 | The returned char pointer is pointing to a string allocated in 154 | the dictionary, do not free or modify it. 155 | */ 156 | /*--------------------------------------------------------------------------*/ 157 | char * iniparser_getstring(dictionary * d, const char * key, char * def); 158 | 159 | /*-------------------------------------------------------------------------*/ 160 | /** 161 | @brief Get the string associated to a key, convert to an int 162 | @param d Dictionary to search 163 | @param key Key string to look for 164 | @param notfound Value to return in case of error 165 | @return integer 166 | 167 | This function queries a dictionary for a key. A key as read from an 168 | ini file is given as "section:key". If the key cannot be found, 169 | the notfound value is returned. 170 | 171 | Supported values for integers include the usual C notation 172 | so decimal, octal (starting with 0) and hexadecimal (starting with 0x) 173 | are supported. Examples: 174 | 175 | - "42" -> 42 176 | - "042" -> 34 (octal -> decimal) 177 | - "0x42" -> 66 (hexa -> decimal) 178 | 179 | Warning: the conversion may overflow in various ways. Conversion is 180 | totally outsourced to strtol(), see the associated man page for overflow 181 | handling. 182 | 183 | Credits: Thanks to A. Becker for suggesting strtol() 184 | */ 185 | /*--------------------------------------------------------------------------*/ 186 | int iniparser_getint(dictionary * d, const char * key, int notfound); 187 | 188 | /*-------------------------------------------------------------------------*/ 189 | /** 190 | @brief Get the string associated to a key, convert to a double 191 | @param d Dictionary to search 192 | @param key Key string to look for 193 | @param notfound Value to return in case of error 194 | @return double 195 | 196 | This function queries a dictionary for a key. A key as read from an 197 | ini file is given as "section:key". If the key cannot be found, 198 | the notfound value is returned. 199 | */ 200 | /*--------------------------------------------------------------------------*/ 201 | double iniparser_getdouble(dictionary * d, char * key, double notfound); 202 | 203 | /*-------------------------------------------------------------------------*/ 204 | /** 205 | @brief Get the string associated to a key, convert to a boolean 206 | @param d Dictionary to search 207 | @param key Key string to look for 208 | @param notfound Value to return in case of error 209 | @return integer 210 | 211 | This function queries a dictionary for a key. A key as read from an 212 | ini file is given as "section:key". If the key cannot be found, 213 | the notfound value is returned. 214 | 215 | A true boolean is found if one of the following is matched: 216 | 217 | - A string starting with 'y' 218 | - A string starting with 'Y' 219 | - A string starting with 't' 220 | - A string starting with 'T' 221 | - A string starting with '1' 222 | 223 | A false boolean is found if one of the following is matched: 224 | 225 | - A string starting with 'n' 226 | - A string starting with 'N' 227 | - A string starting with 'f' 228 | - A string starting with 'F' 229 | - A string starting with '0' 230 | 231 | The notfound value returned if no boolean is identified, does not 232 | necessarily have to be 0 or 1. 233 | */ 234 | /*--------------------------------------------------------------------------*/ 235 | int iniparser_getboolean(dictionary * d, const char * key, int notfound); 236 | 237 | 238 | /*-------------------------------------------------------------------------*/ 239 | /** 240 | @brief Set an entry in a dictionary. 241 | @param ini Dictionary to modify. 242 | @param entry Entry to modify (entry name) 243 | @param val New value to associate to the entry. 244 | @return int 0 if Ok, -1 otherwise. 245 | 246 | If the given entry can be found in the dictionary, it is modified to 247 | contain the provided value. If it cannot be found, -1 is returned. 248 | It is Ok to set val to NULL. 249 | */ 250 | /*--------------------------------------------------------------------------*/ 251 | int iniparser_setstring(dictionary * ini, char * entry, char * val); 252 | 253 | 254 | /*-------------------------------------------------------------------------*/ 255 | /** 256 | @brief Delete an entry in a dictionary 257 | @param ini Dictionary to modify 258 | @param entry Entry to delete (entry name) 259 | @return void 260 | 261 | If the given entry can be found, it is deleted from the dictionary. 262 | */ 263 | /*--------------------------------------------------------------------------*/ 264 | void iniparser_unset(dictionary * ini, char * entry); 265 | 266 | /*-------------------------------------------------------------------------*/ 267 | /** 268 | @brief Finds out if a given entry exists in a dictionary 269 | @param ini Dictionary to search 270 | @param entry Name of the entry to look for 271 | @return integer 1 if entry exists, 0 otherwise 272 | 273 | Finds out if a given entry exists in the dictionary. Since sections 274 | are stored as keys with NULL associated values, this is the only way 275 | of querying for the presence of sections in a dictionary. 276 | */ 277 | /*--------------------------------------------------------------------------*/ 278 | int iniparser_find_entry(dictionary * ini, char * entry) ; 279 | 280 | /*-------------------------------------------------------------------------*/ 281 | /** 282 | @brief Parse an ini file and return an allocated dictionary object 283 | @param ininame Name of the ini file to read. 284 | @return Pointer to newly allocated dictionary 285 | 286 | This is the parser for ini files. This function is called, providing 287 | the name of the file to be read. It returns a dictionary object that 288 | should not be accessed directly, but through accessor functions 289 | instead. 290 | 291 | The returned dictionary must be freed using iniparser_freedict(). 292 | */ 293 | /*--------------------------------------------------------------------------*/ 294 | dictionary * iniparser_load(const char * ininame); 295 | 296 | /*-------------------------------------------------------------------------*/ 297 | /** 298 | @brief Free all memory associated to an ini dictionary 299 | @param d Dictionary to free 300 | @return void 301 | 302 | Free all memory associated to an ini dictionary. 303 | It is mandatory to call this function before the dictionary object 304 | gets out of the current context. 305 | */ 306 | /*--------------------------------------------------------------------------*/ 307 | void iniparser_freedict(dictionary **d); 308 | 309 | #endif 310 | -------------------------------------------------------------------------------- /input.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd input handling 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libfuncs/io.h" 26 | #include "libfuncs/log.h" 27 | 28 | #include "libtsfuncs/tsfuncs.h" 29 | 30 | #include "data.h" 31 | #include "config.h" 32 | #include "network.h" 33 | 34 | extern int keep_going; 35 | extern CONFIG *config; 36 | 37 | // #define dump_tables 1 38 | 39 | #define MAX_ZERO_READS 3 40 | 41 | /* Start: 3 seconds on connect */ 42 | /* In connection: Max UDP timeout == 3 seconds (read) + 2 seconds (connect) == 5 seconds */ 43 | #define UDP_READ_RETRIES 3 44 | #define UDP_READ_TIMEOUT 1000 45 | 46 | /* Start: 1/4 seconds on connect */ 47 | /* In connection: Max TCP timeout == 5 seconds (read) + 2 seconds (connect) == 7 seconds */ 48 | /* In connection: Max TCP timeout == 5 seconds (read) + 8 seconds (connect, host unrch) == 13 seconds */ 49 | #define TCP_READ_RETRIES 5 50 | #define TCP_READ_TIMEOUT 1000 51 | 52 | // Init pmt_pid and nit_pid 53 | // Return 0 on error, 1 on success 54 | int input_process_pat(INPUT *r) { 55 | int i; 56 | int num_programs = 0; 57 | INPUT_STREAM *s = &r->stream; 58 | struct ts_pat *pat = s->pat; 59 | 60 | s->nit_pid = 0x10; // Default NIT pid 61 | for (i=0;iprograms_num;i++) { 62 | struct ts_pat_program *prg = pat->programs[i]; 63 | if (prg->pid) { 64 | if (prg->program == 0) { // NIT 65 | s->nit_pid = prg->pid; 66 | } else { // PAT 67 | s->pmt_pid = prg->pid; 68 | num_programs++; 69 | break; // Get only the first program 70 | } 71 | } 72 | } 73 | 74 | // MPTS is not supported as input stream in the moment 75 | if (num_programs > 1) { 76 | LOGf("INPUT : %-10s | Can't handle MPTS (%d programs) as input stream\n", r->channel->id, num_programs); 77 | return 0; 78 | } 79 | 80 | return 1; 81 | } 82 | 83 | void input_rewrite_pat(INPUT *r) { 84 | int i; 85 | INPUT_STREAM *s = &r->stream; 86 | struct ts_pat *new_pat = ts_pat_copy(s->pat); 87 | if (!new_pat) 88 | return; 89 | 90 | // Rewrite PAT pids 91 | for (i=0;iprograms_num;i++) { 92 | struct ts_pat_program *prg = new_pat->programs[i]; 93 | if (prg->program != 0) { // Skip NIT 94 | // Add pid to rewriter 95 | pidref_add(s->pidref, prg->pid, s->pidref->base_pid); 96 | // Rewrite PAT 97 | prg->program = r->channel->service_id; 98 | prg->pid = s->pidref->base_pid; 99 | s->pidref->base_pid++; 100 | } 101 | } 102 | 103 | // Save rewritten packet 104 | ts_pat_regenerate_packets(new_pat); 105 | s->pat_rewritten = new_pat; 106 | } 107 | 108 | void input_rewrite_pmt(INPUT *r) { 109 | INPUT_STREAM *s = &r->stream; 110 | struct ts_pmt *new_pmt = ts_pmt_copy(s->pmt); 111 | if (!new_pmt) 112 | return; 113 | 114 | // Rewrite PMT pids 115 | new_pmt->ts_header.pid = pidref_get_new_pid(s->pidref, s->pmt_pid); 116 | new_pmt->section_header->ts_id_number = r->channel->service_id; 117 | 118 | uint16_t org_pcr_pid = new_pmt->PCR_pid; 119 | s->pcr_pid = new_pmt->PCR_pid; 120 | pidref_add(s->pidref, org_pcr_pid, s->pidref->base_pid); 121 | new_pmt->PCR_pid = s->pidref->base_pid; 122 | r->output_pcr_pid = new_pmt->PCR_pid; 123 | s->pidref->base_pid++; 124 | 125 | int i; 126 | for (i=0;istreams_num;i++) { 127 | struct ts_pmt_stream *stream = new_pmt->streams[i]; 128 | if (stream->pid == org_pcr_pid) { // Already rewritten and added to pidref 129 | stream->pid = new_pmt->PCR_pid; 130 | continue; 131 | } 132 | pidref_add(s->pidref, stream->pid, s->pidref->base_pid); 133 | stream->pid = s->pidref->base_pid; 134 | s->pidref->base_pid++; 135 | } 136 | 137 | ts_pmt_regenerate_packets(new_pmt); 138 | s->pmt_rewritten = new_pmt; 139 | } 140 | 141 | void input_rewrite_eit(INPUT *r) 142 | { 143 | INPUT_STREAM *s = &r->stream; 144 | struct ts_eit *new_eit = ts_eit_copy(s->eit); 145 | if (!new_eit) 146 | return; 147 | 148 | // Rewrite EIT data 149 | new_eit->transport_stream_id = config->transport_stream_id; 150 | 151 | ts_eit_regenerate_packets(new_eit); 152 | s->eit_rewritten = new_eit; 153 | } 154 | 155 | void input_buffer_add(INPUT *r, uint8_t *data, int datasize) { 156 | if (r->dienow) 157 | return; 158 | if (r->ifd) 159 | if(write(r->ifd, data, datasize) < 0) {;} 160 | if (r->disabled) { 161 | unsigned long bufsize = r->buf->input - r->buf->output; 162 | double buffull = ((double)bufsize / r->buf->size) * 100; 163 | if (buffull <= 50) { 164 | proxy_log(r, "Enable input"); 165 | r->disabled = 0; 166 | } else { 167 | return; 168 | } 169 | } 170 | if (cbuf_fill(r->buf, data, datasize) != 0) { 171 | proxy_log(r, "Disable input, buffer is full."); 172 | r->disabled = 1; 173 | } 174 | } 175 | 176 | int input_check_state(INPUT *r) { 177 | if (r->dienow) { 178 | // proxy_log(r, "Forced disconnect."); 179 | return 2; 180 | } 181 | if (r->reconnect) { 182 | proxy_log(r, "Forced reconnect."); 183 | return 1; 184 | } 185 | return 0; 186 | } 187 | 188 | int process_pat(INPUT *r, uint16_t pid, uint8_t *ts_packet) { 189 | INPUT_STREAM *s = &r->stream; 190 | 191 | if (pid != 0) 192 | return 0; 193 | 194 | // Process PAT 195 | s->pat = ts_pat_push_packet(s->pat, ts_packet); 196 | 197 | 198 | if (s->last_pat->initialized) { 199 | if (!s->pat->initialized) return -1; // Incomplete 200 | if (!ts_pat_is_same(s->pat, s->last_pat)) { 201 | proxy_log(r, "========================PAT changed.========================"); 202 | return -2; // Reconnect 203 | } 204 | ts_pat_free(&s->last_pat); 205 | s->last_pat = ts_pat_alloc(); 206 | } 207 | s->last_pat = ts_pat_push_packet(s->last_pat, ts_packet); 208 | if (s->pat->initialized) { 209 | // PMT pid is still unknown 210 | if (!s->pmt_pid) { 211 | if (!input_process_pat(r)) { 212 | proxy_log(r, "Can't parse PAT to find PMT pid."); 213 | return -2; 214 | } 215 | } 216 | // Rewritten PAT is not yet initialized 217 | if (!s->pat_rewritten || !s->pat_rewritten->initialized) { 218 | input_rewrite_pat(r); 219 | #if dump_tables 220 | proxy_log(r, "PAT found!"); 221 | proxy_log(r, "*** Original PAT ***"); 222 | ts_pat_dump(s->pat); 223 | proxy_log(r, "*** Rewritten PAT ***"); 224 | ts_pat_dump(s->pat_rewritten); 225 | pidref_dump(s->pidref); 226 | #endif 227 | } 228 | 229 | // Only if output file is written 230 | if (r->ifd && s->pat_rewritten && s->pat_rewritten->initialized) { 231 | int j; 232 | struct ts_pat *P = s->pat_rewritten; 233 | for (j=0;jsection_header->num_packets;j++) { 234 | ts_packet_set_cont(P->section_header->packet_data + (j * TS_PACKET_SIZE), j + s->pid_pat_cont); 235 | } 236 | P->ts_header.continuity = s->pid_pat_cont; 237 | s->pid_pat_cont += P->section_header->num_packets; 238 | if (write(r->ifd, P->section_header->packet_data, P->section_header->num_packets * TS_PACKET_SIZE) <0) {;} 239 | } 240 | } 241 | // Stuff packet with NULL data 242 | memset(ts_packet, 0xff, TS_PACKET_SIZE); 243 | ts_packet[0] = 0x47; 244 | ts_packet[1] = 0x1F; 245 | ts_packet[2] = 0xFF; 246 | ts_packet[3] = 0x10; 247 | 248 | return 1; 249 | } 250 | 251 | int process_pmt(INPUT *r, uint16_t pid, uint8_t *ts_packet) { 252 | INPUT_STREAM *s = &r->stream; 253 | 254 | if (!pid || pid != s->pmt_pid) 255 | return 0; 256 | 257 | s->pmt = ts_pmt_push_packet(s->pmt, ts_packet); 258 | 259 | 260 | if (s->last_pmt->initialized) { 261 | if (!s->pmt->initialized) return -1; // Incomplete 262 | if (!ts_pmt_is_same(s->pmt, s->last_pmt)) { 263 | proxy_log(r, "========================PMT changed.========================"); 264 | return -2; // Reconnect 265 | } 266 | ts_pmt_free(&s->last_pmt); 267 | s->last_pmt = ts_pmt_alloc(); 268 | } 269 | 270 | s->last_pmt = ts_pmt_push_packet(s->last_pmt, ts_packet); 271 | 272 | if (s->pmt->initialized) { 273 | if (!s->pmt_rewritten || !s->pmt_rewritten->initialized) { 274 | input_rewrite_pmt(r); 275 | #if dump_tables 276 | proxy_log(r, "PMT found!"); 277 | proxy_log(r, "*** Original PMT ***"); 278 | ts_pmt_dump(s->pmt); 279 | proxy_log(r, "*** Rewritten PMT ***"); 280 | ts_pmt_dump(s->pmt_rewritten); 281 | // pidref_dump(s->pidref); 282 | #endif 283 | } 284 | if (s->pmt_rewritten && s->pmt_rewritten->initialized) { 285 | int j; 286 | struct ts_pmt *P = s->pmt_rewritten; 287 | for (j=0;jsection_header->num_packets;j++) { 288 | ts_packet_set_cont(P->section_header->packet_data + (j * TS_PACKET_SIZE), j + s->pid_pmt_cont); 289 | } 290 | P->ts_header.continuity = s->pid_pmt_cont; 291 | s->pid_pmt_cont += P->section_header->num_packets; 292 | input_buffer_add(r, P->section_header->packet_data, P->section_header->num_packets * TS_PACKET_SIZE); 293 | } 294 | return -1; 295 | } 296 | return 1; 297 | } 298 | 299 | int process_eit(INPUT *r, uint8_t *ts_packet) { 300 | INPUT_STREAM *s = &r->stream; 301 | 302 | if (r->channel->eit_mode == 0) { 303 | return -1; 304 | } 305 | 306 | s->eit = ts_eit_push_packet(s->eit, ts_packet); 307 | 308 | if (s->eit->initialized) 309 | { 310 | uint16_t service_id = s->eit->section_header->ts_id_number; 311 | uint16_t table_id = s->eit->section_header->table_id; 312 | //ts_LOGf("EIT for service_id service_id %d with table_id %d\n", service_id, table_id); 313 | if (service_id != r->channel->service_id) 314 | { 315 | //ts_LOGf("skipping EIT for ts_id_number %d/%d\n", service_id, table_id); 316 | return -2; 317 | } 318 | if (!s->eit_rewritten || (s->eit_rewritten && !s->eit_rewritten->initialized)) 319 | { 320 | input_rewrite_eit(r); 321 | #if dump_tables 322 | proxy_log(r, "EIT found!"); 323 | proxy_log(r, "*** Original EIT ***"); 324 | ts_eit_dump(s->eit); 325 | proxy_log(r, "*** Rewritten EIT ***"); 326 | ts_eit_dump(s->eit_rewritten); 327 | #endif 328 | } 329 | if (s->eit_rewritten && s->eit_rewritten->initialized) 330 | { 331 | //ts_LOGf("sending EIT for service_id service_id %d with table_id %d\n", service_id, table_id); 332 | int j; 333 | struct ts_eit *P = s->eit_rewritten; 334 | for (j = 0; j < P->section_header->num_packets; j++) 335 | { 336 | ts_packet_set_cont(P->section_header->packet_data + (j * TS_PACKET_SIZE), j + s->pid_eit_cont); 337 | } 338 | P->ts_header.continuity = s->pid_eit_cont; 339 | s->pid_eit_cont += P->section_header->num_packets; 340 | input_buffer_add(r, P->section_header->packet_data, P->section_header->num_packets * TS_PACKET_SIZE); 341 | 342 | // reset structs for next EIT packets 343 | ts_eit_free(&s->eit); 344 | s->eit = ts_eit_alloc(); 345 | ts_eit_free(&s->eit_rewritten); 346 | s->eit_rewritten = NULL; 347 | return 1; 348 | } 349 | return -3; 350 | table_id++; // disable warning unused, this code is not reached 351 | } 352 | return 0; 353 | } 354 | 355 | int in_worktime(int start, int end) { 356 | if (!start && !end) 357 | return 1; 358 | struct tm ltime; 359 | time_t timep = time(NULL); 360 | localtime_r(&timep, <ime); 361 | int seconds = ltime.tm_sec + ltime.tm_min * 60 + ltime.tm_hour * 3600; 362 | if (start > end) { 363 | if (start >= seconds && end < seconds) 364 | return 0; 365 | else 366 | return 1; 367 | } else { 368 | if (start <= seconds && end > seconds) 369 | return 1; 370 | else 371 | return 0; 372 | } 373 | return 1; 374 | } 375 | 376 | void * input_stream(void *self) { 377 | INPUT *r = self; 378 | INPUT_STREAM *s = &r->stream; 379 | char buffer[RTP_HEADER_SIZE + FRAME_PACKET_SIZE]; 380 | char *buf = buffer + RTP_HEADER_SIZE; 381 | 382 | signal(SIGPIPE, SIG_IGN); 383 | 384 | proxy_log(r, "Start"); 385 | r->working = in_worktime(r->channel->worktime_start, r->channel->worktime_end); 386 | if (!r->working) 387 | proxy_log(r, "Worktime has not yet begin, sleeping."); 388 | 389 | int http_code = 0; 390 | while (keep_going) { 391 | if (input_check_state(r) == 2) // r->dienow is on 392 | goto QUIT; 393 | 394 | while (!r->working) { 395 | usleep(250000); 396 | r->working = in_worktime(r->channel->worktime_start, r->channel->worktime_end); 397 | if (r->working) 398 | proxy_log(r, "Worktime started."); 399 | if (!keep_going) 400 | goto QUIT; 401 | } 402 | 403 | r->working = in_worktime(r->channel->worktime_start, r->channel->worktime_end); 404 | 405 | int result = connect_source(self, 1, FRAME_PACKET_SIZE * 1000, &http_code); 406 | if (result != 0) 407 | goto RECONNECT; 408 | 409 | channel_source sproto = get_sproto(r->channel->source); 410 | int rtp = is_rtp(r->channel->source); 411 | 412 | if (!rtp && mpeg_sync(r, sproto) != 0) { 413 | proxy_log(r, "Can't sync input MPEG TS"); 414 | sleep(2); 415 | goto RECONNECT; 416 | } 417 | 418 | ssize_t readen; 419 | int max_zero_reads = MAX_ZERO_READS; 420 | 421 | // Reset all stream parameters on reconnect. 422 | input_stream_reset(r); 423 | 424 | for (;;) { 425 | r->working = in_worktime(r->channel->worktime_start, r->channel->worktime_end); 426 | if (!r->working) { 427 | proxy_log(r, "Worktime ended."); 428 | goto STOP; 429 | } 430 | 431 | switch (input_check_state(r)) { 432 | case 1: goto RECONNECT; // r->reconnect is on 433 | case 2: goto QUIT; // r->dienow is on 434 | } 435 | 436 | if (sproto == tcp_sock) { 437 | readen = fdread_ex(r->sock, buf, FRAME_PACKET_SIZE, TCP_READ_TIMEOUT, TCP_READ_RETRIES, 1); 438 | } else { 439 | if (!rtp) { 440 | readen = fdread_ex(r->sock, buf, FRAME_PACKET_SIZE, UDP_READ_TIMEOUT, UDP_READ_RETRIES, 0); 441 | } else { 442 | readen = fdread_ex(r->sock, buffer, FRAME_PACKET_SIZE + RTP_HEADER_SIZE, UDP_READ_TIMEOUT, UDP_READ_RETRIES, 0); 443 | if (readen > RTP_HEADER_SIZE) 444 | readen -= RTP_HEADER_SIZE; 445 | } 446 | } 447 | 448 | if (readen < 0) 449 | goto RECONNECT; 450 | if (readen == 0) { // ho, hum, wtf is going on here? 451 | proxy_log(r, "Zero read, continuing..."); 452 | if (--max_zero_reads == 0) { 453 | proxy_log(r, "Max zero reads reached, reconnecting."); 454 | break; 455 | } 456 | continue; 457 | } 458 | 459 | int i; 460 | for (i=0; idienow) 463 | goto QUIT; 464 | uint8_t *ts_packet = (uint8_t *)buf + i; 465 | uint16_t pid = ts_packet_get_pid(ts_packet); 466 | 467 | int pat_result = process_pat(r, pid, ts_packet); 468 | if (pat_result == -2) 469 | goto RECONNECT; 470 | if (pat_result < 0) // PAT incomplete 471 | continue; 472 | 473 | int pmt_result = process_pmt(r, pid, ts_packet); 474 | if (pmt_result == -2) 475 | goto RECONNECT; 476 | if (pmt_result < 0) // PMT rewritten or incomplete 477 | continue; 478 | 479 | pid = ts_packet_get_pid(ts_packet); 480 | 481 | if (pid == 0x12) // EIT 482 | { 483 | process_eit(r, ts_packet); 484 | continue; 485 | } 486 | 487 | // Kill incoming NIT, SDT, RST, TDT/TOT 488 | if (pid == s->nit_pid || pid == 0x10 || pid == 0x11 || pid == 0x13 || pid == 0x14 || pid >= 0x1fff) { 489 | // LOGf("INPUT: %-10s: Remove PID %03x\n", r->channel->id, pid); 490 | continue; 491 | } 492 | 493 | // Do we have PAT and PMT? (if we have pmt we have PAT, so check only for PMT) 494 | if (s->pmt_rewritten && pid == s->pcr_pid && ts_packet_has_pcr(ts_packet)) { 495 | s->input_pcr = ts_packet_get_pcr(ts_packet); 496 | // LOGf("INPUT : [%-12s] PCR: %llu\n", r->channel->id, s->input_pcr); 497 | } 498 | 499 | // Yes, we have enough data to start outputing 500 | if (s->input_pcr) { 501 | pidref_change_packet_pid(ts_packet, pid, s->pidref); 502 | input_buffer_add(r, ts_packet, TS_PACKET_SIZE); 503 | if (!r->input_ready) 504 | r->input_ready = 1; 505 | } 506 | } 507 | 508 | max_zero_reads = MAX_ZERO_READS; 509 | } 510 | proxy_log(r, "fdread timeout"); 511 | RECONNECT: 512 | proxy_log(r, "Reconnect"); 513 | shutdown_fd(&(r->sock)); 514 | chansrc_next(r->channel); 515 | continue; 516 | STOP: 517 | proxy_log(r, "Stop"); 518 | shutdown_fd(&(r->sock)); 519 | continue; 520 | QUIT: 521 | break; 522 | } 523 | proxy_close(config->inputs, &r); 524 | 525 | return 0; 526 | } 527 | -------------------------------------------------------------------------------- /input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd input handling header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef INPUT_H 19 | #define INPUT_H 20 | 21 | void * input_stream(void *); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /mptsd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd main 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "libfuncs/libfuncs.h" 24 | 25 | #include "libtsfuncs/tsfuncs.h" 26 | 27 | #include "iniparser.h" 28 | #include "data.h" 29 | #include "config.h" 30 | #include "network.h" 31 | #include "input.h" 32 | #include "output.h" 33 | #include "web_server.h" 34 | 35 | #define PROGRAM_NAME "ux-mptsd" 36 | 37 | #ifdef BUILD_ID 38 | const char *program_id = PROGRAM_NAME " " GIT_VER " build " BUILD_ID; 39 | #else 40 | const char *program_id = PROGRAM_NAME " " GIT_VER; 41 | #endif 42 | 43 | char *server_sig = PROGRAM_NAME; 44 | char *server_ver = GIT_VER; 45 | char *copyright = "Copyright (C) 2010-2011 Unix Solutions Ltd."; 46 | 47 | CONFIG *config; 48 | int keep_going = 1; 49 | int rcvsig = 0; 50 | 51 | void spawn_input_threads(CONFIG *conf) { 52 | LNODE *lc, *lctmp; 53 | LNODE *lr, *lrtmp; 54 | int spawned = 0; 55 | list_for_each(conf->channels, lc, lctmp) { 56 | CHANNEL *c = lc->data; 57 | int restreamer_active = 0; 58 | list_lock(conf->inputs); 59 | list_for_each(conf->inputs, lr, lrtmp) { 60 | INPUT *r = lr->data; 61 | if (xstrcmp(r->name, c->name)==0) { 62 | restreamer_active = 1; 63 | break; 64 | } 65 | } 66 | list_unlock(conf->inputs); 67 | if (!restreamer_active) { 68 | INPUT *nr = input_new(c->name, c); 69 | if (nr) { 70 | list_add(conf->inputs, nr); 71 | // LOGf("SPAWN : %s thread.\n", c->name); 72 | if (pthread_create(&nr->thread, NULL, &input_stream, nr) == 0) { 73 | spawned++; 74 | pthread_detach(nr->thread); 75 | } else { 76 | LOGf("ERROR: Can't create proxy for %s\n", c->name); 77 | } 78 | } else { 79 | LOGf("ERROR: Error creating proxy for %s\n", c->name); 80 | } 81 | } 82 | } 83 | LOGf("INPUT : %d thread%s spawned.\n", spawned, spawned > 1 ? "s" : ""); 84 | } 85 | 86 | void spawn_output_threads(CONFIG *conf) { 87 | if (pthread_create(&conf->output->psi_thread, NULL, &output_handle_psi, conf) == 0) { 88 | pthread_detach(conf->output->psi_thread); 89 | } else { 90 | LOGf("ERROR: Can't spawn PSI output thread: %s\n", strerror(errno)); 91 | exit(1); 92 | } 93 | 94 | if (pthread_create(&conf->output->mix_thread, NULL, &output_handle_mix, conf) == 0) { 95 | pthread_detach(conf->output->mix_thread); 96 | } else { 97 | LOGf("ERROR: Can't spawn MIX output thread: %s\n", strerror(errno)); 98 | exit(1); 99 | } 100 | 101 | if (pthread_create(&conf->output->write_thread, NULL, &output_handle_write, conf) == 0) { 102 | pthread_detach(conf->output->write_thread); 103 | } else { 104 | LOGf("ERROR: Can't spawn WRITE output thread: %s\n", strerror(errno)); 105 | exit(1); 106 | } 107 | } 108 | 109 | void kill_threads(CONFIG *conf) { 110 | int loops = 0; 111 | conf->output->dienow = 1; 112 | while (conf->inputs->items || conf->output->dienow < 4) { 113 | usleep(50000); 114 | if (loops++ > 60) // 3 seconds 115 | exit(0); 116 | } 117 | } 118 | 119 | /* 120 | void do_reconnect(CONFIG *conf) { 121 | LNODE *l, *tmp; 122 | list_lock(conf->inputs); 123 | list_for_each(conf->inputs, l, tmp) { 124 | INPUT *r = l->data; 125 | r->reconnect = 1; 126 | } 127 | list_unlock(conf->inputs); 128 | } 129 | 130 | void do_reconf(CONFIG *conf) { 131 | // load_channels_config(); 132 | spawn_input_threads(conf); 133 | } 134 | */ 135 | 136 | void signal_quit(int sig) { 137 | rcvsig = sig; 138 | keep_going = 0; 139 | } 140 | 141 | void init_signals(void) { 142 | signal(SIGCHLD, SIG_IGN); 143 | signal(SIGPIPE, SIG_IGN); 144 | 145 | // signal(SIGHUP , do_reconf); 146 | // signal(SIGUSR1, do_reconnect); 147 | 148 | signal(SIGINT , signal_quit); 149 | signal(SIGTERM, signal_quit); 150 | } 151 | 152 | int main(int argc, char **argv) { 153 | set_http_response_server_ident(server_sig, server_ver); 154 | ts_set_log_func(LOG); 155 | 156 | config = config_alloc(); 157 | config_load(config, argc, argv); 158 | 159 | output_psi_init(config, config->output); 160 | 161 | daemonize(config->pidfile); 162 | web_server_start(config); 163 | log_init(config->logident, config->syslog_active, config->pidfile == NULL, config->loghost, config->logport); 164 | init_signals(); 165 | 166 | LOGf("INIT : %s %s (%s)\n" , server_sig, server_ver, config->ident); 167 | 168 | connect_output(config->output); 169 | spawn_input_threads(config); 170 | spawn_output_threads(config); 171 | 172 | do { usleep(50000); } while(keep_going); 173 | 174 | kill_threads(config); 175 | web_server_stop(config); 176 | 177 | LOGf("SHUTDOWN: Signal %d | %s %s (%s)\n", rcvsig, server_sig, server_ver, config->ident); 178 | config_free(&config); 179 | 180 | log_close(); 181 | 182 | exit(0); 183 | } 184 | -------------------------------------------------------------------------------- /mptsd.conf: -------------------------------------------------------------------------------- 1 | [Global] 2 | network_id=1 3 | 4 | [Timeouts] 5 | pat = 100 // Min:25 Max:500 6 | #cat = 200 // Min:25 Max:500 ** unused ** 7 | pmt = 200 // Min:25 Max:500 8 | nit = 2000 // Min:25 Max:10000 9 | sdt = 500 // Min:25 Max:2000 10 | #bat = 5000 // Min:25 Max:10000 ** unused ** 11 | eit = 1000 // Min:25 Max:2000 12 | #rst = 20000 // Min:25 Max: - ** unused ** 13 | tdt = 5000 // Min:25 Max:30000 14 | tot = 15000 // Min:25 Max:30000 15 | stats = 1000 // No limits 16 | -------------------------------------------------------------------------------- /mptsd_channels.conf: -------------------------------------------------------------------------------- 1 | [Global] 2 | provider_name = Unix Solutions 3 | transport_stream_id = 1 4 | 5 | [Channel1] 6 | service_id = 1 7 | id = btv 8 | name = bTV 9 | eit_mode = 0 # 0 = ignore EIT data from input 10 | # 1 = forward EIT data for the configured service, ignore any other EIT data 11 | source1 = http://signal-server/stb/btv.mpg 12 | #source2 = http://signal-server2/stb/btv.mpg 13 | #source3 = http://signal-server3/stb/btv.mpg 14 | #source4 = udp://239.0.0.1:5000/ 15 | #source5 = rtp://239.78.78.2:5000/ 16 | #worktime = 14:18-14:19 17 | lcn = 2 18 | lcn_visible = yes 19 | 20 | [Channel2] 21 | service_id = 2 22 | id = kanal1 23 | name = "Kanal 1" 24 | source = http://signal-server/stb/kanal1.mpg 25 | lcn = 2 26 | lcn_visible = yes 27 | 28 | [Channel3] 29 | service_id = 3 30 | id = novatv 31 | name = "Nova" 32 | source1 = http://signal-server/stb/novatv.mpg 33 | lcn = 3 34 | lcn_visible = yes 35 | 36 | [Channel4] 37 | service_id = 4 38 | id = tv2 39 | name = "TV 2" 40 | source1 = http://signal-server/stb/tv2.mpg 41 | lcn = 7 42 | lcn_visible = yes 43 | 44 | [Channel5] 45 | service_id = 5 46 | id = ngc 47 | name = "NatGeo" 48 | source1 = http://signal-server/stb/ngc.mpg 49 | lcn = 72 50 | lcn_visible = yes 51 | 52 | [Channel6] 53 | service_id = 6 54 | id = tv1 55 | name = "TV 1" 56 | source1 = http://signal-server/stb/tv1.mpg 57 | lcn = 25 58 | lcn_visible = yes 59 | 60 | [Channel7] 61 | service_id = 7 62 | id = planetahit 63 | name = "Planeta HIT" 64 | source1 = http://signal-server/stb/planetahit.mpg 65 | lcn = 33 66 | lcn_visible = no 67 | 68 | [Channel8] 69 | service_id = 8 70 | id = fresh 71 | name = "Radio Fresh" 72 | radio = yes 73 | source = http://signal-server/stb/fresh.mpg 74 | lcn = 201 75 | lcn_visible = yes 76 | 77 | [Channel9] 78 | service_id = 9 79 | id = bgradio 80 | name = "BG Radio" 81 | radio = yes 82 | source1 = http://signal-server/stb/bgradio.mpg 83 | lcn = 202 84 | lcn_visible = yes 85 | -------------------------------------------------------------------------------- /mptsd_epg.conf: -------------------------------------------------------------------------------- 1 | [btv-now] 2 | start=1271055600 3 | duration=3600 4 | event=Денят е прекрасен 5 | sdescr=женско токшоу с водещи Ива и Боги 6 | descr=123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890xxxxxxyy 7 | 8 | [btv-next] 9 | start=1271059200 10 | duration=3600 11 | event=Вятър от долината 12 | sdescr=сериал, еп. 69 13 | descr= 14 | 15 | [kanal1-now] 16 | start=1271056500 17 | duration=2700 18 | event=Дързост и красота 19 | sdescr=тв филм /2477 епизод/ 20 | descr= 21 | 22 | [kanal1-next] 23 | start=1271059200 24 | duration=2700 25 | event=Жените с Марта Вачкова /токшоу/ 26 | sdescr= 27 | descr= 28 | 29 | [novatv-now] 30 | start=1271052900 31 | duration=8100 32 | event=На кафе 33 | sdescr=предаване на НТВ 34 | descr= 35 | 36 | [novatv-next] 37 | start=1271061000 38 | duration=5400 39 | event=Къщата на парите 40 | sdescr=забавно предаване на НТВ 41 | descr= 42 | 43 | [tv2-now] 44 | start=1271055600 45 | duration=3600 46 | event=Антракт 47 | sdescr= 48 | descr= 49 | 50 | [tv2-next] 51 | start=1271059200 52 | duration=7200 53 | event=Парола. Риба меч 54 | sdescr=криминален с Джон Траволта, Холи Бери 55 | descr= 56 | 57 | [ngc-now] 58 | start=1271055600 59 | duration=3600 60 | event=Невероятната мечка 61 | sdescr= 62 | descr= 63 | 64 | [ngc-next] 65 | start=1271059200 66 | duration=3600 67 | event=Гигантската риба на Амазонка 68 | sdescr= 69 | descr= 70 | 71 | -------------------------------------------------------------------------------- /mptsd_nit.conf: -------------------------------------------------------------------------------- 1 | [Global] 2 | network_name = "Unixsol DVB-C network" 3 | 4 | [Transponder1] 5 | transport_stream_id = 1 6 | frequency = 0658,0000 7 | modulation = 64-QAM 8 | symbol_rate = 006,8750 9 | 10 | [Transponder2] 11 | transport_stream_id = 2 12 | frequency = 0666,0000 13 | modulation = 64-QAM 14 | symbol_rate = 006,8750 15 | 16 | [Transponder3] 17 | transport_stream_id = 3 18 | frequency = 0674,0000 19 | modulation = 64-QAM 20 | symbol_rate = 006,8750 21 | 22 | [Transponder4] 23 | transport_stream_id = 4 24 | frequency = 0682,0000 25 | modulation = 64-QAM 26 | symbol_rate = 006,8750 27 | 28 | [Transponder5] 29 | transport_stream_id = 5 30 | frequency = 0690,0000 31 | modulation = 64-QAM 32 | symbol_rate = 006,8750 33 | 34 | [Transponder6] 35 | transport_stream_id = 6 36 | frequency = 0698,0000 37 | modulation = 64-QAM 38 | symbol_rate = 006,8750 39 | 40 | [Transponder7] 41 | transport_stream_id = 7 42 | frequency = 0706,0000 43 | modulation = 64-QAM 44 | symbol_rate = 006,8750 45 | 46 | [Transponder8] 47 | transport_stream_id = 8 48 | frequency = 0714,0000 49 | modulation = 64-QAM 50 | symbol_rate = 006,8750 51 | -------------------------------------------------------------------------------- /mptsd_valgrind: -------------------------------------------------------------------------------- 1 | ulimit -c unlimited 2 | valgrind \ 3 | --log-file=mptsd-$(date +%F-%H:%M:%S).log \ 4 | --leak-check=full \ 5 | --show-reachable=yes \ 6 | --undef-value-errors=no \ 7 | --trace-children=yes \ 8 | --run-libc-freeres=yes \ 9 | --time-stamp=yes \ 10 | -- \ 11 | ./mptsd -O 239.78.78.78 12 | -------------------------------------------------------------------------------- /network.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd network routines 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "libfuncs/io.h" 29 | #include "libfuncs/log.h" 30 | #include "libfuncs/list.h" 31 | #include "libfuncs/asyncdns.h" 32 | 33 | #include "libtsfuncs/tsfuncs.h" 34 | 35 | #include "data.h" 36 | #include "config.h" 37 | 38 | extern char *server_sig; 39 | extern char *server_ver; 40 | extern CONFIG *config; 41 | 42 | int connect_udp(struct sockaddr_in send_to) { 43 | int sendsock = socket(AF_INET, SOCK_DGRAM, 0); 44 | if (sendsock < 0) { 45 | LOGf("socket(SOCK_DGRAM): %s\n", strerror(errno)); 46 | return -1; 47 | } 48 | int on = 1; 49 | setsockopt(sendsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 50 | // subscribe to multicast group 51 | // LOGf("Using ttl %d\n", multicast_ttl); 52 | if (IN_MULTICAST(ntohl(send_to.sin_addr.s_addr))) { 53 | if (setsockopt(sendsock, IPPROTO_IP, IP_MULTICAST_TTL, &config->multicast_ttl, sizeof(config->multicast_ttl)) < 0) { 54 | LOGf("setsockopt(IP_MUTICAST_TTL): %s\n", strerror(errno)); 55 | close(sendsock); 56 | return -1; 57 | } 58 | if (setsockopt(sendsock, IPPROTO_IP, IP_MULTICAST_IF, &config->output_intf, sizeof(config->output_intf)) < 0) { 59 | LOGf("setsockopt(IP_MUTICAST_IF, %s): %s\n", strerror(errno), inet_ntoa(config->output_intf)); 60 | close(sendsock); 61 | return -1; 62 | } 63 | } 64 | int writebuflen = 1316 * 100; 65 | if (setsockopt(sendsock, SOL_SOCKET, SO_SNDBUF, (const char *)&writebuflen, sizeof(writebuflen)) < 0) 66 | log_perror("play(): setsockopt(SO_SNDBUF)", errno); 67 | 68 | // call connect to get errors 69 | if (connect(sendsock, (struct sockaddr *)&send_to, sizeof send_to)) { 70 | LOGf("udp_connect() error: %s\n", strerror(errno)); 71 | close(sendsock); 72 | return -1; 73 | } 74 | return sendsock; 75 | } 76 | 77 | void connect_output(OUTPUT *o) { 78 | struct sockaddr_in sock; 79 | sock.sin_family = AF_INET; 80 | sock.sin_port = htons(o->out_port); 81 | sock.sin_addr = o->out_host; 82 | o->out_sock = connect_udp(sock); 83 | if (o->out_sock > -1) { 84 | //LOGf("OUTPUT: Connected out_fd: %i | Output: udp://%s:%d\n", o->out_sock, inet_ntoa(o->out_host), o->out_port); 85 | } else { 86 | LOGf("ERROR: Can't connect output | Output: udp://%s:%d\n", inet_ntoa(o->out_host), o->out_port); 87 | exit(1); 88 | } 89 | } 90 | 91 | /* 92 | On the last try, send no-signal to clients and exit 93 | otherwise wait a little bit before trying again 94 | */ 95 | #define DO_RECONNECT do \ 96 | { \ 97 | chansrc_free(&src); \ 98 | if (retries == 0) { \ 99 | return -1; \ 100 | } else { \ 101 | if (errno != EHOSTUNREACH) /* When host is unreachable there is already a delay of ~4 secs per try so no sleep is needed */ \ 102 | usleep(PROXY_RETRY_TIMEOUT * 1000); \ 103 | if (r->dienow) \ 104 | return -1; \ 105 | return 1; \ 106 | } \ 107 | } while(0) 108 | 109 | #define FATAL_ERROR do \ 110 | { \ 111 | chansrc_free(&src); \ 112 | return -1; \ 113 | } while (0) 114 | 115 | /* 116 | Returns: 117 | -1 = exit thread 118 | 1 = retry 119 | 0 = connected ok 120 | */ 121 | int connect_source(INPUT *r, int retries, int readbuflen, int *http_code) { 122 | CHANSRC *src = chansrc_init(r->channel->source); 123 | if (!src) { 124 | LOGf("ERR : Can't parse channel source | Channel: %s Source: %s\n", r->channel->name, r->channel->source); 125 | FATAL_ERROR; 126 | } 127 | r->connected = 0; 128 | r->reconnect = 0; 129 | 130 | int active = 1; 131 | int dret = async_resolve_host(src->host, src->port, &(r->src_sockname), 5000, &active); 132 | if (dret != 0) { 133 | if (dret == 1) 134 | proxy_log(r, "Can't resolve host"); 135 | if (dret == 2) 136 | proxy_log(r, "Timeout resolving host"); 137 | DO_RECONNECT; 138 | } 139 | 140 | proxy_log(r, "Connecting"); 141 | 142 | char buf[1024]; 143 | *http_code = 0; 144 | if (src->sproto == tcp_sock) { 145 | r->sock = socket(PF_INET, SOCK_STREAM, 0); 146 | if (r->sock < 0) { 147 | log_perror("play(): Could not create SOCK_STREAM socket.", errno); 148 | FATAL_ERROR; 149 | } 150 | //proxy_log(r, "Add"); 151 | if (do_connect(r->sock, (struct sockaddr *)&(r->src_sockname), sizeof(r->src_sockname), PROXY_CONNECT_TIMEOUT) < 0) { 152 | LOGf("ERR : Error connecting to %s srv_fd: %i err: %s\n", r->channel->source, r->sock, strerror(errno)); 153 | DO_RECONNECT; 154 | } 155 | 156 | snprintf(buf,sizeof(buf)-1, "GET /%s HTTP/1.0\r\nHost: %s:%u\r\nX-Smart-Client: yes\r\nUser-Agent: %s %s (%s)\r\n\r\n", 157 | src->path, src->host, src->port, server_sig, server_ver, config->ident); 158 | buf[sizeof(buf)-1] = 0; 159 | fdwrite(r->sock, buf, strlen(buf)); 160 | 161 | char xresponse[128]; 162 | memset(xresponse, 0, sizeof(xresponse)); 163 | memset(buf, 0, sizeof(buf)); 164 | regmatch_t res[4]; 165 | while (fdgetline(r->sock,buf,sizeof(buf)-1)) { 166 | if (buf[0] == '\n' || buf[0] == '\r') 167 | break; 168 | if (strstr(buf,"HTTP/1.") != NULL) { 169 | regex_t http_response; 170 | regcomp(&http_response, "^HTTP/1.[0-1] (([0-9]{3}) .*)", REG_EXTENDED); 171 | if (regexec(&http_response,buf,3,res,0) != REG_NOMATCH) { 172 | char codestr[4]; 173 | if ((unsigned int)res[1].rm_eo-res[1].rm_so < (unsigned int)sizeof(xresponse)) { 174 | strncpy(xresponse, &buf[res[1].rm_so], res[1].rm_eo-res[1].rm_so); 175 | xresponse[res[1].rm_eo-res[1].rm_so] = '\0'; 176 | chomp(xresponse); 177 | strncpy(codestr, &buf[res[2].rm_so], res[2].rm_eo-res[2].rm_so); 178 | codestr[3] = 0; 179 | *http_code = atoi(codestr); 180 | } 181 | } 182 | regfree(&http_response); 183 | } 184 | if (*http_code == 504) { // Extract extra error code 185 | if (strstr(buf, "X-ErrorCode: ") != NULL) { 186 | *http_code = atoi(buf+13); 187 | break; 188 | } 189 | } 190 | } 191 | if (*http_code == 0) { // No valid HTTP response, retry 192 | LOGf("DEBUG: Server returned not valid HTTP code | srv_fd: %i\n", r->sock); 193 | DO_RECONNECT; 194 | } 195 | if (*http_code == 504) { // No signal, exit 196 | LOGf("ERR : Get no-signal for %s from %s on srv_fd: %i\n", r->channel->name, r->channel->source, r->sock); 197 | FATAL_ERROR; 198 | } 199 | if (*http_code > 300) { // Unhandled or error codes, exit 200 | LOGf("ERR : Get code %i for %s from %s on srv_fd: %i exiting.\n", *http_code, r->channel->name, r->channel->source, r->sock); 201 | FATAL_ERROR; 202 | } 203 | // connected ok, continue 204 | } else { 205 | 206 | char multicast = IN_MULTICAST(ntohl(r->src_sockname.sin_addr.s_addr)); 207 | 208 | //if (!IN_MULTICAST(ntohl(r->src_sockname.sin_addr.s_addr))) { 209 | // LOGf("ERR : %s is not multicast address\n", r->channel->source); 210 | // FATAL_ERROR; 211 | //} 212 | struct ip_mreq mreq; 213 | struct sockaddr_in receiving_from; 214 | 215 | r->sock = socket(PF_INET, SOCK_DGRAM, 0); 216 | if (r->sock < 0) { 217 | log_perror("play(): Could not create SOCK_DGRAM socket.", errno); 218 | FATAL_ERROR; 219 | } 220 | // LOGf("CONN : Listening on multicast socket %s srv_fd: %i retries left: %i\n", r->channel->source, r->sock, retries); 221 | int on = 1; 222 | setsockopt(r->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 223 | 224 | if (multicast) { 225 | // subscribe to multicast group 226 | memcpy(&mreq.imr_multiaddr, &(r->src_sockname.sin_addr), sizeof(struct in_addr)); 227 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 228 | if (setsockopt(r->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { 229 | LOGf("ERR : Failed to add IP membership on %s srv_fd: %i\n", r->channel->source, r->sock); 230 | FATAL_ERROR; 231 | } 232 | } 233 | // bind to the socket so data can be read 234 | memset(&receiving_from, 0, sizeof(receiving_from)); 235 | receiving_from.sin_family = AF_INET; 236 | receiving_from.sin_addr = r->src_sockname.sin_addr; 237 | receiving_from.sin_port = htons(src->port); 238 | if (bind(r->sock, (struct sockaddr *) &receiving_from, sizeof(receiving_from)) < 0) { 239 | LOGf("ERR : Failed to bind to %s srv_fd: %i\n", r->channel->source, r->sock); 240 | FATAL_ERROR; 241 | } 242 | } 243 | 244 | if (setsockopt(r->sock, SOL_SOCKET, SO_RCVBUF, (const char *)&readbuflen, sizeof(readbuflen)) < 0) 245 | log_perror("play(): setsockopt(SO_RCVBUF)", errno); 246 | 247 | r->connected = 1; 248 | 249 | // proxy_log(r, "Connected"); 250 | chansrc_free(&src); 251 | return 0; 252 | } 253 | 254 | /* Start: 3 seconds on connect */ 255 | /* In connection: Max UDP timeout == 3 seconds (read) + 2 seconds (connect) == 5 seconds */ 256 | #define UDP_READ_RETRIES 3 257 | #define UDP_READ_TIMEOUT 1000 258 | 259 | /* Start: 1/4 seconds on connect */ 260 | /* In connection: Max TCP timeout == 5 seconds (read) + 2 seconds (connect) == 7 seconds */ 261 | /* In connection: Max TCP timeout == 5 seconds (read) + 8 seconds (connect, host unrch) == 13 seconds */ 262 | #define TCP_READ_RETRIES 5 263 | #define TCP_READ_TIMEOUT 1000 264 | 265 | /* 266 | Returns: 267 | 0 = synced ok 268 | 1 = not synced, reconnect 269 | */ 270 | int mpeg_sync(INPUT *r, channel_source source_proto) { 271 | time_t sync_start = time(NULL); 272 | unsigned int sync_packets = 0; 273 | unsigned int read_bytes = 0; 274 | char syncframe[188]; 275 | 276 | int _timeout = TCP_READ_TIMEOUT; 277 | int _retries = TCP_READ_RETRIES; 278 | if (source_proto == udp_sock) { 279 | _timeout = UDP_READ_TIMEOUT; 280 | _retries = UDP_READ_RETRIES; 281 | } 282 | do { 283 | if (r->dienow) 284 | return 1; 285 | resync: 286 | if (fdread_ex(r->sock, syncframe, 1, _timeout, _retries, 1) != 1) { 287 | proxy_log(r, "mpeg_sync fdread() timeoutA"); 288 | return 1; // reconnect 289 | } 290 | // LOGf("DEBUG: Read 0x%02x Offset %u Sync: %u\n", (uint8_t)syncframe[0], read_bytes, sync_packets); 291 | read_bytes++; 292 | if (syncframe[0] == 0x47) { 293 | ssize_t rdsz = fdread_ex(r->sock, syncframe, 188-1, _timeout, _retries, 1); 294 | if (rdsz != 188-1) { 295 | proxy_log(r, "mpeg_sync fdread() timeoutB"); 296 | return 1; // reconnect 297 | } 298 | read_bytes += 188-1; 299 | if (++sync_packets == 7) // sync 7 packets 300 | break; 301 | goto resync; 302 | } else { 303 | sync_packets = 0; 304 | } 305 | if (read_bytes > FRAME_PACKET_SIZE) { // Can't sync in 1316 bytes 306 | proxy_log(r, "mpeg_sync can't sync after 1316 bytes"); 307 | return 1; // reconnect 308 | } 309 | if (sync_start+2 <= time(NULL)) { // Do not sync in two seconds 310 | proxy_log(r, "mpeg_sync can't sync in 2 seconds"); 311 | return 1; // reconnect 312 | } 313 | } while (1); 314 | if (read_bytes-FRAME_PACKET_SIZE != 0) 315 | LOGf("INPUT : [%-12s] TS synced after %u bytes\n", r->channel->id, read_bytes-FRAME_PACKET_SIZE); 316 | return 0; 317 | } 318 | -------------------------------------------------------------------------------- /network.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd network routines header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef NETWORK_H 19 | #define NETWORK_H 20 | 21 | #include "data.h" 22 | 23 | void connect_output (OUTPUT *o); 24 | int connect_source (INPUT *r, int retries, int readbuflen, int *http_code); 25 | int mpeg_sync (INPUT *r, channel_source source_proto); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd output routines header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef OUTPUT_H 19 | #define OUTPUT_H 20 | 21 | #include "config.h" 22 | 23 | void output_psi_init (CONFIG *conf, OUTPUT *output); 24 | void output_psi_free (OUTPUT *output); 25 | 26 | void * output_handle_psi (void *_config); 27 | void * output_handle_mix (void *_config); 28 | void * output_handle_write (void *_config); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /output_mix.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd output mix packets 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "libfuncs/libfuncs.h" 27 | 28 | #include "libtsfuncs/tsfuncs.h" 29 | 30 | #include "data.h" 31 | #include "config.h" 32 | #include "input.h" 33 | 34 | void output_show_programs(CONFIG *conf) { 35 | LNODE *lr, *lrtmp; 36 | list_for_each(conf->inputs, lr, lrtmp) { 37 | INPUT *r = lr->data; 38 | if (r->input_ready == 1) { 39 | LOGf("OUTPUT: [%-12s] Service %d appeared.\n", r->channel->id, r->channel->service_id); 40 | r->input_ready++; 41 | } 42 | } 43 | } 44 | 45 | void * output_handle_mix(void *_config) { 46 | LNODE *lr, *lrtmp; 47 | LNODE *inpt; // Track last used input 48 | CONFIG *conf = _config; 49 | OUTPUT *o = conf->output; 50 | int buf_in_use = 0; 51 | unsigned int o_datasize, o_packets, packets; 52 | unsigned int o_maxpackets = o->obuf[0].size / TS_PACKET_SIZE; 53 | 54 | signal(SIGPIPE, SIG_IGN); 55 | 56 | inpt = conf->inputs->tail; // Next is the first one 57 | while (!o->dienow) { 58 | OBUF *curbuf = &o->obuf[buf_in_use]; 59 | 60 | usleep(o->obuf_ms); // Fill interval 61 | 62 | output_show_programs(conf); 63 | 64 | while (curbuf->status != obuf_empty) { 65 | if (o->dienow) 66 | goto OUT; 67 | //LOGf("MIX: Waiting for obuf %d\n", buf_in_use); 68 | usleep(1); 69 | } 70 | 71 | list_lock(conf->inputs); 72 | 73 | o_datasize = o->psibuf->input - o->psibuf->output; // PSI data 74 | list_for_each(conf->inputs, lr, lrtmp) { // INPUT data 75 | INPUT *r = lr->data; 76 | o_datasize += r->buf->input - r->buf->output; 77 | } 78 | 79 | o_packets = o_datasize / TS_PACKET_SIZE; 80 | packets = min(o_packets, o_maxpackets); 81 | 82 | double null_per_data = 1; 83 | double data_per_null = 0; 84 | if (o_maxpackets - packets) { 85 | data_per_null = (double)packets / (o_maxpackets-packets); 86 | if (data_per_null < 1) { 87 | null_per_data = (double)(o_maxpackets-packets) / packets; 88 | data_per_null = 1; 89 | } 90 | } 91 | 92 | curbuf->status = obuf_filling; // Mark buffer as being filled 93 | 94 | if (conf->debug) { 95 | LOGf("MIX[%2d]: Data:%6u | Bufsz:%6d | Packs:%4u | D/N:%5.2f/%5.2f\n", 96 | buf_in_use, 97 | o_datasize, 98 | curbuf->size, 99 | packets, 100 | ((double)packets / o_maxpackets) * 100, 101 | (double)100 - ((double)packets / o_maxpackets) * 100 102 | ); 103 | LOGf("datapacks:%5d maxpacks:%5d null:%5d (%5.2f) | null_per_data:%5.2f data_per_null:%5.2f\n", 104 | packets, 105 | o_maxpackets, 106 | o_maxpackets-packets, 107 | 100-((double)packets / o_maxpackets)*100, 108 | null_per_data, 109 | data_per_null 110 | ); 111 | } 112 | 113 | unsigned int nulls=0, null_packets_count = o_maxpackets - packets; 114 | // The is no data in the input buffer, send only NULLs 115 | if (null_packets_count == o_maxpackets) { 116 | // Increase sended packets 117 | list_for_each(conf->inputs, lr, lrtmp) { 118 | INPUT *r = lr->data; 119 | r->outputed_packets += o_maxpackets; 120 | } 121 | goto NEXT_BUFFER; 122 | } 123 | 124 | unsigned int data_packets; 125 | int data_size; 126 | uint8_t *data; 127 | for (data_packets=0;data_packetsdienow) 129 | break; 130 | 131 | // Try the PSI data first 132 | data = cbuf_get(o->psibuf, TS_PACKET_SIZE, &data_size); 133 | if (data && data_size == TS_PACKET_SIZE) 134 | goto SEND_PACKET; 135 | 136 | // Loop over inputs 137 | int inputs_left = conf->inputs->items; 138 | int read_count = 0; 139 | while (inputs_left--) { 140 | inpt = inpt->next; 141 | INPUT *r = inpt->data; 142 | if (!r) { // Skip the empty root! 143 | inpt = inpt->next; 144 | r = inpt->data; 145 | } 146 | if (!r || !r->buf) 147 | continue; 148 | 149 | // Move pcrs || Move & rewrite prcs 150 | if (conf->pcr_mode == 1 || conf->pcr_mode == 3) { 151 | // Is there any data in this input? 152 | data = cbuf_peek(r->buf, TS_PACKET_SIZE, &data_size); 153 | if (data_size == TS_PACKET_SIZE) { 154 | uint16_t pid = ts_packet_get_pid(data); 155 | // Do we have PCR packet? 156 | if (pid == r->output_pcr_pid && ts_packet_has_pcr(data)) { 157 | 158 | if (r->output_last_pcr != 0 && r->output_pcr_packets_needed <= 0) { 159 | // Calculate the required number of packets betweem this PCR and the last one 160 | r->output_pcr_packets_needed = round((double)(conf->output_bitrate / 8 / 27000000 / 188) * (ts_packet_get_pcr(data) - r->output_last_pcr)) - 1; 161 | // Check the boundaries 162 | int bound = (o_maxpackets < (INT_MAX/2))? (unsigned int)o_maxpackets : (INT_MAX/2) ; 163 | if (r->output_pcr_packets_needed > bound) 164 | r->output_pcr_packets_needed = bound; 165 | else if (r->output_pcr_packets_needed < 0) 166 | r->output_pcr_packets_needed = 0; 167 | } 168 | 169 | if (r->output_pcr_packets_needed > 0 && r->outputed_packets < r->output_pcr_packets_needed) { 170 | data = NULL; 171 | data_size = 0; 172 | read_count++; 173 | continue; 174 | } 175 | r->output_pcr = ts_packet_get_pcr(data); 176 | /* 177 | LOGf("%10s | pcr:%15llu last_pcr:%15llu diff:%10lld packets:%5d needed_packs:%d diff:%d\n", 178 | r->channel->id, 179 | r->output_pcr, 180 | r->output_last_pcr, 181 | r->output_pcr - r->output_last_pcr, 182 | r->outputed_packets, 183 | r->output_pcr_packets_needed, 184 | r->outputed_packets - r->output_pcr_packets_needed 185 | ); 186 | */ 187 | r->output_last_pcr = r->output_pcr; 188 | r->output_pcr_packets_needed = 0; 189 | r->outputed_packets = 0; 190 | } 191 | data = cbuf_get(r->buf, TS_PACKET_SIZE, &data_size); 192 | read_count++; 193 | if (data_size == TS_PACKET_SIZE) { // We have our data, no need to look at other inputs 194 | if (ts_packet_get_pid(data) < 0x1fff) 195 | break; 196 | else { // Remove padding 197 | data = NULL; 198 | data_size = 0; 199 | continue; 200 | } 201 | } 202 | } 203 | // Do not move PCRs 204 | } else { 205 | data = cbuf_get(r->buf, TS_PACKET_SIZE, &data_size); 206 | read_count++; 207 | if (data_size == TS_PACKET_SIZE) {// We have our data, no need to look at other inputs 208 | if (ts_packet_get_pid(data) < 0x1fff) 209 | break; 210 | else { // Remove padding 211 | data = NULL; 212 | data_size = 0; 213 | continue; 214 | } 215 | } 216 | } 217 | } // while (inputs_left--) 218 | 219 | if (read_count == 0) { // We have a problem! We have data in the incoming buffers, but nothing readed! 220 | data_packets--; // So, we do a fake loop for retry... (insufficient incoming data?) 221 | continue; 222 | } 223 | 224 | // We have data. Mix it with NULLs and stuff it in the output buffer 225 | // If the have no data, the output buffer will automaticaly be left 226 | // with NULL packets 227 | SEND_PACKET: 228 | if (data && data_size == TS_PACKET_SIZE) { 229 | // Mix data with NULLs 230 | if (nulls < null_packets_count) { 231 | if (round(nulls * data_per_null) < round(data_packets * null_per_data)) { 232 | nulls += round(data_packets * null_per_data) - round(nulls * data_per_null); 233 | } 234 | if (nulls > null_packets_count) 235 | nulls = null_packets_count; 236 | } 237 | if (data_packets+nulls >= o_maxpackets) { // Can't happen 238 | LOGf("wtf: %d packets:%d\n", data_packets+nulls, o_maxpackets); 239 | break; 240 | } 241 | uint8_t *bufptr = curbuf->buf + ((data_packets + nulls) * TS_PACKET_SIZE); 242 | memcpy(bufptr, data, TS_PACKET_SIZE); 243 | } 244 | 245 | // Increase sended packets 246 | list_for_each(conf->inputs, lr, lrtmp) { 247 | INPUT *r = lr->data; 248 | r->outputed_packets++; 249 | } 250 | } 251 | 252 | NEXT_BUFFER: 253 | list_unlock(conf->inputs); 254 | curbuf->status = obuf_full; // Mark buffer as full 255 | 256 | buf_in_use = buf_in_use ? 0 : 1; // Switch buffer 257 | } 258 | 259 | OUT: 260 | LOG("OUTPUT: MIX thread stopped.\n"); 261 | o->dienow++; 262 | 263 | LNODE *l, *tmp; 264 | list_for_each(conf->inputs, l, tmp) { 265 | INPUT *r = l->data; 266 | r->dienow = 1; 267 | } 268 | 269 | return 0; 270 | } 271 | -------------------------------------------------------------------------------- /output_psi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd output PSI handling 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | 22 | #include "libfuncs/log.h" 23 | #include "libfuncs/list.h" 24 | 25 | #include "libtsfuncs/tsfuncs.h" 26 | 27 | #include "config.h" 28 | #include "data.h" 29 | 30 | static void output_psi_init_pat(CONFIG *conf, OUTPUT *o) { 31 | LNODE *lc, *lctmp; 32 | o->pat = ts_pat_alloc_init(conf->transport_stream_id); 33 | list_lock(conf->channels); 34 | list_for_each(conf->channels, lc, lctmp) { 35 | CHANNEL *c = lc->data; 36 | ts_pat_add_program(o->pat, c->service_id, c->pmt_pid); 37 | } 38 | list_unlock(conf->channels); 39 | gettimeofday(&o->pat_ts, NULL); 40 | } 41 | 42 | static void output_psi_init_nit(CONFIG *conf, OUTPUT *o) { 43 | struct ts_nit *nit = ts_nit_alloc_init(conf->network_id); 44 | 45 | ts_nit_add_network_name_descriptor(nit, conf->network_name); 46 | 47 | if (!conf->use_lcn) { 48 | 49 | if (conf->nit->items < 64) { 50 | int num; 51 | LNODE *lc, *lctmp; 52 | uint32_t *freqs = malloc(conf->nit->items * sizeof(uint32_t)); 53 | uint32_t *services = malloc(conf->channels->items * sizeof(uint32_t)); 54 | 55 | num = 0; 56 | list_lock(conf->nit); 57 | list_for_each(conf->nit, lc, lctmp) { 58 | NIT *ndata = lc->data; 59 | freqs[num++] = ndata->_freq; 60 | } 61 | 62 | ts_nit_add_frequency_list_descriptor_cable(nit, conf->transport_stream_id, conf->network_id, freqs, num); 63 | 64 | list_for_each(conf->nit, lc, lctmp) { 65 | NIT *ndata = lc->data; 66 | ts_nit_add_cable_delivery_descriptor(nit, ndata->ts_id, conf->network_id, ndata->_freq, ndata->_modulation, ndata->_symbol_rate); 67 | } 68 | list_unlock(conf->nit); 69 | 70 | num = 0; 71 | list_lock(conf->channels); 72 | list_for_each(conf->channels, lc, lctmp) { 73 | CHANNEL *c = lc->data; 74 | uint32_t srv = 0; 75 | srv = (c->service_id &~ 0x00ff) << 8; 76 | srv |= (c->service_id &~ 0xff00) << 8; 77 | srv |= c->radio ? 0x02 : 0x01; 78 | services[num++] = srv; 79 | } 80 | list_unlock(conf->channels); 81 | 82 | ts_nit_add_service_list_descriptor(nit, conf->transport_stream_id, conf->network_id, services, num); 83 | free(freqs); 84 | free(services); 85 | } else { 86 | LOG("CONF : Too much items in the NIT, maximum is 64! NIT not generated.\n"); 87 | } 88 | 89 | } else { 90 | 91 | int num; 92 | char *ts_freq = NULL; 93 | char *ts_modulation = NULL; 94 | char *ts_symbol_rate = NULL; 95 | uint32_t ts__freq = 0; 96 | uint8_t ts__modulation = 0; 97 | uint32_t ts__symbol_rate = 0; 98 | LNODE *lc, *lctmp; 99 | uint32_t *svc_services = malloc(conf->channels->items * sizeof(uint32_t)); 100 | uint32_t *lcn_services = malloc(conf->channels->items * sizeof(uint32_t)); 101 | 102 | list_lock(conf->nit); 103 | list_for_each(conf->nit, lc, lctmp) { 104 | NIT *ndata = lc->data; 105 | if (ndata->ts_id == conf->transport_stream_id) { 106 | ts_freq = ndata->freq; 107 | ts_modulation = ndata->modulation; 108 | ts_symbol_rate = ndata->symbol_rate; 109 | ts__freq = ndata->_freq; 110 | ts__modulation = ndata->_modulation; 111 | ts__symbol_rate = ndata->_symbol_rate; 112 | } 113 | } 114 | list_unlock(conf->nit); 115 | 116 | list_lock(conf->channels); 117 | num = 0; 118 | list_for_each(conf->channels, lc, lctmp) { 119 | CHANNEL *c = lc->data; 120 | uint32_t srv = 0; 121 | srv = (c->service_id &~ 0x00ff) << 16; 122 | srv |= (c->service_id &~ 0xff00) << 16; 123 | srv |= (c->lcn_visible ? 0x01 : 0x00 &~ 0xf0 ) << 15; 124 | srv |= (0X00 &~ 0xf0 ) << 14; 125 | srv |= (c->lcn &~ 0xc0ff); 126 | srv |= (c->lcn &~ 0xff00); 127 | lcn_services[num++] = srv; 128 | } 129 | 130 | num = 0; 131 | list_for_each(conf->channels, lc, lctmp) { 132 | CHANNEL *c = lc->data; 133 | uint32_t srv = 0; 134 | srv = (c->service_id &~ 0x00ff) << 8; 135 | srv |= (c->service_id &~ 0xff00) << 8; 136 | srv |= c->radio ? 0x02 : 0x01; 137 | svc_services[num++] = srv; 138 | } 139 | list_unlock(conf->channels); 140 | 141 | NIT *ts_ndata = nit_new(conf->transport_stream_id, ts_freq, ts_modulation, ts_symbol_rate); 142 | ts_nit_add_stream_descriptors(nit, ts_ndata->ts_id, conf->network_id, ts__freq, ts__modulation, ts__symbol_rate, lcn_services, svc_services, num); 143 | 144 | if (conf->nit->items < 64) { 145 | list_lock(conf->nit); 146 | list_for_each(conf->nit, lc, lctmp) { 147 | NIT *ndata = lc->data; 148 | ts_nit_add_cable_delivery_descriptor(nit, ndata->ts_id, conf->network_id, ndata->_freq, ndata->_modulation, ndata->_symbol_rate); 149 | } 150 | list_unlock(conf->nit); 151 | 152 | free(lcn_services); 153 | free(svc_services); 154 | } else { 155 | LOG("CONF : Too much items in the NIT, maximum is 64! NIT not generated.\n"); 156 | } 157 | 158 | } 159 | 160 | gettimeofday(&o->nit_ts, NULL); 161 | o->nit = nit; 162 | } 163 | 164 | static void output_psi_init_sdt(CONFIG *conf, OUTPUT *o) { 165 | LNODE *lc, *lctmp; 166 | struct ts_sdt *sdt = ts_sdt_alloc_init(conf->network_id, conf->transport_stream_id); 167 | list_lock(conf->channels); 168 | list_for_each(conf->channels, lc, lctmp) { 169 | CHANNEL *c = lc->data; 170 | ts_sdt_add_service_descriptor(sdt, c->service_id, c->radio == 0, conf->provider_name, c->name); 171 | } 172 | list_unlock(conf->channels); 173 | gettimeofday(&o->sdt_ts, NULL); 174 | o->sdt = sdt; 175 | } 176 | 177 | static void output_psi_init_tdt_tot(CONFIG *conf, OUTPUT *o) { 178 | (void)conf; // Silence warning 179 | o->pid_tdt_cont = 15; 180 | o->tdt = ts_tdt_alloc_init(time(NULL)); 181 | o->tot = ts_tot_alloc_init(time(NULL)); 182 | gettimeofday(&o->tdt_ts, NULL); 183 | gettimeofday(&o->tot_ts, NULL); 184 | } 185 | 186 | 187 | static void output_add_pat(OUTPUT *o) { 188 | if (!o->pat->programs_num) { 189 | LOG("OUTPUT: Error no programs in PAT!\n"); 190 | return; 191 | } 192 | int i; 193 | struct ts_pat *pat = o->pat; 194 | // LOGf("OUTPUT: Outputing PAT with %d programs\n", o->pat->programs_num); 195 | for (i=0;isection_header->num_packets;i++) { 196 | ts_packet_set_cont(pat->section_header->packet_data + (i * TS_PACKET_SIZE), i + o->pid_pat_cont); 197 | } 198 | pat->ts_header.continuity = o->pid_pat_cont; 199 | o->pid_pat_cont += pat->section_header->num_packets; 200 | cbuf_fill(o->psibuf, pat->section_header->packet_data, pat->section_header->num_packets * TS_PACKET_SIZE); 201 | // ts_pat_dump(o->pat); 202 | } 203 | 204 | void output_add_nit(OUTPUT *o) { 205 | if (!o || !o->nit) 206 | return; 207 | int i; 208 | struct ts_nit *nit = o->nit; 209 | // LOGf("OUTPUT: Outputing NIT\n"); 210 | for (i=0;isection_header->num_packets;i++) { 211 | ts_packet_set_cont(nit->section_header->packet_data + (i * TS_PACKET_SIZE), i + o->pid_nit_cont); 212 | } 213 | nit->ts_header.continuity = o->pid_nit_cont; 214 | o->pid_nit_cont += nit->section_header->num_packets; 215 | cbuf_fill(o->psibuf, nit->section_header->packet_data, nit->section_header->num_packets * TS_PACKET_SIZE); 216 | // ts_nit_dump(nit); 217 | } 218 | 219 | void output_add_sdt(OUTPUT *o) { 220 | if (!o || !o->sdt) 221 | return; 222 | int i; 223 | struct ts_sdt *sdt = o->sdt; 224 | // LOGf("OUTPUT: Outputing SDT\n"); 225 | for (i=0;isection_header->num_packets;i++) { 226 | ts_packet_set_cont(sdt->section_header->packet_data + (i * TS_PACKET_SIZE), i + o->pid_sdt_cont); 227 | } 228 | sdt->ts_header.continuity = o->pid_sdt_cont; 229 | o->pid_sdt_cont += sdt->section_header->num_packets; 230 | cbuf_fill(o->psibuf, sdt->section_header->packet_data, sdt->section_header->num_packets * TS_PACKET_SIZE); 231 | // ts_sdt_dump(o->sdt); 232 | } 233 | 234 | static void output_add_pid0x14(OUTPUT *o, struct ts_tdt *tdt) { 235 | if (!o || !o->tdt) 236 | return; 237 | int i; 238 | // LOGf("OUTPUT: Outputing TDT\n"); 239 | for (i=0;isection_header->num_packets;i++) { 240 | ts_packet_set_cont(tdt->section_header->packet_data + (i * TS_PACKET_SIZE), i + o->pid_tdt_cont); 241 | } 242 | tdt->ts_header.continuity = o->pid_tdt_cont; 243 | o->pid_tdt_cont += tdt->section_header->num_packets; 244 | cbuf_fill(o->psibuf, tdt->section_header->packet_data, tdt->section_header->num_packets * TS_PACKET_SIZE); 245 | } 246 | 247 | static void output_add_tdt(OUTPUT *o) { 248 | // LOGf("OUTPUT: Outputing TDT\n"); 249 | ts_tdt_set_time(o->tdt, time(NULL)); 250 | output_add_pid0x14(o, o->tdt); 251 | // ts_tdt_dump(o->tdt); 252 | } 253 | 254 | static void output_add_tot(OUTPUT *o) { 255 | // LOGf("OUTPUT: Outputing TOT\n"); 256 | ts_tot_set_localtime_offset_sofia(o->tot, time(NULL)); 257 | output_add_pid0x14(o, o->tot); 258 | // ts_tdt_dump(o->tot); 259 | } 260 | 261 | static void __output_add_eit(OUTPUT *o, struct ts_eit *eit) { 262 | if (!eit) 263 | return; 264 | // LOGf("OUTPUT: Outputing EIT\n"); 265 | int i, pcnt = o->pid_eit_cont; 266 | if (eit->section_header && eit->section_header->packet_data) { 267 | for (i=0;isection_header->num_packets;i++) { 268 | ts_packet_set_cont(eit->section_header->packet_data + (i * TS_PACKET_SIZE), i + pcnt); 269 | } 270 | eit->ts_header.continuity = pcnt; 271 | o->pid_eit_cont += eit->section_header->num_packets; 272 | cbuf_fill(o->psibuf, eit->section_header->packet_data, eit->section_header->num_packets * TS_PACKET_SIZE); 273 | } 274 | // ts_eit_dump(eit); 275 | } 276 | 277 | static void output_add_eit(CONFIG *conf, OUTPUT *o) { 278 | LNODE *lr, *lrtmp; 279 | config_load_epg(conf); 280 | list_for_each(conf->inputs, lr, lrtmp) { 281 | INPUT *r = lr->data; 282 | __output_add_eit(o, r->channel->eit_now); 283 | __output_add_eit(o, r->channel->eit_next); 284 | } 285 | } 286 | 287 | static void output_psi_add(CONFIG *conf, OUTPUT *o, struct timeval *now) { 288 | if (timeval_diff_msec(&o->pat_ts, now) >= conf->timeouts.pat) { 289 | o->pat_ts = *now; 290 | output_add_pat(o); 291 | } 292 | if (timeval_diff_msec(&o->nit_ts, now) >= conf->timeouts.nit) { 293 | o->nit_ts = *now; 294 | output_add_nit(o); 295 | } 296 | if (timeval_diff_msec(&o->sdt_ts, now) >= conf->timeouts.sdt) { 297 | o->sdt_ts = *now; 298 | output_add_sdt(o); 299 | } 300 | if (timeval_diff_msec(&o->tdt_ts, now) >= conf->timeouts.tdt) { 301 | o->tdt_ts = *now; 302 | output_add_tdt(o); 303 | } 304 | if (timeval_diff_msec(&o->tot_ts, now) >= conf->timeouts.tot) { 305 | o->tot_ts = *now; 306 | output_add_tot(o); 307 | } 308 | if (timeval_diff_msec(&o->eit_ts, now) >= conf->timeouts.eit) { 309 | o->eit_ts = *now; 310 | output_add_eit(conf, o); 311 | } 312 | } 313 | 314 | 315 | void output_psi_init(CONFIG *conf, OUTPUT *output) { 316 | output_psi_init_pat(conf, output); 317 | output_psi_init_nit(conf, output); 318 | output_psi_init_sdt(conf, output); 319 | output_psi_init_tdt_tot(conf, output); 320 | gettimeofday(&output->eit_ts, NULL); 321 | } 322 | 323 | void output_psi_free(OUTPUT *o) { 324 | ts_pat_free(&o->pat); 325 | ts_nit_free(&o->nit); 326 | ts_sdt_free(&o->sdt); 327 | ts_tdt_free(&o->tdt); 328 | ts_tdt_free(&o->tot); 329 | } 330 | 331 | void * output_handle_psi(void *_config) { 332 | CONFIG *conf = _config; 333 | OUTPUT *o = conf->output; 334 | struct timeval now; 335 | 336 | signal(SIGPIPE, SIG_IGN); 337 | while (!o->dienow) { 338 | gettimeofday(&now, NULL); 339 | output_psi_add(conf, o, &now); 340 | usleep(10000); // 10 ms 341 | } 342 | LOG("OUTPUT: PSI thread stopped.\n"); 343 | o->dienow++; 344 | return 0; 345 | } 346 | -------------------------------------------------------------------------------- /output_write.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd output writing 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "libfuncs/io.h" 27 | #include "libfuncs/log.h" 28 | #include "libfuncs/list.h" 29 | 30 | #include "libtsfuncs/tsfuncs.h" 31 | 32 | #include "sleep.h" 33 | #include "data.h" 34 | #include "config.h" 35 | #include "network.h" 36 | 37 | void increase_process_priority() { 38 | return; 39 | #ifdef __linux__ 40 | struct sched_param param; 41 | param.sched_priority = 99; 42 | if (sched_setscheduler(0, SCHED_FIFO, ¶m)==-1) { 43 | log_perror("sched_setscheduler() failed!", errno); 44 | } else { 45 | LOGf("PRIO : sched_setschedule() succeded.\n"); 46 | } 47 | #endif 48 | } 49 | 50 | void ts_frame_process(CONFIG *conf, OUTPUT *o, uint8_t *data) { 51 | int i; 52 | uint16_t pid; 53 | uint8_t *ts_packet; 54 | for (i=0; ipadding_period += TS_PACKET_SIZE; 61 | } 62 | 63 | if (ts_packet_has_pcr(ts_packet)) { 64 | uint64_t pcr = ts_packet_get_pcr(ts_packet); // Current PCR 65 | uint64_t new_pcr = pcr; 66 | uint64_t bytes = o->traffic + i; 67 | 68 | if (o->last_pcr[pid]) { 69 | uint64_t old_pcr = o->last_pcr[pid]; 70 | uint64_t old_org_pcr = o->last_org_pcr[pid]; 71 | uint64_t old_bytes = o->last_traffic[pid]; 72 | if (old_org_pcr < pcr) { // Detect PCR wraparound 73 | new_pcr = old_pcr + (double)((bytes - old_bytes) * 8 * 27000000) / o->output_bitrate; 74 | // Rewrite pcrs || Move pcrs & rewrite prcs 75 | if (conf->pcr_mode == 2 || conf->pcr_mode == 3) { 76 | ts_packet_set_pcr(ts_packet, new_pcr); 77 | } 78 | if (conf->debug) { 79 | uint64_t ts_rate = (double)(((bytes - old_bytes) * 8) * 27000000) / (pcr - old_org_pcr); 80 | uint64_t ts_rate_new = (double)(((bytes - old_bytes) * 8) * 27000000) / (new_pcr - old_pcr); 81 | LOGf("PCR[%03x]: old:%14" PRIu64 " new:%14" PRIu64 " pcr_diff:%8" PRId64 " ts_rate:%9" PRIu64 " ts_rate_new:%9" PRIu64 " diff:%9" PRId64 " | passed:%" PRIu64 "\n", 82 | pid, 83 | pcr, 84 | new_pcr, 85 | pcr - new_pcr, 86 | ts_rate, 87 | ts_rate_new, 88 | ts_rate - ts_rate_new, 89 | bytes - old_bytes 90 | ); 91 | } 92 | } 93 | } else { 94 | // if (config->debug) { 95 | // LOGf("PCR[%03x]: %10llu init\n", pid, pcr); 96 | // } 97 | } 98 | o->last_pcr[pid] = new_pcr; 99 | o->last_org_pcr[pid] = pcr; 100 | o->last_traffic[pid] = bytes; 101 | } 102 | } 103 | } 104 | 105 | 106 | // write plain UDP packets 107 | ssize_t ts_frame_write(OUTPUT *o, uint8_t *data) { 108 | ssize_t written, written2; 109 | 110 | written = fdwrite(o->out_sock, (char *)data, FRAME_PACKET_SIZE); 111 | if (o->ofd) 112 | written2 = write(o->ofd, data, FRAME_PACKET_SIZE); 113 | else written2 = 0; 114 | 115 | if (written2 > written) 116 | written = written2; 117 | if (written >= 0) { 118 | o->traffic += written; 119 | o->traffic_period += written; 120 | } 121 | 122 | return written; 123 | } 124 | 125 | 126 | // write RTP packets 127 | ssize_t ts_frame_write_rtp(OUTPUT *o, uint8_t *data) { 128 | ssize_t written, written2; 129 | const size_t write_len = FRAME_PACKET_SIZE + RTP_HEADER_SIZE; 130 | uint8_t rtp_buffer[write_len]; 131 | 132 | rtp_buffer[0] = (((2 & 0x03) << 6) // version 133 | | ((0 & 0x01) << 5) // padding 134 | | ((0 & 0x01) << 4) // extension 135 | | ((0 & 0x0F) << 0)); // cc 136 | 137 | rtp_buffer[1] = ( ((0 & 0x01) << 7) // Marker 138 | | ((33 & 0x7F) << 0) ); // Payload type: MPEG-II transport streams (33) 139 | 140 | // sequence number 141 | uint16_t seq = htons(o->rtp_sequence_number); 142 | memcpy (&rtp_buffer[2], &seq, 2); 143 | 144 | // timestamp 145 | struct timespec ts; 146 | clock_gettime(CLOCK_MONOTONIC, &ts); 147 | uint64_t time = ts.tv_sec * 1000000ll + ts.tv_nsec / 1000; 148 | uint32_t timestamp_value = (uint32_t) (90000 * (time/1000000ll))+(9*(time%1000000ll))/100; // 90 kHz Clock 149 | uint32_t timestamp = htonl(timestamp_value); 150 | memcpy (&rtp_buffer[4], ×tamp, 4); 151 | 152 | // SSRC 153 | uint32_t ssrc = htonl(o->rtp_ssrc); 154 | memcpy (&rtp_buffer[8], &ssrc, 4); 155 | 156 | // copy payload 157 | memcpy(&rtp_buffer[12], data, FRAME_PACKET_SIZE); 158 | 159 | written = fdwrite(o->out_sock, (char *)rtp_buffer, write_len); 160 | if (o->ofd) 161 | written2 = write(o->ofd, data, FRAME_PACKET_SIZE); 162 | else written2 = 0; 163 | 164 | if (written2 > written) 165 | written = written2 + RTP_HEADER_SIZE; 166 | if (written >= RTP_HEADER_SIZE) { 167 | written -= RTP_HEADER_SIZE; 168 | o->traffic += written; 169 | o->traffic_period += written; 170 | } 171 | 172 | o->rtp_sequence_number++; // increment RTP sequence number 173 | 174 | return written; 175 | } 176 | 177 | void * output_handle_write(void *_config) { 178 | CONFIG *conf = _config; 179 | OUTPUT *o = conf->output; 180 | int buf_in_use = 0; 181 | struct timeval stats_ts, now; 182 | struct timeval start_write_ts, end_write_ts, used_ts; 183 | unsigned long long stats_interval; 184 | 185 | signal(SIGPIPE, SIG_IGN); 186 | 187 | increase_process_priority(); 188 | 189 | gettimeofday(&stats_ts, NULL); 190 | while (!o->dienow) { 191 | gettimeofday(&now, NULL); 192 | OBUF *curbuf = &o->obuf[buf_in_use]; 193 | 194 | while (curbuf->status != obuf_full) { // Wait untill the buffer is ready ot it is already emptying 195 | if (o->dienow) 196 | goto OUT; 197 | //LOGf("MIX: Waiting for obuf %d\n", buf_in_use); 198 | usleep(1); 199 | } 200 | curbuf->status = obuf_emptying; // Mark buffer as being filled 201 | 202 | // Show stats 203 | stats_interval = timeval_diff_msec(&stats_ts, &now); 204 | if (stats_interval > conf->timeouts.stats) { 205 | stats_ts = now; 206 | double out_kbps = (double)(o->traffic_period * 8) / 1000; 207 | double out_mbps = (double)out_kbps / 1000; 208 | double opadding = ((double)o->padding_period / o->traffic_period) * 100; 209 | 210 | if (!conf->quiet) { 211 | LOGf("STAT : Pad:%6.2f%% Traf:%5.2f Mbps | %8.2f | %7" PRIu64 "\n", 212 | opadding, 213 | out_mbps, 214 | out_kbps, 215 | o->traffic_period 216 | ); 217 | } 218 | o->traffic_period = 0; 219 | o->padding_period = 0; 220 | } 221 | 222 | gettimeofday(&start_write_ts, NULL); 223 | int packets_written = 0, real_sleep_time = conf->output_tmout - conf->usleep_overhead; 224 | long time_taken, time_diff, real_time, overhead = 0, overhead_total = 0; 225 | ssize_t written = 0; 226 | while (curbuf->written < curbuf->size) { 227 | if (o->dienow) 228 | goto OUT; 229 | long sleep_interval = conf->output_tmout; 230 | uint8_t *ts_frame = curbuf->buf + curbuf->written; 231 | ts_frame_process(conf, o, ts_frame); // Fix PCR and count NULL packets 232 | if (o->rtp_ssrc != 0) { 233 | written += ts_frame_write_rtp(o, ts_frame); // Write RTP packet to network/file 234 | } else { 235 | written += ts_frame_write(o, ts_frame); // Write plain UDP packet to network/file 236 | } 237 | curbuf->written += FRAME_PACKET_SIZE; 238 | if (packets_written) { 239 | time_taken = timeval_diff_usec(&start_write_ts, &used_ts); 240 | real_time = packets_written * (conf->output_tmout + conf->usleep_overhead); 241 | time_diff = real_time - time_taken; 242 | overhead = (time_taken / packets_written) - sleep_interval; 243 | overhead_total += overhead; 244 | /* 245 | LOGf("[%5d] time_taken:%5ld real_time:%5ld time_diff:%ld | overhead:%5ld overhead_total:%5ld\n", 246 | packets_written, 247 | time_taken, 248 | real_time, 249 | time_diff, 250 | overhead, 251 | overhead_total 252 | ); 253 | */ 254 | if (time_diff > real_sleep_time) { 255 | sleep_interval = time_diff - conf->usleep_overhead; 256 | if (sleep_interval < 0) 257 | sleep_interval = 1; 258 | // LOGf("Add sleep. time_diff: %ld sleep_interval: %ld\n", time_diff, sleep_interval); 259 | } else { 260 | //LOGf("Skip sleep %ld\n", time_diff); 261 | sleep_interval = 0; 262 | } 263 | 264 | } 265 | if (sleep_interval > 0) 266 | usleep(sleep_interval); 267 | gettimeofday(&used_ts, NULL); 268 | packets_written++; 269 | } 270 | gettimeofday(&end_write_ts, NULL); 271 | unsigned long long write_time = timeval_diff_usec(&start_write_ts, &end_write_ts); 272 | if (write_time < o->obuf_ms * 1000) { 273 | //LOGf("Writen for -%llu us less\n", o->obuf_ms*1000 - write_time); 274 | usleep(o->obuf_ms*1000 - write_time); 275 | } else { 276 | //LOGf("Writen for +%llu us more\n", write_time - o->obuf_ms*1000); 277 | } 278 | 279 | obuf_reset(curbuf); // Buffer us all used up 280 | buf_in_use = buf_in_use ? 0 : 1; // Switch buffer 281 | if (written < 0) { 282 | LOG("OUTPUT: Error writing into output socket.\n"); 283 | shutdown_fd(&o->out_sock); 284 | if (conf->write_output_network) { 285 | connect_output(o); 286 | } 287 | } 288 | } 289 | OUT: 290 | LOG("OUTPUT: WRITE thread stopped.\n"); 291 | o->dienow++; 292 | return 0; 293 | } 294 | -------------------------------------------------------------------------------- /pidref.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pidref implementation 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | 20 | #include "libfuncs/log.h" 21 | #include "libtsfuncs/tsfuncs.h" 22 | #include "pidref.h" 23 | 24 | PIDREF *pidref_init(int num, uint16_t base_pid) { 25 | PIDREF *ref = calloc(1, sizeof(PIDREF)); 26 | ref->num = num; 27 | ref->base_pid = base_pid; 28 | ref->entries = calloc(ref->num, sizeof(PIDREF_ENTRY)); 29 | return ref; 30 | } 31 | 32 | void pidref_free(PIDREF **pref) { 33 | PIDREF *ref = *pref; 34 | if (!ref) 35 | return; 36 | FREE(ref->entries); 37 | FREE(*pref); 38 | } 39 | 40 | int pidref_add(PIDREF *ref, uint16_t org_pid, uint16_t new_pid) { 41 | int i; 42 | if (!org_pid) 43 | return 0; 44 | for (i=0;inum;i++) { 45 | PIDREF_ENTRY *entry = &ref->entries[i]; 46 | if (!entry->org_pid) { 47 | entry->org_pid = org_pid; 48 | entry->new_pid = new_pid; 49 | return 1; 50 | } 51 | } 52 | return 0; 53 | } 54 | 55 | int pidref_del(PIDREF *ref, uint16_t org_pid) { 56 | int i; 57 | if (!org_pid) 58 | return 0; 59 | for (i=0;inum;i++) { 60 | PIDREF_ENTRY *entry = &ref->entries[i]; 61 | if (entry->org_pid == org_pid) { 62 | entry->org_pid = 0; 63 | entry->new_pid = 0; 64 | return 1; 65 | } 66 | } 67 | return 0; 68 | } 69 | 70 | uint16_t pidref_get_new_pid(PIDREF *ref, uint16_t org_pid) { 71 | int i; 72 | if (!org_pid) 73 | return 0; 74 | for (i=0;inum;i++) { 75 | PIDREF_ENTRY *entry = &ref->entries[i]; 76 | if (entry->org_pid == org_pid) { 77 | return entry->new_pid; 78 | } 79 | } 80 | return 0; 81 | } 82 | 83 | int pidref_change_packet_pid(uint8_t *ts_packet, uint16_t packet_pid, PIDREF *ref) { 84 | uint16_t new_pid = pidref_get_new_pid(ref, packet_pid); 85 | if (new_pid) { 86 | ts_packet_set_pid(ts_packet, new_pid); 87 | return new_pid; 88 | } 89 | return 0; 90 | } 91 | 92 | void pidref_dump(PIDREF *ref) { 93 | int i; 94 | LOGf("pidref->base_pid = 0x%04x\n", ref->base_pid); 95 | LOGf("pidref->num = %d\n" , ref->num); 96 | LOG ("pidref->entries org_pid new_pid\n"); 97 | for (i=0;inum;i++) { 98 | PIDREF_ENTRY *entry = &ref->entries[i]; 99 | if (entry->org_pid) 100 | LOGf("pidref->entry[%02d] = 0x%04x 0x%04x\n", i, entry->org_pid, entry->new_pid); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pidref.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pidref header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef PIDREF_H 19 | #define PIDREF_H 20 | 21 | #include 22 | 23 | typedef struct { 24 | uint16_t org_pid; 25 | uint16_t new_pid; 26 | } PIDREF_ENTRY; 27 | 28 | typedef struct { 29 | uint16_t base_pid; // From this pid on there will be rewrites 30 | int num; 31 | PIDREF_ENTRY *entries; 32 | } PIDREF; 33 | 34 | PIDREF * pidref_init (int num, uint16_t base_pid); 35 | void pidref_free (PIDREF **ref); 36 | 37 | int pidref_add (PIDREF *ref, uint16_t org_pid, uint16_t new_pid); 38 | int pidref_del (PIDREF *ref, uint16_t org_pid); 39 | 40 | uint16_t pidref_get_new_pid (PIDREF *ref, uint16_t org_pid); 41 | 42 | int pidref_change_packet_pid (uint8_t *ts_packet, uint16_t packet_pid, PIDREF *ref); 43 | 44 | void pidref_dump (PIDREF *ref); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /rc.mptsd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mptsd server control script 3 | # Copyright (C) 2007-2011 Unix Solutions Ltd. 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 7 | # as published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | # 18 | 19 | PATH="/home/iptv:/bin:/sbin:/usr/bin:/usr/local/bin" 20 | export PATH 21 | cd $(dirname $0) 22 | 23 | CONFIG="$(basename $0).conf" 24 | 25 | if [ ! -r $CONFIG ] 26 | then 27 | echo "[ERROR] $CONFIG is not found." 28 | exit 1 29 | fi 30 | 31 | . ./$CONFIG 32 | 33 | istart() { 34 | echo "[START] Starting $PRGNAME." 35 | if [ ! -f "$SERVER" -o ! -x "$SERVER" ] 36 | then 37 | echo "[ERROR] $SERVER does not exist or it's not executable." 38 | exit 1 39 | fi 40 | if [ -r "$PIDFILE" ] 41 | then 42 | fpid=$(cat "$PIDFILE" 2>/dev/null) 43 | rpid=$(pidof $PRGNAME 2>/dev/null) 44 | if [ -n "$rpid" -a "0$fpid" -eq "0$rpid" ] 45 | then 46 | echo "[ERROR] $PRGNAME is already running: (pid $rpid)" 47 | exit 1 48 | else 49 | echo "[ERROR] $PIDFILE is stale, $PRGNAME is not running. Deleting it pid file." 50 | rm $PIDFILE 51 | fi 52 | fi 53 | echo "[CMD ] $SERVER $PARAMS" 54 | cd $(dirname $SERVER) 55 | $SERVER $PARAMS 56 | if [ $? -eq 0 ] 57 | then 58 | echo "[OK ] $PRGNAME started." 59 | else 60 | echo "[ERROR] $PRGNAME not started." 61 | fi 62 | } 63 | 64 | istop() { 65 | echo "[STOP ] Stopping $PRGNAME." 66 | killall $PRGNAME 67 | if [ $? -eq 0 ] 68 | then 69 | echo -n "[WAIT ] Waiting" 70 | echo -n "." && sleep .3 71 | echo -n "." && sleep .3 72 | echo -n "." && sleep .3 73 | echo -n "." && sleep .2 74 | echo "." 75 | if [ -r "$PIDFILE" ] 76 | then 77 | RPID=$(pidof $PRGNAME 2>/dev/null) 78 | if [ "0$RPID" -ne "0" ] 79 | then 80 | echo "[ERROR] $PRGNAME is still running: (pid $(cat $PIDFILE)). Kill -9ing it." 81 | killall -9 $PRGNAME 82 | fi 83 | fi 84 | echo "[OK ] $PRGNAME is stopped." 85 | fi 86 | } 87 | 88 | icheck() { 89 | if [ -r "$PIDFILE" ] 90 | then 91 | fpid=$(cat "$PIDFILE" 2>/dev/null) 92 | rpid=$(pidof $PRGNAME 2>/dev/null) 93 | if [ -n "$rpid" -a "0$fpid" -eq "0$rpid" ] 94 | then 95 | echo "[CHECK] $PRGNAME is already running: (pid $rpid)" 96 | else 97 | istart 98 | fi 99 | else 100 | echo "[CHECK] Stop and start" 101 | istop 102 | istart 103 | fi 104 | } 105 | 106 | istatus() { 107 | rpid=$(pidof $PRGNAME 2>/dev/null) 108 | echo "[STATUS] $PRGNAME pidfile pid: $(cat $PIDFILE 2>/dev/null)" 109 | echo "[STATUS] $PRGNAME pidof pid: $rpid" 110 | if [ -z "$rpid" ] 111 | then 112 | echo "[STATUS] $PRGNAME is not running." 113 | else 114 | if [ -n "$rpid" -a "0$(cat $PIDFILE 2>/dev/null)" -eq "0$rpid" ] 115 | then 116 | echo "[STATUS] $PRGNAME is running" 117 | else 118 | echo "[STATUS] $PRGNAME is running but no pid file exist: $PIDFILE" 119 | fi 120 | ps ax | grep "$PRGNAME -i" | grep -v grep 121 | fi 122 | } 123 | 124 | ireconnect() { 125 | echo "[RECONN] Sending SIGUSR1 to $PRGNAME causing reconnect" 126 | kill -USR1 $(pidof $PRGNAME) 127 | } 128 | 129 | ireload() { 130 | echo "[RECONN] Sending SIGHUP to $PRGNAME causing configuration reload" 131 | kill -HUP $(pidof $PRGNAME) 132 | } 133 | 134 | case "$1" in 135 | 'start') 136 | if [ "$2" != "" ] 137 | then 138 | sleep "$2" 139 | fi 140 | istart 141 | ;; 142 | 'stop') 143 | istop 144 | ;; 145 | 'check') 146 | icheck 147 | ;; 148 | 'status') 149 | istatus 150 | ;; 151 | 'restart') 152 | istop 153 | istart 154 | ;; 155 | 'reload') 156 | ireload 157 | ;; 158 | 'reconnect') 159 | ireconnect 160 | ;; 161 | *) 162 | echo "Usage: `basename $0` start|stop|check|restart|reload|reconnect|status" 163 | esac 164 | -------------------------------------------------------------------------------- /rc.mptsd.conf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ulimit -c unlimited -s 128 2>/dev/null 4 | 5 | PROVIDER="ux" 6 | IDENT="mptsd" 7 | LOGHOST="gf.unixsol.bg" 8 | OUTPUT_HOST="239.78.78.78" 9 | OUTPUT_PORT="5000" 10 | BITRATE="-B 37.3" 11 | CONFS="-g mptsd.conf -n mptsd_nit.conf -c mptsd_channels.conf -e mptsd_epg.conf" 12 | 13 | OUTPUT_HOST="238.0.0.10" 14 | OUTPUT_PORT="5010" 15 | CONFS="" 16 | 17 | BASEDIR="." 18 | PRGNAME="mptsd" 19 | 20 | SERVER="$BASEDIR/$PRGNAME" 21 | PIDFILE="$BASEDIR/$PRGNAME.pid" 22 | PARAMS="-i $PROVIDER/$IDENT $BITRATE -O $OUTPUT_HOST -P $OUTPUT_PORT $CONFS" 23 | #PARAMS="-i $PROVIDER/$IDENT $BITRATE -O $OUTPUT_HOST -P $OUTPUT_PORT $CONFS -l $LOGHOST" 24 | #PARAMS="-i $PROVIDER/$IDENT $BITRATE -O $OUTPUT_HOST -P $OUTPUT_PORT $CONFS -d $PIDFILE" 25 | #PARAMS="-i $PROVIDER/$IDENT $BITRATE -O $OUTPUT_HOST -P $OUTPUT_PORT $CONFS -l $LOGHOST -d $PIDFILE" 26 | -------------------------------------------------------------------------------- /sleep.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd sleep calibration 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libfuncs/log.h" 26 | 27 | #include "config.h" 28 | 29 | void * calibrate_sleep(void *_config) { 30 | struct timeval tv1, tv2; 31 | unsigned long diff = 0, loops = 0; 32 | CONFIG *conf = _config; 33 | 34 | if (!conf->quiet) { 35 | LOGf("\tCalibrating sleep timeout...\n"); 36 | LOGf("\tRequest timeout : %ld us\n", conf->output_tmout); 37 | } 38 | 39 | do { 40 | gettimeofday(&tv1, NULL); 41 | usleep(1); 42 | gettimeofday(&tv2, NULL); 43 | diff += timeval_diff_usec(&tv1, &tv2) - 1; 44 | } while (loops++ != 3000); 45 | 46 | conf->usleep_overhead = diff / loops; 47 | conf->output_tmout -= conf->usleep_overhead; 48 | 49 | if (!conf->quiet) { 50 | LOGf("\tusleep(1) overhead: %ld us\n", conf->usleep_overhead); 51 | LOGf("\tOutput pkt tmout : %ld us\n", conf->output_tmout); 52 | } 53 | 54 | if (conf->output_tmout < 0) { 55 | LOGf("usleep overhead is too high! Make sure the kernel is compiled with CONFIG_HIGH_RES_TIMERS.\n"); 56 | conf->output_tmout = 0; 57 | } 58 | 59 | pthread_exit(0); 60 | } 61 | -------------------------------------------------------------------------------- /sleep.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd sleep calibration header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef SLEEP_H 19 | #define SLEEP_H 20 | 21 | void * calibrate_sleep(void *_config); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /web_pages.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web pages 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "libfuncs/io.h" 26 | #include "libfuncs/log.h" 27 | #include "libfuncs/list.h" 28 | #include "libfuncs/http_response.h" 29 | 30 | #include "config.h" 31 | 32 | extern CONFIG *config; 33 | 34 | void cmd_index(int clientsock) { 35 | send_200_ok(clientsock); 36 | send_header_textplain(clientsock); 37 | fdputs(clientsock, "\nHi from mptsd.\n"); 38 | } 39 | 40 | void cmd_reconnect(int clientsock) { 41 | send_200_ok(clientsock); 42 | send_header_textplain(clientsock); 43 | fdputsf(clientsock, "\nReconnecting %d inputs.\n", config->inputs->items); 44 | } 45 | -------------------------------------------------------------------------------- /web_pages.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web pages header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef WEB_PAGES_H 19 | #define WEB_PAGES_H 20 | 21 | void cmd_index(int clientsock); 22 | void cmd_reconnect(int clientsock); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /web_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web server 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "libfuncs/libfuncs.h" 27 | 28 | #include "web_pages.h" 29 | #include "web_server.h" 30 | 31 | typedef struct req_info { 32 | int clientsock; 33 | struct sockaddr_in client; 34 | } request_info; 35 | 36 | extern int keep_going; 37 | 38 | #define NEXT_CLIENT { FREE(path); FREE(buf); pthread_exit(0); } 39 | #define SHUTDOWN_CLIENT { FREE(path); FREE(buf); shutdown_fd(&clientsock); pthread_exit(0); } 40 | #define BUF_SIZE 1024 41 | 42 | void *process_web_request(void *); 43 | 44 | void *web_server_thread(void *data) { 45 | CONFIG *conf = data; 46 | while (keep_going) { 47 | struct sockaddr_in client; 48 | unsigned int clientlen = sizeof(client); 49 | int clientsock; 50 | clientsock = accept(conf->server_socket, (struct sockaddr *) &client, &clientlen); 51 | if (clientsock < 0) { 52 | if (conf->server_socket > -1) // The server_socket is closed on exit, so do not report errors 53 | LOGf("ERROR : Failed to accept client fd: %i err: %s\n", clientsock, strerror(errno)); 54 | if (errno==EMFILE || errno==ENFILE) /* No more FDs */ 55 | break; 56 | } else { 57 | request_info *req; 58 | pthread_t req_thread; 59 | req = malloc(sizeof(request_info)); 60 | if (!req) { 61 | log_perror("Can't allocate request_info", errno); 62 | continue; 63 | } 64 | req->clientsock = clientsock; 65 | req->client = client; 66 | if (pthread_create(&req_thread, NULL, (void *)&process_web_request, (void *)req)) { 67 | log_perror("Error creating request processing thread.", errno); 68 | exit(1); 69 | } 70 | pthread_detach(req_thread); 71 | } 72 | } 73 | 74 | pthread_exit(0); 75 | } 76 | 77 | void web_server_start(CONFIG *conf) { 78 | if (conf->server_socket > -1) 79 | pthread_create(&conf->server_thread, NULL, &web_server_thread, conf); 80 | } 81 | 82 | void web_server_stop(CONFIG *conf) { 83 | if (conf->server_socket > -1) { 84 | shutdown_fd(&conf->server_socket); 85 | pthread_join(conf->server_thread, NULL); 86 | } 87 | } 88 | 89 | void *process_web_request(void *in_req) { 90 | request_info *req = (request_info *)in_req; 91 | int clientsock = req->clientsock; 92 | regmatch_t res[3]; 93 | char *path=NULL, *buf=NULL; 94 | FREE(req); 95 | 96 | signal(SIGPIPE, SIG_IGN); 97 | 98 | if (!keep_going) 99 | pthread_exit(0); 100 | 101 | buf = malloc(BUF_SIZE); 102 | if (!buf) { 103 | log_perror("Can't allocate buffer", errno); 104 | SHUTDOWN_CLIENT; 105 | } 106 | 107 | if (fdgetline(clientsock,buf,BUF_SIZE)<=0) { 108 | SHUTDOWN_CLIENT; 109 | } 110 | 111 | regex_t request_get; 112 | regcomp(&request_get, "^GET /([^ ]*) HTTP/1.*$", REG_EXTENDED); 113 | if (regexec(&request_get,buf,2,res,0)==REG_NOMATCH) { 114 | send_501_not_implemented(clientsock); 115 | SHUTDOWN_CLIENT; 116 | } 117 | 118 | buf[res[1].rm_eo]=0; 119 | chomp(buf+res[1].rm_so); 120 | if (buf[res[1].rm_eo-1]=='/') buf[res[1].rm_eo-1]=0; 121 | path = strdup(buf+res[1].rm_so); 122 | regfree(&request_get); 123 | 124 | while (fdgetline(clientsock,buf,BUF_SIZE) > 0) { 125 | if (buf[0] == '\n' || buf[0] == '\r') // End of headers 126 | break; 127 | } 128 | 129 | if (strlen(path) == 0) { 130 | cmd_index(clientsock); 131 | } else if (strstr(path,"reconnect")==path) { 132 | cmd_reconnect(clientsock); 133 | } else { 134 | send_404_not_found(clientsock); 135 | } 136 | 137 | SHUTDOWN_CLIENT; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /web_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mptsd internal web server header file 3 | * Copyright (C) 2010-2011 Unix Solutions Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 17 | */ 18 | #ifndef WEB_SERVER_H 19 | # define WEB_SERVER_H 20 | 21 | #include "config.h" 22 | 23 | void web_server_start(CONFIG *conf); 24 | void web_server_stop(CONFIG *conf); 25 | 26 | #endif 27 | --------------------------------------------------------------------------------